From 90cdf66fa87020101688e1673aad0c28a153ea40 Mon Sep 17 00:00:00 2001 From: Evert Pot Date: Fri, 26 Apr 2019 01:35:20 -0400 Subject: [PATCH 1/6] Serializer work in progress --- index.js | 40 --------------- src/index.ts | 77 ++++++++++++++++++++++++++++ src/parser.ts | 14 +---- src/serializer.ts | 127 ++++++++++++++++++++++++++++++++++++++++++++++ src/types.ts | 11 ++++ 5 files changed, 217 insertions(+), 52 deletions(-) delete mode 100644 index.js create mode 100644 src/index.ts create mode 100644 src/serializer.ts create mode 100644 src/types.ts diff --git a/index.js b/index.js deleted file mode 100644 index 26b0b08..0000000 --- a/index.js +++ /dev/null @@ -1,40 +0,0 @@ -var Parser = require('./dist/parser'); - -module.exports = { - - parseDictionary: function(input) { - - var parser = new Parser(input); - return parser.parseDictionary(); - - }, - - parseList: function(input) { - - var parser = new Parser(input); - return parser.parseList(); - - }, - - parseListList: function(input) { - - var parser = new Parser(input); - return parser.parseListList(); - - }, - - parseParamList: function(input) { - - var parser = new Parser(input); - return parser.parseParamList(); - - }, - - parseItem: function(input) { - - var parser = new Parser(input); - return parser.parseItem(); - - } - -}; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..aa73352 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,77 @@ +import Parser from './parser'; +import Serializer from './serializer'; +import { Dictionary, Item, List, ListList, ParameterizedList } from './types'; + +module.exports = { + + parseDictionary: function(input: string): Dictionary { + + let parser = new Parser(input); + return parser.parseDictionary(); + + }, + + parseList: function(input: string): List { + + let parser = new Parser(input); + return parser.parseList(); + + }, + + parseListList: function(input: string): ListList { + + let parser = new Parser(input); + return parser.parseListList(); + + }, + + parseParamList: function(input: string): ParameterizedList { + + let parser = new Parser(input); + return parser.parseParamList(); + + }, + + parseItem: function(input: string): Item { + + let parser = new Parser(input); + return parser.parseItem(); + + }, + + serializeDictionary: function(input: Dictionary): string { + + let serializer = new Serializer(); + return serializer.serializeDictionary(input); + + }, + + serializeList: function(input: List) { + + let serializer = new Serializer(); + return serializer.serializeList(input); + + }, + + serializeListList: function(input: ListList) { + + let serializer = new Serializer(); + return serializer.serializeListList(input); + + }, + + serializeParamList: function(input: ParameterizedList) { + + let serializer = new Serializer(); + return serializer.serializeParamList(input); + + }, + + serializeItem: function(input: Item) { + + let serializer = new Serializer(); + return serializer.serializeItem(input); + + }, + +}; diff --git a/src/parser.ts b/src/parser.ts index 5928475..91db8f6 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -1,16 +1,6 @@ -type Item = string | number | Buffer | boolean; +import { Dictionary, Item, List, ListList, ParameterizedIdentifier, ParameterizedList } from './types'; -type Dictionary = { - [s: string]: Item -}; - -type List = Item[]; -type ListList = List[]; - -type ParameterizedIdentifier = [string, Dictionary]; -type ParameterizedList = ParameterizedIdentifier[]; - -class Parser { +export default class Parser { input: string; position: number; diff --git a/src/serializer.ts b/src/serializer.ts new file mode 100644 index 0000000..d48a784 --- /dev/null +++ b/src/serializer.ts @@ -0,0 +1,127 @@ +import { Dictionary, Item, List, ListList, ParameterizedList } from './types'; + +export default class Serializer { + + serializeDictionary(input: Dictionary): string { + + const output = []; + for (const [key, value] of Object.entries(input)) { + output.push(this.serializeKey(key) + '=' + this.serializeItem(value)); + } + + return output.join(', '); + + } + + serializeList(input: List): string { + + return input.map(this.serializeItem).join(', '); + + } + + serializeListList(input: ListList): string { + + return input.map( + innerList => innerList.map(this.serializeItem).join('; ') + ).join(', '); + + } + + serializeParamList(input: ParameterizedList): string { + + const output = []; + for (const [key, dict] of input) { + let item = ''; + item += this.serializeKey(key); + + for (const [dictKey, dictValue] of Object.entries(dict)) { + item += ';' + this.serializeKey(dictKey); + if (dictValue) { + item += '=' + this.serializeItem(dictValue); + } + } + + output.push(item); + } + return output.join(', '); + + } + + serializeKey(input: string): string { + + if (!input.match(/^[a-z][a-z0-9_-]*$/)) { + throw new Error('Dictionary keys must start with a-z and only contain a-z0-9_-'); + } + + return input; + + } + + serializeItem(input: Item): string { + + if (typeof input === 'number') { + if (Number.isInteger(input)) { + return this.serializeInt(input); + } + return this.serializeFloat(input); + } + if (typeof input === 'string') { + return this.serializeString(input); + } + // Token ??? + if (typeof input === 'boolean') { + return this.serializeBoolean(input); + } + if (input instanceof Buffer) { + return this.serializeByteSequence(input); + } + throw new Error('Cannot serialize values of type ' + typeof input); + + } + + serializeInt(input: number): string { + if (input > 999_999_999_999_999 || input < -999_999_999_999_999) { + throw new Error('Integers may not be larger than 15 digits'); + } + return input.toString(); + } + + serializeFloat(input: number): string { + + return input.toString(); + + } + + serializeString(input: string): string { + + if (/^[\x1F-\x7F]*$/.test(input)) { + throw new Error('Strings must be in the ASCII range'); + } + + return '"' + input.replace('"', '\\"') + '"'; + + } + + serializeToken(input: string): string { + + if (/^[a-zA-Z][A-Za-z0-9_-\.\:%\*/]*$/.test(input)) { + throw new Error('Tokens must start with a letter and must only contain A-Za-z_-.:%*/'); + } + + return input; + + } + + serializeByteSequence(input: Buffer): string { + + return '*' + input.toString('base64') + '*'; + + } + + serializeBoolean(input: boolean): string { + + return input ? '?1' : '?0'; + + } + +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..5b1a595 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,11 @@ +export type Item = string | number | Buffer | boolean; + +export type Dictionary = { + [s: string]: Item +}; + +export type List = Item[]; +export type ListList = List[]; + +export type ParameterizedIdentifier = [string, Dictionary]; +export type ParameterizedList = ParameterizedIdentifier[]; From 5d823f79f4af9b8e7fe262409af86f2500ea4ab6 Mon Sep 17 00:00:00 2001 From: Evert Pot Date: Sat, 27 Apr 2019 00:10:27 -0400 Subject: [PATCH 2/6] Lint fixes --- src/index.ts | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/index.ts b/src/index.ts index aa73352..14c9bc5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,72 +4,72 @@ import { Dictionary, Item, List, ListList, ParameterizedList } from './types'; module.exports = { - parseDictionary: function(input: string): Dictionary { + parseDictionary: (input: string): Dictionary => { - let parser = new Parser(input); + const parser = new Parser(input); return parser.parseDictionary(); }, - parseList: function(input: string): List { + parseList: (input: string): List => { - let parser = new Parser(input); + const parser = new Parser(input); return parser.parseList(); }, - parseListList: function(input: string): ListList { + parseListList: (input: string): ListList => { - let parser = new Parser(input); + const parser = new Parser(input); return parser.parseListList(); }, - parseParamList: function(input: string): ParameterizedList { + parseParamList: (input: string): ParameterizedList => { - let parser = new Parser(input); + const parser = new Parser(input); return parser.parseParamList(); }, - parseItem: function(input: string): Item { + parseItem: (input: string): Item => { - let parser = new Parser(input); + const parser = new Parser(input); return parser.parseItem(); }, - serializeDictionary: function(input: Dictionary): string { + serializeDictionary: (input: Dictionary): string => { - let serializer = new Serializer(); + const serializer = new Serializer(); return serializer.serializeDictionary(input); }, - serializeList: function(input: List) { + serializeList: (input: List) => { - let serializer = new Serializer(); + const serializer = new Serializer(); return serializer.serializeList(input); }, - serializeListList: function(input: ListList) { + serializeListList: (input: ListList) => { - let serializer = new Serializer(); + const serializer = new Serializer(); return serializer.serializeListList(input); }, - serializeParamList: function(input: ParameterizedList) { + serializeParamList: (input: ParameterizedList) => { - let serializer = new Serializer(); + const serializer = new Serializer(); return serializer.serializeParamList(input); }, - serializeItem: function(input: Item) { + serializeItem: (input: Item) => { - let serializer = new Serializer(); + const serializer = new Serializer(); return serializer.serializeItem(input); }, From f58fb131bd777741c7b88395915b68eda19436a0 Mon Sep 17 00:00:00 2001 From: Evert Pot Date: Sat, 27 Apr 2019 00:14:37 -0400 Subject: [PATCH 3/6] Tests are passing --- test/httpwg-tests.js | 4 ++-- test/parse-dictionary.js | 2 +- test/parse-item.js | 2 +- test/parse-list-list.js | 2 +- test/parse-list.js | 2 +- test/parse-parameterized-list.js | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/httpwg-tests.js b/test/httpwg-tests.js index 8e17f99..0da4ec6 100644 --- a/test/httpwg-tests.js +++ b/test/httpwg-tests.js @@ -54,8 +54,8 @@ function makeTest(test) { const parser = new Parser(test.raw.join(',')); const skipped = [ - 'too long integer', - 'negative too long integer', + 'long integer', + 'long negative integer', ]; it(test.name, function() { diff --git a/test/parse-dictionary.js b/test/parse-dictionary.js index 6dcc1da..d31d0c9 100644 --- a/test/parse-dictionary.js +++ b/test/parse-dictionary.js @@ -1,4 +1,4 @@ -const parse = require('../index').parseDictionary; +const parse = require('../dist').parseDictionary; const expect = require('chai').expect; describe("Dictionaries", () => { diff --git a/test/parse-item.js b/test/parse-item.js index 566122a..01192d2 100644 --- a/test/parse-item.js +++ b/test/parse-item.js @@ -1,4 +1,4 @@ -const parse = require('../index').parseItem; +const parse = require('../dist').parseItem; const expect = require('chai').expect; describe("Lists", () => { diff --git a/test/parse-list-list.js b/test/parse-list-list.js index 5803800..8a7a9eb 100644 --- a/test/parse-list-list.js +++ b/test/parse-list-list.js @@ -1,4 +1,4 @@ -const parse = require('../index').parseListList; +const parse = require('../dist').parseListList; const expect = require('chai').expect; describe("Lists of lists", () => { diff --git a/test/parse-list.js b/test/parse-list.js index 1c609d2..0674403 100644 --- a/test/parse-list.js +++ b/test/parse-list.js @@ -1,4 +1,4 @@ -const parse = require('../index').parseList; +const parse = require('../dist').parseList; const expect = require('chai').expect; describe("Lists", () => { diff --git a/test/parse-parameterized-list.js b/test/parse-parameterized-list.js index f882b81..9be7bbb 100644 --- a/test/parse-parameterized-list.js +++ b/test/parse-parameterized-list.js @@ -1,4 +1,4 @@ -const parse = require('../index').parseParamList; +const parse = require('../dist').parseParamList; const expect = require('chai').expect; describe("Parameterized lists", () => { From 6b7789ff6cfe67c0e4d5330c6e7e7389558166f6 Mon Sep 17 00:00:00 2001 From: Evert Pot Date: Sat, 27 Apr 2019 00:18:09 -0400 Subject: [PATCH 4/6] Fixing code coverage --- package-lock.json | 33 ++++++++++++++++++++++++++++++++- package.json | 6 ++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 09329fa..e03cfab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "structured-headers", - "version": "0.1.1", + "version": "0.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -421,6 +421,12 @@ "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", "dev": true }, + "arg": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.0.tgz", + "integrity": "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==", + "dev": true + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -3148,6 +3154,12 @@ } } }, + "make-error": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", + "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", + "dev": true + }, "mamacro": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz", @@ -4976,6 +4988,19 @@ "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", "dev": true }, + "ts-node": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.1.0.tgz", + "integrity": "sha512-34jpuOrxDuf+O6iW1JpgTRDFynUZ1iEqtYruBqh35gICNjN8x+LpVcPAcwzLPi9VU6mdA3ym+x233nZmZp445A==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "diff": "^3.1.0", + "make-error": "^1.1.1", + "source-map-support": "^0.5.6", + "yn": "^3.0.0" + } + }, "tslib": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", @@ -5507,6 +5532,12 @@ "lodash": "^4.17.11", "yargs": "^12.0.5" } + }, + "yn": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.0.tgz", + "integrity": "sha512-kKfnnYkbTfrAdd0xICNFw7Atm8nKpLcLv9AZGEt+kczL/WQVai4e2V6ZN8U/O+iI6WrNuJjNNOyu4zfhl9D3Hg==", + "dev": true } } } diff --git a/package.json b/package.json index dadbc61..21382a8 100644 --- a/package.json +++ b/package.json @@ -28,9 +28,15 @@ "chai": "^4.2.0", "mocha": "^6.1.4", "nyc": "^14.0.0", + "ts-node": "^8.1.0", "tslint": "^5.16.0", "typescript": "^3.4.5", "webpack": "^4.30.0", "webpack-cli": "^3.3.1" + }, + "nyc": { + "extension": [ + ".ts" + ] } } From 674cc4ce707e9aaf81c6db6d293ac6668f12802e Mon Sep 17 00:00:00 2001 From: Evert Pot Date: Sat, 27 Apr 2019 00:51:13 -0400 Subject: [PATCH 5/6] 100% test coverage --- src/serializer.ts | 10 +++---- test/mocha.opts | 4 +++ test/serialize-dictionary.js | 22 +++++++++++++++ test/serialize-item.js | 53 ++++++++++++++++++++++++++++++++++++ test/serialize-list-list.js | 15 ++++++++++ test/serialize-list.js | 15 ++++++++++ test/serialize-param-list.js | 18 ++++++++++++ test/serialize-token.js | 25 +++++++++++++++++ 8 files changed, 157 insertions(+), 5 deletions(-) create mode 100644 test/mocha.opts create mode 100644 test/serialize-dictionary.js create mode 100644 test/serialize-item.js create mode 100644 test/serialize-list-list.js create mode 100644 test/serialize-list.js create mode 100644 test/serialize-param-list.js create mode 100644 test/serialize-token.js diff --git a/src/serializer.ts b/src/serializer.ts index d48a784..b003aa5 100644 --- a/src/serializer.ts +++ b/src/serializer.ts @@ -15,14 +15,14 @@ export default class Serializer { serializeList(input: List): string { - return input.map(this.serializeItem).join(', '); + return input.map(this.serializeItem.bind(this)).join(', '); } serializeListList(input: ListList): string { return input.map( - innerList => innerList.map(this.serializeItem).join('; ') + innerList => innerList.map(this.serializeItem.bind(this)).join('; ') ).join(', '); } @@ -49,7 +49,7 @@ export default class Serializer { serializeKey(input: string): string { - if (!input.match(/^[a-z][a-z0-9_-]*$/)) { + if (!/^[a-z][a-z0-9_-]*$/.test(input)) { throw new Error('Dictionary keys must start with a-z and only contain a-z0-9_-'); } @@ -94,7 +94,7 @@ export default class Serializer { serializeString(input: string): string { - if (/^[\x1F-\x7F]*$/.test(input)) { + if (!/^[\x1F-\x7F]*$/.test(input)) { throw new Error('Strings must be in the ASCII range'); } @@ -104,7 +104,7 @@ export default class Serializer { serializeToken(input: string): string { - if (/^[a-zA-Z][A-Za-z0-9_-\.\:%\*/]*$/.test(input)) { + if (!/^[a-zA-Z][a-zA-Z0-9_\-\.\:\%\*]*$/.test(input)) { throw new Error('Tokens must start with a letter and must only contain A-Za-z_-.:%*/'); } diff --git a/test/mocha.opts b/test/mocha.opts new file mode 100644 index 0000000..1325232 --- /dev/null +++ b/test/mocha.opts @@ -0,0 +1,4 @@ +--recursive +--require ts-node/register +--exit +test/*.js diff --git a/test/serialize-dictionary.js b/test/serialize-dictionary.js new file mode 100644 index 0000000..998ffa5 --- /dev/null +++ b/test/serialize-dictionary.js @@ -0,0 +1,22 @@ +const serialize = require('../dist').serializeDictionary; +const expect = require('chai').expect; + +describe("Dictionaries", () => { + + it('should serialize dictionaries', () => { + + const input = {a: 5, b: true, c: 'foo'}; + expect( + serialize(input) + ).to.eql('a=5, b=?1, c="foo"'); + + }); + + it('should fail when serializing dictionaries with keys outside the valid range', () => { + + const input = {'%$%': 5}; + expect( () => serialize(input) ).to.throw(Error); + + }); + +}); diff --git a/test/serialize-item.js b/test/serialize-item.js new file mode 100644 index 0000000..783c489 --- /dev/null +++ b/test/serialize-item.js @@ -0,0 +1,53 @@ +const serialize = require('../dist').serializeItem; +const expect = require('chai').expect; + +describe("Items", () => { + + it('should serialize integers', () => { + + const input = 5; + expect( + serialize(input) + ).to.eql('5'); + + }); + it('should fail when serializing too large integers', () => { + + const input = Number.MAX_SAFE_INTEGER; + expect( () => serialize(input) ).to.throw(Error); + + }); + + it('should serialize strings', () => { + + const input = "hello"; + expect( + serialize(input) + ).to.eql('"hello"'); + + }); + + it('should fail when serializing non-ascii strings', () => { + + const input = "hello 🕹"; + expect( () => serialize(input) ).to.throw(Error); + + }); + + it('should fail when serializing objects', () => { + + const input = {}; + expect( () => serialize(input) ).to.throw(Error); + + }); + + it('should serialize Buffer as base64', () => { + + const input = Buffer.from('hello'); + expect( + serialize(input) + ).to.eql('*aGVsbG8=*'); + + }); + +}); diff --git a/test/serialize-list-list.js b/test/serialize-list-list.js new file mode 100644 index 0000000..d7da2cd --- /dev/null +++ b/test/serialize-list-list.js @@ -0,0 +1,15 @@ +const serialize = require('../dist').serializeListList; +const expect = require('chai').expect; + +describe("Lists", () => { + + it('should serialize lists', () => { + + const input = [[1, 'a'], [2, 'b'], [3, false], [4, 0.5]]; + expect( + serialize(input) + ).to.eql('1; "a", 2; "b", 3; ?0, 4; 0.5'); + + }); + +}); diff --git a/test/serialize-list.js b/test/serialize-list.js new file mode 100644 index 0000000..efa3aa0 --- /dev/null +++ b/test/serialize-list.js @@ -0,0 +1,15 @@ +const serialize = require('../dist').serializeList; +const expect = require('chai').expect; + +describe("Lists", () => { + + it('should serialize lists', () => { + + const input = ['foo', 'bar', 5, true]; + expect( + serialize(input) + ).to.eql('"foo", "bar", 5, ?1'); + + }); + +}); diff --git a/test/serialize-param-list.js b/test/serialize-param-list.js new file mode 100644 index 0000000..b29187a --- /dev/null +++ b/test/serialize-param-list.js @@ -0,0 +1,18 @@ +const serialize = require('../dist').serializeParamList; +const expect = require('chai').expect; + +describe("Parameterized Lists", () => { + + it('should serialize param-lists', () => { + + const input = [ + ['foo', {a: 'b'}], + ['bar', {c: 2}] + ]; + expect( + serialize(input) + ).to.eql('foo;a="b", bar;c=2'); + + }); + +}); diff --git a/test/serialize-token.js b/test/serialize-token.js new file mode 100644 index 0000000..3cf5312 --- /dev/null +++ b/test/serialize-token.js @@ -0,0 +1,25 @@ +const Serializer = require('../dist/serializer').default; +const expect = require('chai').expect; + +describe("Tokens", () => { + + it('should serialize tokens', () => { + + const serializer = new Serializer(); + const input = 'foo'; + expect( + serializer.serializeToken(input) + ).to.eql('foo'); + + }); + + it('should fail when serializing tokens with invalid characters', () => { + + const serializer = new Serializer(); + const input = '"'; + expect( + () => serializer.serializeToken(input) + ).to.throw(Error) + + }); +}); From 033818b266ee8ccaf7fb42f490ad0b07d45c1dc8 Mon Sep 17 00:00:00 2001 From: Evert Pot Date: Sat, 27 Apr 2019 00:54:22 -0400 Subject: [PATCH 6/6] Fix webpack --- webpack.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack.config.js b/webpack.config.js index 17df7dd..af793c1 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,6 +1,6 @@ module.exports = [ { - entry: './index', + entry: './dist/index', output: { path: __dirname + '/browser', filename: 'structured-header.min.js',