From ab7972f47957aa49a191ecf520ea9d547685c386 Mon Sep 17 00:00:00 2001 From: Evert Pot Date: Sat, 27 Jan 2024 23:18:07 -0500 Subject: [PATCH 1/3] Simplify the serializeItem signature. --- readme.md | 22 ++++++++++++++-------- src/serializer.ts | 20 +++++++++++++++++--- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/readme.md b/readme.md index 6ac2dc8..a8db7d6 100644 --- a/readme.md +++ b/readme.md @@ -199,7 +199,6 @@ import { serializeItem } from 'structured-headers'; - // Returns "foo", "bar" serializeList([ ['foo', new Map()], @@ -213,25 +212,32 @@ sh.serializeDictionary(new Map([ ])); // Returns 42 -serializeItem([42, new Map()]); +serializeItem(42); // Returns 5.5 -serializeItem([5.5, new Map()]); +serializeItem(5.5); // Returns "hello world" -serializeItem(["hello world", new Map()]); +serializeItem("hello world"); // Returns %"Frysl%C3%A2n" -serializeItem(["Fryslân", new Map()]); +serializeItem("Fryslân"); // Returns ?1 -serializeItem([true, new Map()]); +serializeItem(true); // Returns a base-64 representation like: *aGVsbG8=* -serializeItem([new ByteSequence('aGVsbG8='), new Map()]); +serializeItem(new ByteSequence('aGVsbG8=')); // Returns a unix timestamp -serializeItem([new Date(), new Map()]); +serializeItem(new Date()); + +// Parameters to items can be passed as the second argument +// Returns "hello", q=5 +serializeItem( + "hello", + new Map(['q', 5]) +); ``` Browser support diff --git a/src/serializer.ts b/src/serializer.ts index 5c2f19f..a7b2e8e 100644 --- a/src/serializer.ts +++ b/src/serializer.ts @@ -52,9 +52,23 @@ export function serializeDictionary(input: Dictionary): string { } -export function serializeItem(input: Item): string { - - return serializeBareItem(input[0]) + serializeParameters(input[1]); +/** + * Serialize a Structured Fields Item. + * + * An Item is a standalone value like a string, number of date, followed by + * an optional set of parameters. + * + * You can either pass the value in the first argument and parameters in the second, or pass both as a tuple. The later exists for symmetry with parseItem. + */ +export function serializeItem(input: Item): string; +export function serializeItem(input: BareItem, params?: Parameters): string; +export function serializeItem(input: Item|BareItem, params?: Parameters): string { + + if (Array.isArray(input)) { + return serializeBareItem(input[0]) + serializeParameters(input[1]); + } else { + return serializeBareItem(input) + (params?serializeParameters(params):''); + } } From 8d24932091f043fd4709fc95ec9b2b703df80f60 Mon Sep 17 00:00:00 2001 From: Evert Pot Date: Sun, 28 Jan 2024 00:48:53 -0500 Subject: [PATCH 2/3] Simplify dictionary serializing as wel.. --- readme.md | 11 ++++----- src/serializer.ts | 34 +++++++++++++++++----------- src/types.ts | 8 +++++++ test/serializer-tests.js | 49 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 19 deletions(-) create mode 100644 test/serializer-tests.js diff --git a/readme.md b/readme.md index a8db7d6..10a6c08 100644 --- a/readme.md +++ b/readme.md @@ -105,9 +105,8 @@ the second is a `Map` object with parameters. The type is roughly: ```typescript - // The raw value -type BareItem = number | string | Token | ByteSequence | boolean; +type BareItem = number | string | Token | ByteSequence | boolean | Date | DisplayString; // The return type of parseItem type Item = [ @@ -206,10 +205,10 @@ serializeList([ ]); // Returns a=1, b=?0 -sh.serializeDictionary(new Map([ - ['a', [1, new Map()]], - ['b', [false, new Map()]], -])); +sh.serializeDictionary({ + a: 1, + b: false, +}); // Returns 42 serializeItem(42); diff --git a/src/serializer.ts b/src/serializer.ts index a7b2e8e..a814eec 100644 --- a/src/serializer.ts +++ b/src/serializer.ts @@ -2,6 +2,7 @@ import { BareItem, ByteSequence, Dictionary, + DictionaryObject, InnerList, Item, List, @@ -29,24 +30,31 @@ export function serializeList(input: List): string { } -export function serializeDictionary(input: Dictionary): string { +export function serializeDictionary(input: Dictionary | DictionaryObject): string { - return Array.from( - input.entries() - ).map(([key, value]) => { + const entries: Iterable<[string, BareItem|Item|InnerList]> = input instanceof Map ? input.entries() : Object.entries(input); - let out = serializeKey(key); - if (value[0]===true) { - out += serializeParameters(value[1]); + return Array.from(entries).map(([key, entry]) => { + + const keyStr = serializeKey(key); + + if (Array.isArray(entry)) { + if (entry[0]===true) { + return keyStr + serializeParameters(entry[1]); + } else { + if (isInnerList(entry)) { + return keyStr + '=' + serializeInnerList(entry); + } else { + return keyStr + '=' + serializeItem(entry); + } + } } else { - out += '='; - if (isInnerList(value)) { - out += serializeInnerList(value); + if (entry===true) { + return keyStr; } else { - out += serializeItem(value); + return keyStr + '=' + serializeBareItem(entry); } } - return out; }).join(', '); @@ -61,7 +69,7 @@ export function serializeDictionary(input: Dictionary): string { * You can either pass the value in the first argument and parameters in the second, or pass both as a tuple. The later exists for symmetry with parseItem. */ export function serializeItem(input: Item): string; -export function serializeItem(input: BareItem, params?: Parameters): string; +export function serializeItem(input: BareItem, params?: Parameters): string; export function serializeItem(input: Item|BareItem, params?: Parameters): string { if (Array.isArray(input)) { diff --git a/src/types.ts b/src/types.ts index cba19a9..2d0d1eb 100644 --- a/src/types.ts +++ b/src/types.ts @@ -31,6 +31,14 @@ export type Parameters = Map; */ export type Dictionary = Map; +/** + * Another representatation of a Dictionary. + * + * Serialize functions also accept an Object instead of a Map for a + * Dictionary. Parse functions will always return the Map however. + */ +export type DictionaryObject = Record; + export class ByteSequence { base64Value: string; diff --git a/test/serializer-tests.js b/test/serializer-tests.js new file mode 100644 index 0000000..19b47c6 --- /dev/null +++ b/test/serializer-tests.js @@ -0,0 +1,49 @@ +const { serializeDictionary, serializeItem, SerializeError } = require("../dist"); +const expect = require('chai').expect; + +/** + * These tests cover cases that aren't covered by the HTTPWG tests. + */ +describe('serializer shorthands', () => { + + describe('serializeDictionary', () => { + + it('should support a simple object syntax', () => { + + const simpleDict = { + a: 1, + b: true, + c: 'd', + f: [[1,2,3], new Map([['a', 'b']])], + }; + + const str = serializeDictionary(simpleDict); + expect(str).to.equal( + 'a=1, b, c="d", f=(1 2 3);a="b"' + ); + + }); + + }); + + describe('serializeItem', () => { + + it('should error when passing a type that\'s not recognized', () => { + + let caught = false; + try { + serializeItem(Symbol.for('bla')); + } catch (err) { + if (err instanceof SerializeError) { + caught = true; + } else { + throw err; + } + } + expect(caught).to.be.true; + + }); + + }); + +}); From 6a1a195315948d69e6e1b1818436d11f46331ca9 Mon Sep 17 00:00:00 2001 From: Evert Pot Date: Sun, 28 Jan 2024 00:52:12 -0500 Subject: [PATCH 3/3] Update changelog --- changelog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/changelog.md b/changelog.md index 26f2b8c..a01fa78 100644 --- a/changelog.md +++ b/changelog.md @@ -6,6 +6,9 @@ ChangeLog * Support for a new 'Date' type, from draft [draft-ietf-httpbis-sfbis-02][7]. * Support for the "Display String" type. +* Simplified serializing Dictionaries and Items. The existing signatures still + work, but the new API is a bit more natural and doesn\'t require wrapping + everything in arrays and Maps. * Now requires Node 18.