Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify serializer functions #56

Merged
merged 3 commits into from
Jan 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.


Expand Down
33 changes: 19 additions & 14 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -199,39 +198,45 @@ import {
serializeItem
} from 'structured-headers';


// Returns "foo", "bar"
serializeList([
['foo', new Map()],
['bar', new Map()],
]);

// 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, 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
Expand Down
52 changes: 37 additions & 15 deletions src/serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
BareItem,
ByteSequence,
Dictionary,
DictionaryObject,
InnerList,
Item,
List,
Expand Down Expand Up @@ -29,32 +30,53 @@ 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(', ');

}

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):'');
}

}

Expand Down
8 changes: 8 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ export type Parameters = Map<string, BareItem>;
*/
export type Dictionary = Map<string, Item|InnerList>;

/**
* 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<string, BareItem|Item|InnerList>;

export class ByteSequence {

base64Value: string;
Expand Down
49 changes: 49 additions & 0 deletions test/serializer-tests.js
Original file line number Diff line number Diff line change
@@ -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;

});

});

});
Loading