From de7ddd31e6dd2568d36b39ec74599f54962c70ef Mon Sep 17 00:00:00 2001 From: TheoryOfNekomata Date: Sat, 1 Apr 2023 18:47:21 +0800 Subject: [PATCH] Update serializers Make de/serializer functions sync, and add URL-encoded form type. --- package.json | 19 +++++++- src/common.ts | 4 +- src/index.ts | 36 +++++++------- src/types/application/json.ts | 12 ++--- .../application/x-www-form-urlencoded.ts | 16 +++++++ src/types/application/xml.ts | 12 ++--- test/application/json.test.ts | 48 +++++++++---------- .../application/x-www-form-urlencoded.test.ts | 34 +++++++++++++ test/application/xml.test.ts | 48 +++++++++---------- 9 files changed, 146 insertions(+), 83 deletions(-) create mode 100644 src/types/application/x-www-form-urlencoded.ts create mode 100644 test/application/x-www-form-urlencoded.test.ts diff --git a/package.json b/package.json index 2d171e3..e0fa0e7 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "@modal-sh/oatmeal", + "name": "@modal-sh/oatmeal-core", "version": "0.0.0", "files": [ "dist", @@ -48,5 +48,22 @@ }, "dependencies": { "xml-js": "^1.6.11" + }, + "types": "./dist/types/index.d.ts", + "main": "./dist/cjs/production/index.js", + "module": "./dist/esm/production/index.js", + "exports": { + ".": { + "development": { + "require": "./dist/cjs/development/index.js", + "import": "./dist/esm/development/index.js" + }, + "require": "./dist/cjs/production/index.js", + "import": "./dist/esm/production/index.js", + "types": "./dist/types/index.d.ts" + } + }, + "typesVersions": { + "*": {} } } diff --git a/src/common.ts b/src/common.ts index c370510..ec84109 100644 --- a/src/common.ts +++ b/src/common.ts @@ -3,6 +3,6 @@ export interface TypeOptions { } export interface SerializeType { - serialize: (data: T, options: Options) => Promise; - deserialize: (serialized: string, options: Options) => Promise; + serialize: (data: T, options: Options) => string; + deserialize: (serialized: string, options: Options) => T; } diff --git a/src/index.ts b/src/index.ts index 2474a59..fdae9ab 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,40 +1,38 @@ import { TypeOptions, SerializeType } from 'src/common'; +import * as applicationJson from 'src/types/application/json'; +import * as applicationXml from 'src/types/application/xml'; +import * as applicationXWwwFormUrlencoded from 'src/types/application/x-www-form-urlencoded'; -interface LoadSerializeModule { - (): Promise; -} - -const TYPES: Record> = { - 'application/json': () => import('src/types/application/json'), - 'text/json': () => import('src/types/application/json'), - 'application/xml': () => import('src/types/application/xml'), - 'text/xml': () => import('src/types/application/xml'), +const TYPES: Record = { + 'application/json': applicationJson, + 'text/json': applicationJson, + 'application/xml': applicationXml, + 'text/xml': applicationXml, + 'application/x-www-form-urlencoded': applicationXWwwFormUrlencoded, }; export const AVAILABLE_TYPES: (keyof typeof TYPES)[] = Object.keys(TYPES); -export const serialize = async ( +export const serialize = ( data: T, options: O, -): Promise => { +): string => { const { type: optionsType } = options; - const { [optionsType]: loadType } = TYPES; - if (!loadType) { + const { [optionsType]: serializeType } = TYPES; + if (!serializeType) { throw new Error(`Unsupported type: ${optionsType}`); } - const serializeType = await loadType(); return serializeType.serialize(data, options); }; -export const deserialize = async ( +export const deserialize = ( serialized: string, options: O, -): Promise => { +): T => { const { type: optionsType } = options; - const { [optionsType]: loadType } = TYPES; - if (!loadType) { + const { [optionsType]: serializeType } = TYPES; + if (!serializeType) { throw new Error(`Unsupported type: ${optionsType}`); } - const serializeType = await loadType(); return serializeType.deserialize(serialized, options); }; diff --git a/src/types/application/json.ts b/src/types/application/json.ts index 0efae2c..bdb7346 100644 --- a/src/types/application/json.ts +++ b/src/types/application/json.ts @@ -9,22 +9,22 @@ interface SerializeApplicationJsonOptions extends CommonApplicationJsonOptions { replacer?: (key: string, value: unknown) => unknown; } -export const serialize = async ( +export const serialize = ( data: T, options = {} as O, -): Promise => { +): string => { const theOptions = options as SerializeApplicationJsonOptions; - return Promise.resolve(JSON.stringify(data, theOptions.replacer, theOptions.indent)); + return JSON.stringify(data, theOptions.replacer, theOptions.indent); }; interface DeserializeApplicationJsonOptions extends CommonApplicationJsonOptions { reviver?: (key: string, value: unknown) => unknown; } -export const deserialize = async ( +export const deserialize = ( serialized: string, options = {} as O, -): Promise => { +): T => { const theOptions = options as DeserializeApplicationJsonOptions; - return Promise.resolve(JSON.parse(serialized, theOptions.reviver) as T); + return JSON.parse(serialized, theOptions.reviver) as T; }; diff --git a/src/types/application/x-www-form-urlencoded.ts b/src/types/application/x-www-form-urlencoded.ts new file mode 100644 index 0000000..748fe93 --- /dev/null +++ b/src/types/application/x-www-form-urlencoded.ts @@ -0,0 +1,16 @@ +export const serialize = ( + data: T, +): string => { + if (typeof data !== 'object') { + throw new TypeError('Data must be an object'); + } + return new URLSearchParams(data as Record).toString(); +} + +export const deserialize = ( + serialized: string, +): T => { + return Object.fromEntries( + new URLSearchParams(serialized).entries() + ) as unknown as T; +} diff --git a/src/types/application/xml.ts b/src/types/application/xml.ts index ee2a772..a7a84e2 100644 --- a/src/types/application/xml.ts +++ b/src/types/application/xml.ts @@ -1,4 +1,4 @@ -import type { Element } from 'xml-js'; +import { js2xml, xml2js, type Element } from 'xml-js'; import { TypeOptions } from 'src/common'; interface CommonApplicationXmlOptions extends TypeOptions { @@ -119,11 +119,10 @@ parseElement = (element: Element, allowValueOverride = false): unknown => { ); }; -export const serialize = async ( +export const serialize = ( data: T, options = {} as O, -): Promise => { - const { js2xml } = await import('xml-js'); +): string => { const { indent, rootElementName = 'root' } = options as SerializeApplicationXmlOptions; return js2xml( @@ -139,11 +138,10 @@ interface DeserializeApplicationXmlOptions extends CommonApplicationXmlOptions { allowValueOverride?: boolean; } -export const deserialize = async ( +export const deserialize = ( serialized: string, options = {} as O, -): Promise => { - const { xml2js } = await import('xml-js'); +): T => { const { allowValueOverride } = options as DeserializeApplicationXmlOptions; const rawElements = xml2js(serialized, { compact: false, diff --git a/test/application/json.test.ts b/test/application/json.test.ts index c07670d..2471b4d 100644 --- a/test/application/json.test.ts +++ b/test/application/json.test.ts @@ -3,65 +3,65 @@ import { deserialize, serialize } from '../../src/types/application/json'; describe('application/json', () => { describe('serialize', () => { - it('should serialize a string', async () => { - const result = await serialize('Hello, World!', { type: 'application/json' }); + it('should serialize a string', () => { + const result = serialize('Hello, World!', { type: 'application/json' }); expect(result).toBe('"Hello, World!"'); }); - it('should serialize a number', async () => { - const result = await serialize(123, { type: 'application/json' }); + it('should serialize a number', () => { + const result = serialize(123, { type: 'application/json' }); expect(result).toBe('123'); }); - it('should serialize a boolean', async () => { - const result = await serialize(true, { type: 'application/json' }); + it('should serialize a boolean', () => { + const result = serialize(true, { type: 'application/json' }); expect(result).toBe('true'); }); - it('should serialize an array', async () => { - const result = await serialize([1, 2, 3], { type: 'application/json' }); + it('should serialize an array', () => { + const result = serialize([1, 2, 3], { type: 'application/json' }); expect(result).toBe('[1,2,3]'); }); - it('should serialize an object', async () => { - const result = await serialize({ a: 1, b: 2, c: 3 }, { type: 'application/json' }); + it('should serialize an object', () => { + const result = serialize({ a: 1, b: 2, c: 3 }, { type: 'application/json' }); expect(result).toBe('{"a":1,"b":2,"c":3}'); }); - it('should serialize an object with indent', async () => { - const result = await serialize({ a: 1, b: 2, c: 3 }, { type: 'application/json', indent: 2 }); + it('should serialize an object with indent', () => { + const result = serialize({ a: 1, b: 2, c: 3 }, { type: 'application/json', indent: 2 }); expect(result).toBe('{\n "a": 1,\n "b": 2,\n "c": 3\n}'); }); }); describe('deserialize', () => { - it('should deserialize a string', async () => { - const result = await deserialize('"Hello, World!"', { type: 'application/json' }); + it('should deserialize a string', () => { + const result = deserialize('"Hello, World!"', { type: 'application/json' }); expect(result).toBe('Hello, World!'); }); - it('should deserialize a number', async () => { - const result = await deserialize('123', { type: 'application/json' }); + it('should deserialize a number', () => { + const result = deserialize('123', { type: 'application/json' }); expect(result).toBe(123); }); - it('should deserialize a boolean', async () => { - const result = await deserialize('true', { type: 'application/json' }); + it('should deserialize a boolean', () => { + const result = deserialize('true', { type: 'application/json' }); expect(result).toBe(true); }); - it('should deserialize an array', async () => { - const result = await deserialize('[1,2,3]', { type: 'application/json' }); + it('should deserialize an array', () => { + const result = deserialize('[1,2,3]', { type: 'application/json' }); expect(result).toEqual([1, 2, 3]); }); - it('should deserialize an object', async () => { - const result = await deserialize<{ a: number, b: number, c: number }>('{"a":1,"b":2,"c":3}', { type: 'application/json' }); + it('should deserialize an object', () => { + const result = deserialize<{ a: number, b: number, c: number }>('{"a":1,"b":2,"c":3}', { type: 'application/json' }); expect(result).toEqual({ a: 1, b: 2, c: 3 }); }); - it('should deserialize an object with indent', async () => { - const result = await deserialize<{ a: number, b: number, c: number }>( + it('should deserialize an object with indent', () => { + const result = deserialize<{ a: number, b: number, c: number }>( '{\n "a": 1,\n "b": 2,\n "c": 3\n}', { type: 'application/json' }, ); diff --git a/test/application/x-www-form-urlencoded.test.ts b/test/application/x-www-form-urlencoded.test.ts new file mode 100644 index 0000000..6c3f616 --- /dev/null +++ b/test/application/x-www-form-urlencoded.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from 'vitest'; +import { deserialize, serialize } from '../../src/types/application/x-www-form-urlencoded'; + +describe('application/x-www-form-urlencoded', () => { + describe('serialize', () => { + it('should throw when serializing a string', () => { + expect(() => serialize('Hello, World!')).toThrow(); + }); + + it('should throw when serializing a number', () => { + expect(() => serialize(123)).toThrow(); + }); + + it('should throw when serializing a boolean', () => { + expect(() => serialize(true)).toThrow(); + }); + + it('should throw when serializing an array', () => { + expect(() => serialize([1, 2, 3])).toThrow(); + }); + + it('should serialize an object', () => { + const result = serialize({ a: 1, b: 2, c: 3 }); + expect(result).toBe('a=1&b=2&c=3'); + }); + }); + + describe('deserialize', () => { + it('should deserialize an object', () => { + const result = deserialize<{ a: string, b: string, c: string }>('a=1&b=2&c=3'); + expect(result).toEqual({ a: '1', b: '2', c: '3' }); + }); + }); +}); diff --git a/test/application/xml.test.ts b/test/application/xml.test.ts index f8b5c5b..cbe9938 100644 --- a/test/application/xml.test.ts +++ b/test/application/xml.test.ts @@ -3,38 +3,38 @@ import { serialize, deserialize } from '../../src/types/application/xml'; describe('application/xml', () => { describe('serialize', () => { - it('should serialize a string', async () => { - const result = await serialize('Hello, World!', { type: 'application/xml' }); + it('should serialize a string', () => { + const result = serialize('Hello, World!', { type: 'application/xml' }); expect(result).toBe('Hello, World!'); }); - it('should serialize a number', async () => { - const result = await serialize(123, { type: 'application/xml' }); + it('should serialize a number', () => { + const result = serialize(123, { type: 'application/xml' }); expect(result).toBe('123'); }); - it('should serialize a boolean', async () => { - const result = await serialize(true, { type: 'application/xml' }); + it('should serialize a boolean', () => { + const result = serialize(true, { type: 'application/xml' }); expect(result).toBe('true'); }); - it('should serialize an array', async () => { - const result = await serialize([1, 2, 3], { type: 'application/xml' }); + it('should serialize an array', () => { + const result = serialize([1, 2, 3], { type: 'application/xml' }); expect(result).toBe('<_0 type="number">1<_1 type="number">2<_2 type="number">3'); }); - it('should serialize an object', async () => { - const result = await serialize({ a: 1, b: 2, c: 3 }, { type: 'application/xml' }); + it('should serialize an object', () => { + const result = serialize({ a: 1, b: 2, c: 3 }, { type: 'application/xml' }); expect(result).toBe('123'); }); - it('should serialize an object with a custom root element name', async () => { - const result = await serialize({ a: 1, b: 2, c: 3 }, { type: 'application/xml', rootElementName: 'fsh', }); + it('should serialize an object with a custom root element name', () => { + const result = serialize({ a: 1, b: 2, c: 3 }, { type: 'application/xml', rootElementName: 'fsh', }); expect(result).toBe('123'); }); - it('should serialize an object with indent', async () => { - const result = await serialize({ a: 1, b: 2, c: 3 }, { type: 'application/xml', indent: 2 }); + it('should serialize an object with indent', () => { + const result = serialize({ a: 1, b: 2, c: 3 }, { type: 'application/xml', indent: 2 }); expect(result).toBe(` 1 2 @@ -44,28 +44,28 @@ describe('application/xml', () => { }); describe('deserialize', () => { - it('should deserialize a string', async () => { - const result = await deserialize('Hello, World!', { type: 'application/xml' }); + it('should deserialize a string', () => { + const result = deserialize('Hello, World!', { type: 'application/xml' }); expect(result).toBe('Hello, World!'); }); - it('should deserialize a number', async () => { - const result = await deserialize('123', { type: 'application/xml' }); + it('should deserialize a number', () => { + const result = deserialize('123', { type: 'application/xml' }); expect(result).toBe(123); }); - it('should deserialize a boolean', async () => { - const result = await deserialize('true', { type: 'application/xml' }); + it('should deserialize a boolean', () => { + const result = deserialize('true', { type: 'application/xml' }); expect(result).toBe(true); }); - it('should deserialize an array', async () => { - const result = await deserialize('<_0 type="number">1<_1 type="number">2<_2 type="number">3', { type: 'application/xml' }); + it('should deserialize an array', () => { + const result = deserialize('<_0 type="number">1<_1 type="number">2<_2 type="number">3', { type: 'application/xml' }); expect(result).toEqual([1, 2, 3]); }); - it('should deserialize an object', async () => { - const result = await deserialize<{ a: number, b: number, c: number }>( + it('should deserialize an object', () => { + const result = deserialize<{ a: number, b: number, c: number }>( '123', { type: 'application/xml' }, );