Implement json builtin (#195)

This commit is contained in:
Pavel 2022-10-26 16:02:28 +03:00 committed by GitHub
parent cb0a934f31
commit 3ef88d9905
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 226 additions and 2 deletions

View File

@ -1,6 +1,6 @@
{
"name": "@fluencelabs/fluence",
"version": "0.26.2",
"version": "0.26.3",
"description": "TypeScript implementation of Fluence Peer",
"main": "./dist/index.js",
"typings": "./dist/index.d.ts",

View File

@ -0,0 +1,75 @@
import { Particle } from '../../internal/Particle';
import { doNothing } from '../../internal/utils';
import { FluencePeer } from '../../index';
let peer: FluencePeer;
describe('Sig service test suite', () => {
afterEach(async () => {
if (peer) {
await peer.stop();
}
});
beforeEach(async () => {
peer = new FluencePeer();
await peer.start();
});
it('JSON builtin spec', async () => {
const script = `
(seq
(seq
(seq
;; create
(seq
(call %init_peer_id% ("json" "obj") ["name" "nested_first" "num" 1] nested_first)
(call %init_peer_id% ("json" "obj") ["name" "nested_second" "num" 2] nested_second)
)
(call %init_peer_id% ("json" "obj") ["name" "outer_first" "num" 0 "nested" nested_first] outer_first)
)
(seq
;; modify
(seq
(call %init_peer_id% ("json" "put") [outer_first "nested" nested_second] outer_tmp_second)
(call %init_peer_id% ("json" "puts") [outer_tmp_second "name" "outer_second" "num" 3] outer_second)
)
;; stringify and parse
(seq
(call %init_peer_id% ("json" "stringify") [outer_first] outer_first_string)
(call %init_peer_id% ("json" "parse") [outer_first_string] outer_first_parsed)
)
)
)
(call %init_peer_id% ("res" "res") [nested_first nested_second outer_first outer_second outer_first_string outer_first_parsed])
)
`;
const promise = new Promise<any>((resolve) => {
peer.internals.regHandler.common('res', 'res', (req) => {
resolve(req.args);
return {
result: {},
retCode: 0,
};
});
});
const p = peer.internals.createNewParticle(script) as Particle;
await peer.internals.initiateParticle(p, doNothing);
const [nestedFirst, nestedSecond, outerFirst, outerSecond, outerFirstString, outerFirstParsed] = await promise;
const nfExpected = { name: 'nested_first', num: 1 };
const nsExpected = { name: 'nested_second', num: 2 };
const ofExpected = { name: 'outer_first', nested: nfExpected, num: 0 };
const ofString = JSON.stringify(ofExpected);
const osExpected = { name: 'outer_second', num: 3, nested: nsExpected };
expect(nestedFirst).toMatchObject(nfExpected);
expect(nestedSecond).toMatchObject(nsExpected);
expect(outerFirst).toMatchObject(ofExpected);
expect(outerSecond).toMatchObject(osExpected);
expect(outerFirstParsed).toMatchObject(ofExpected);
expect(outerFirstString).toBe(ofString);
});
});

View File

@ -111,6 +111,29 @@ describe('Tests for default handler', () => {
${'array'} | ${'diff'}" | ${[["a", "b", "c"], ["c", "b", "d"]]} | ${0} | ${["a"]}
${'array'} | ${'sdiff'}" | ${[["a", "b", "c"], ["c", "b", "d"]]} | ${0} | ${["a", "d"]}
${'json'} | ${'obj'}" | ${["a", 10, "b", "string", "c", null]} | ${0} | ${{a: 10, b: "string", c: null}}
${'json'} | ${'obj'}" | ${["a", 10, "b", "string", "c"]} | ${1} | ${"Expected even number of argument(s). Got 5"}
${'json'} | ${'obj'}" | ${[]} | ${0} | ${{}}
${'json'} | ${'put'}" | ${[{}, "a", 10]} | ${0} | ${{a: 10}}
${'json'} | ${'put'}" | ${[{b: 11}, "a", 10]} | ${0} | ${{a: 10, b: 11}}
${'json'} | ${'put'}" | ${["a", "a", 11]} | ${1} | ${"Argument 0 expected to be of type object, Got string"}
${'json'} | ${'put'}" | ${[{}, "a", 10, "b", 20]} | ${1} | ${"Expected 3 argument(s). Got 5"}
${'json'} | ${'put'}" | ${[{}]} | ${1} | ${"Expected 3 argument(s). Got 1"}
${'json'} | ${'puts'}" | ${[{}, "a", 10]} | ${0} | ${{a: 10}}
${'json'} | ${'puts'}" | ${[{b: 11}, "a", 10]} | ${0} | ${{a: 10, b: 11}}
${'json'} | ${'puts'}" | ${[{}, "a", 10, "b", "string", "c", null]} | ${0} | ${{a: 10, b: "string", c: null}}
${'json'} | ${'puts'}" | ${[{x: "text"}, "a", 10, "b", "string"]} | ${0} | ${{a: 10, b: "string", x: "text"}}
${'json'} | ${'puts'}" | ${[{}]} | ${1} | ${"Expected more than 3 argument(s). Got 1"}
${'json'} | ${'puts'}" | ${["a", "a", 11]} | ${1} | ${"Argument 0 expected to be of type object, Got string"}
${'json'} | ${'stringify'}" | ${[{a: 10, b: "string", c: null}]} | ${0} | ${"{\"a\":10,\"b\":\"string\",\"c\":null}"}
${'json'} | ${'stringify'}" | ${[1]} | ${1} | ${"Argument 0 expected to be of type object, Got number"}
${'json'} | ${'parse'}" | ${["{\"a\":10,\"b\":\"string\",\"c\":null}"]} | ${0} | ${{a: 10, b: "string", c: null}}
${'json'} | ${'parse'}" | ${["incorrect"]} | ${1} | ${"Unexpected token i in JSON at position 0"}
${'json'} | ${'parse'}" | ${[10]} | ${1} | ${"Argument 0 expected to be of type string, Got number"}
`.test(
//
'$fnName with $args expected retcode: $retCode and result: $result',

View File

@ -18,7 +18,7 @@ import { encode, decode } from 'bs58';
import { sha256 } from 'multiformats/hashes/sha2';
import { CallServiceResult } from '@fluencelabs/avm';
import { GenericCallServiceHandler, ResultCodes } from '../commonTypes';
import { CallServiceData, GenericCallServiceHandler, ResultCodes } from '../commonTypes';
import { jsonify } from '../utils';
import Buffer from '../Buffer';
@ -40,6 +40,23 @@ const errorNotImpl = (methodName: string) => {
return error(`The JS implementation of Peer does not support "${methodName}"`);
};
const makeJsonImpl = (args: Array<any>) => {
const [obj, ...kvs] = args;
const toMerge: Record<string, any> = {};
for (let i = 0; i < kvs.length / 2; i++) {
const k = kvs[i * 2];
if (!isString(k)) {
return error(`Argument ${k} is expected to be string`);
}
const v = kvs[i * 2 + 1];
toMerge[k] = v;
}
const res = { ...obj, ...toMerge };
return success(res);
};
export const builtInServices: Record<string, Record<string, GenericCallServiceHandler>> = {
peer: {
identify: () => {
@ -467,6 +484,81 @@ export const builtInServices: Record<string, Record<string, GenericCallServiceHa
return success(sdiff);
},
},
json: {
obj: (req) => {
let err;
if ((err = checkForArgumentsCountEven(req, 1))) {
return err;
}
return makeJsonImpl([{}, ...req.args]);
},
put: (req) => {
let err;
if ((err = checkForArgumentsCount(req, 3))) {
return err;
}
if((err = checkForArgumentType(req, 0, "object"))) {
return err;
}
return makeJsonImpl(req.args);
},
puts: (req) => {
let err;
if ((err = checkForArgumentsCountOdd(req, 1))) {
return err;
}
if ((err = checkForArgumentsCountMoreThan(req, 3))) {
return err;
}
if((err = checkForArgumentType(req, 0, "object"))) {
return err;
}
return makeJsonImpl(req.args);
},
stringify: (req) => {
let err;
if ((err = checkForArgumentsCount(req, 1))) {
return err;
}
if((err = checkForArgumentType(req, 0, "object"))) {
return err;
}
const [json] = req.args;
const res = JSON.stringify(json);
return success(res);
},
parse: (req) => {
let err;
if ((err = checkForArgumentsCount(req, 1))) {
return err;
}
if((err = checkForArgumentType(req, 0, "string"))) {
return err;
}
const [raw] = req.args;
try{
const json = JSON.parse(raw);
return success(json);
} catch(err: any) {
return error(err.message);
}
},
},
} as const;
const checkForArgumentsCount = (req: { args: Array<unknown> }, count: number) => {
@ -474,3 +566,37 @@ const checkForArgumentsCount = (req: { args: Array<unknown> }, count: number) =>
return error(`Expected ${count} argument(s). Got ${req.args.length}`);
}
};
const checkForArgumentsCountMoreThan = (req: { args: Array<unknown> }, count: number) => {
if (req.args.length < count) {
return error(`Expected more than ${count} argument(s). Got ${req.args.length}`);
}
};
const checkForArgumentsCountEven = (req: { args: Array<unknown> }, count: number) => {
if (req.args.length % 2 === 1) {
return error(`Expected even number of argument(s). Got ${req.args.length}`);
}
};
const checkForArgumentsCountOdd = (req: { args: Array<unknown> }, count: number) => {
if (req.args.length % 2 === 0) {
return error(`Expected odd number of argument(s). Got ${req.args.length}`);
}
};
const checkForArgumentType = (req: { args: Array<unknown> }, index: number, type: string) => {
const actual = typeof req.args[index];
if (actual !== type) {
return error(`Argument ${index} expected to be of type ${type}, Got ${actual}`);
}
};
export const isString = (unknown: unknown): unknown is string => {
return unknown !== null && typeof unknown === 'string';
};
export const isObject = (unknown: unknown): unknown is object => {
return unknown !== null && typeof unknown === 'object';
};