Skip to content

Commit

Permalink
feat: add a simple implementation for stringifing and parsing JSON
Browse files Browse the repository at this point in the history
  • Loading branch information
yifanwww committed Sep 17, 2023
1 parent 8f815b8 commit ee17db9
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const naming = [

{ selector: 'objectLiteralProperty', format: null },

{ selector: 'variable', format: ['camelCase', 'UPPER_CASE'], leadingUnderscore: 'allow' },
{ selector: 'variable', format: ['camelCase', 'PascalCase', 'UPPER_CASE'], leadingUnderscore: 'allow' },
];

module.exports = {
Expand Down
62 changes: 62 additions & 0 deletions src/__tests__/json.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Err, Ok } from '../factory';
import { ResultJSON } from '../json';

describe(`Test fn \`${ResultJSON.stringify.name}\``, () => {
it('should convert a Result to a JSON string', () => {
expect(ResultJSON.stringify(Ok(1))).toBe('{"ok":1}');
expect(ResultJSON.stringify(Ok('hello world'))).toBe('{"ok":"hello world"}');
expect(ResultJSON.stringify(Ok(null))).toBe('{"ok":null}');
expect(ResultJSON.stringify(Ok(undefined))).toBe('{}');
expect(ResultJSON.stringify(Ok({}))).toBe('{"ok":{}}');
expect(ResultJSON.stringify(Ok([]))).toBe('{"ok":[]}');

expect(ResultJSON.stringify(Err(1))).toBe('{"err":1}');
expect(ResultJSON.stringify(Err('hello world'))).toBe('{"err":"hello world"}');
expect(ResultJSON.stringify(Err(null))).toBe('{"err":null}');
expect(ResultJSON.stringify(Err(undefined))).toBe('{}');
expect(ResultJSON.stringify(Err({}))).toBe('{"err":{}}');
expect(ResultJSON.stringify(Err([]))).toBe('{"err":[]}');

expect(ResultJSON.stringify(Ok(Ok(1)))).toBe('{"ok":{"ok":1}}');
expect(ResultJSON.stringify(Ok(Err(1)))).toBe('{"ok":{"err":1}}');
expect(ResultJSON.stringify(Err(Ok(1)))).toBe('{"err":{"ok":1}}');
expect(ResultJSON.stringify(Err(Err(1)))).toBe('{"err":{"err":1}}');
});
});

describe(`Test fn \`${ResultJSON.parse.name}\``, () => {
it('should parse the valid json string', () => {
expect(ResultJSON.parse('{"ok":1}').unwrap().unwrap()).toBe(1);
expect(ResultJSON.parse('{"ok":"hello world"}').unwrap().unwrap()).toBe('hello world');
expect(ResultJSON.parse('{"ok":null}').unwrap().unwrap()).toBeNull();
expect(ResultJSON.parse('{"ok":{}}').unwrap().unwrap()).toStrictEqual({});
expect(ResultJSON.parse('{"ok":[]}').unwrap().unwrap()).toStrictEqual([]);

expect(ResultJSON.parse('{"err":1}').unwrap().unwrapErr()).toBe(1);
expect(ResultJSON.parse('{"err":"hello world"}').unwrap().unwrapErr()).toBe('hello world');
expect(ResultJSON.parse('{"err":null}').unwrap().unwrapErr()).toBeNull();
expect(ResultJSON.parse('{"err":{}}').unwrap().unwrapErr()).toStrictEqual({});
expect(ResultJSON.parse('{"err":[]}').unwrap().unwrapErr()).toStrictEqual([]);

expect(ResultJSON.parse('{"ok":{"ok":1}}').unwrap().unwrap()).toStrictEqual({ ok: 1 });
expect(ResultJSON.parse('{"ok":{"err":1}}').unwrap().unwrap()).toStrictEqual({ err: 1 });
expect(ResultJSON.parse('{"err":{"ok":1}}').unwrap().unwrapErr()).toStrictEqual({ ok: 1 });
expect(ResultJSON.parse('{"err":{"err":1}}').unwrap().unwrapErr()).toStrictEqual({ err: 1 });
});

it('should return `Err` if failed to parse the valid json string', () => {
expect(ResultJSON.parse('{}').unwrapErr()).toStrictEqual(new Error('Cannot parse to Result, text: {}'));
expect(ResultJSON.parse('1').unwrapErr()).toStrictEqual(new Error('Cannot parse to Result, text: 1'));
expect(ResultJSON.parse('"1"').unwrapErr()).toStrictEqual(new Error('Cannot parse to Result, text: "1"'));
expect(ResultJSON.parse('true').unwrapErr()).toStrictEqual(new Error('Cannot parse to Result, text: true'));
expect(ResultJSON.parse('false').unwrapErr()).toStrictEqual(new Error('Cannot parse to Result, text: false'));
expect(ResultJSON.parse('[]').unwrapErr()).toStrictEqual(new Error('Cannot parse to Result, text: []'));
});

it('should return `Err` if parsing invalid json string', () => {
expect(ResultJSON.parse('{').unwrapErr()).toStrictEqual(new SyntaxError('Unexpected end of JSON input'));
expect(ResultJSON.parse('tru').unwrapErr()).toStrictEqual(new SyntaxError('Unexpected end of JSON input'));
expect(ResultJSON.parse('"1').unwrapErr()).toStrictEqual(new SyntaxError('Unexpected end of JSON input'));
expect(ResultJSON.parse('[').unwrapErr()).toStrictEqual(new SyntaxError('Unexpected end of JSON input'));
});
});
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './factory';
export * from './json';
export * from './resultify';
export * from './types';
57 changes: 57 additions & 0 deletions src/json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Err, Ok } from './factory';
import { RustlikeResult } from './result';
import type { Result } from './types';

type ResultJson = { ok: unknown } | { err: unknown };

function toJSON(result: unknown): unknown {
if (result instanceof RustlikeResult) {
return result.isOk() ? { ok: toJSON(result.unwrapUnchecked()) } : { err: toJSON(result.unwrapErrUnchecked()) };
}
return result;
}

/**
* A simple implementation that convert `Result` to and from JSON string.
*
* The format of the JSON string follows the externally tagged enum representation in Rust Serde.
* https://serde.rs/enum-representations.html#externally-tagged
*/
export const ResultJSON = {
/**
* Converts a JSON string into a `Result`.
*
* The format of the JSON string follows the externally tagged enum representation in Rust Serde.
* https://serde.rs/enum-representations.html#externally-tagged
*
* @param text A valid JSON string.
*/
parse<T, E>(text: string): Result<Result<T, E>, Error> {
let json: ResultJson;
try {
json = JSON.parse(text) as ResultJson;
} catch (err) {
return Err(err as Error);
}
if (typeof json !== 'object' || json === null) return Err(new Error(`Cannot parse to Result, text: ${text}`));
else if ('ok' in json) return Ok(Ok(json.ok as T));
else if ('err' in json) return Ok(Err(json.err as E));
else {
return Err(new Error(`Cannot parse to Result, text: ${text}`));
}
},

/**
* Converts a `Result` to a JSON string.
*
* The format of the JSON string follows the externally tagged enum representation in Rust Serde.
* https://serde.rs/enum-representations.html#externally-tagged
*
* The nested `Result` will be stringified.
*
* @param result A `Result`.
*/
stringify(result: Result<unknown, unknown>): string {
return JSON.stringify(toJSON(result));
},
};

0 comments on commit ee17db9

Please sign in to comment.