Skip to content
This repository was archived by the owner on Jul 10, 2025. It is now read-only.

Commit 3ef88d9

Browse files
authored
Implement json builtin (#195)
1 parent cb0a934 commit 3ef88d9

File tree

4 files changed

+226
-2
lines changed

4 files changed

+226
-2
lines changed

packages/fluence-js/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@fluencelabs/fluence",
3-
"version": "0.26.2",
3+
"version": "0.26.3",
44
"description": "TypeScript implementation of Fluence Peer",
55
"main": "./dist/index.js",
66
"typings": "./dist/index.d.ts",
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { Particle } from '../../internal/Particle';
2+
import { doNothing } from '../../internal/utils';
3+
import { FluencePeer } from '../../index';
4+
5+
let peer: FluencePeer;
6+
7+
describe('Sig service test suite', () => {
8+
afterEach(async () => {
9+
if (peer) {
10+
await peer.stop();
11+
}
12+
});
13+
14+
beforeEach(async () => {
15+
peer = new FluencePeer();
16+
await peer.start();
17+
});
18+
19+
it('JSON builtin spec', async () => {
20+
const script = `
21+
(seq
22+
(seq
23+
(seq
24+
;; create
25+
(seq
26+
(call %init_peer_id% ("json" "obj") ["name" "nested_first" "num" 1] nested_first)
27+
(call %init_peer_id% ("json" "obj") ["name" "nested_second" "num" 2] nested_second)
28+
)
29+
(call %init_peer_id% ("json" "obj") ["name" "outer_first" "num" 0 "nested" nested_first] outer_first)
30+
)
31+
(seq
32+
;; modify
33+
(seq
34+
(call %init_peer_id% ("json" "put") [outer_first "nested" nested_second] outer_tmp_second)
35+
(call %init_peer_id% ("json" "puts") [outer_tmp_second "name" "outer_second" "num" 3] outer_second)
36+
)
37+
;; stringify and parse
38+
(seq
39+
(call %init_peer_id% ("json" "stringify") [outer_first] outer_first_string)
40+
(call %init_peer_id% ("json" "parse") [outer_first_string] outer_first_parsed)
41+
)
42+
)
43+
)
44+
(call %init_peer_id% ("res" "res") [nested_first nested_second outer_first outer_second outer_first_string outer_first_parsed])
45+
)
46+
`;
47+
const promise = new Promise<any>((resolve) => {
48+
peer.internals.regHandler.common('res', 'res', (req) => {
49+
resolve(req.args);
50+
return {
51+
result: {},
52+
retCode: 0,
53+
};
54+
});
55+
});
56+
const p = peer.internals.createNewParticle(script) as Particle;
57+
await peer.internals.initiateParticle(p, doNothing);
58+
59+
const [nestedFirst, nestedSecond, outerFirst, outerSecond, outerFirstString, outerFirstParsed] = await promise;
60+
61+
const nfExpected = { name: 'nested_first', num: 1 };
62+
const nsExpected = { name: 'nested_second', num: 2 };
63+
64+
const ofExpected = { name: 'outer_first', nested: nfExpected, num: 0 };
65+
const ofString = JSON.stringify(ofExpected);
66+
const osExpected = { name: 'outer_second', num: 3, nested: nsExpected };
67+
68+
expect(nestedFirst).toMatchObject(nfExpected);
69+
expect(nestedSecond).toMatchObject(nsExpected);
70+
expect(outerFirst).toMatchObject(ofExpected);
71+
expect(outerSecond).toMatchObject(osExpected);
72+
expect(outerFirstParsed).toMatchObject(ofExpected);
73+
expect(outerFirstString).toBe(ofString);
74+
});
75+
});

packages/fluence-js/src/__test__/unit/builtInHandler.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,29 @@ describe('Tests for default handler', () => {
111111
${'array'} | ${'diff'}" | ${[["a", "b", "c"], ["c", "b", "d"]]} | ${0} | ${["a"]}
112112
${'array'} | ${'sdiff'}" | ${[["a", "b", "c"], ["c", "b", "d"]]} | ${0} | ${["a", "d"]}
113113
114+
${'json'} | ${'obj'}" | ${["a", 10, "b", "string", "c", null]} | ${0} | ${{a: 10, b: "string", c: null}}
115+
${'json'} | ${'obj'}" | ${["a", 10, "b", "string", "c"]} | ${1} | ${"Expected even number of argument(s). Got 5"}
116+
${'json'} | ${'obj'}" | ${[]} | ${0} | ${{}}
117+
118+
${'json'} | ${'put'}" | ${[{}, "a", 10]} | ${0} | ${{a: 10}}
119+
${'json'} | ${'put'}" | ${[{b: 11}, "a", 10]} | ${0} | ${{a: 10, b: 11}}
120+
${'json'} | ${'put'}" | ${["a", "a", 11]} | ${1} | ${"Argument 0 expected to be of type object, Got string"}
121+
${'json'} | ${'put'}" | ${[{}, "a", 10, "b", 20]} | ${1} | ${"Expected 3 argument(s). Got 5"}
122+
${'json'} | ${'put'}" | ${[{}]} | ${1} | ${"Expected 3 argument(s). Got 1"}
123+
124+
${'json'} | ${'puts'}" | ${[{}, "a", 10]} | ${0} | ${{a: 10}}
125+
${'json'} | ${'puts'}" | ${[{b: 11}, "a", 10]} | ${0} | ${{a: 10, b: 11}}
126+
${'json'} | ${'puts'}" | ${[{}, "a", 10, "b", "string", "c", null]} | ${0} | ${{a: 10, b: "string", c: null}}
127+
${'json'} | ${'puts'}" | ${[{x: "text"}, "a", 10, "b", "string"]} | ${0} | ${{a: 10, b: "string", x: "text"}}
128+
${'json'} | ${'puts'}" | ${[{}]} | ${1} | ${"Expected more than 3 argument(s). Got 1"}
129+
${'json'} | ${'puts'}" | ${["a", "a", 11]} | ${1} | ${"Argument 0 expected to be of type object, Got string"}
130+
131+
${'json'} | ${'stringify'}" | ${[{a: 10, b: "string", c: null}]} | ${0} | ${"{\"a\":10,\"b\":\"string\",\"c\":null}"}
132+
${'json'} | ${'stringify'}" | ${[1]} | ${1} | ${"Argument 0 expected to be of type object, Got number"}
133+
${'json'} | ${'parse'}" | ${["{\"a\":10,\"b\":\"string\",\"c\":null}"]} | ${0} | ${{a: 10, b: "string", c: null}}
134+
${'json'} | ${'parse'}" | ${["incorrect"]} | ${1} | ${"Unexpected token i in JSON at position 0"}
135+
${'json'} | ${'parse'}" | ${[10]} | ${1} | ${"Argument 0 expected to be of type string, Got number"}
136+
114137
`.test(
115138
//
116139
'$fnName with $args expected retcode: $retCode and result: $result',

packages/fluence-js/src/internal/builtins/common.ts

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { encode, decode } from 'bs58';
1818
import { sha256 } from 'multiformats/hashes/sha2';
1919
import { CallServiceResult } from '@fluencelabs/avm';
2020

21-
import { GenericCallServiceHandler, ResultCodes } from '../commonTypes';
21+
import { CallServiceData, GenericCallServiceHandler, ResultCodes } from '../commonTypes';
2222
import { jsonify } from '../utils';
2323
import Buffer from '../Buffer';
2424

@@ -40,6 +40,23 @@ const errorNotImpl = (methodName: string) => {
4040
return error(`The JS implementation of Peer does not support "${methodName}"`);
4141
};
4242

43+
const makeJsonImpl = (args: Array<any>) => {
44+
const [obj, ...kvs] = args;
45+
46+
const toMerge: Record<string, any> = {};
47+
for (let i = 0; i < kvs.length / 2; i++) {
48+
const k = kvs[i * 2];
49+
if (!isString(k)) {
50+
return error(`Argument ${k} is expected to be string`);
51+
}
52+
const v = kvs[i * 2 + 1];
53+
toMerge[k] = v;
54+
}
55+
56+
const res = { ...obj, ...toMerge };
57+
return success(res);
58+
};
59+
4360
export const builtInServices: Record<string, Record<string, GenericCallServiceHandler>> = {
4461
peer: {
4562
identify: () => {
@@ -467,10 +484,119 @@ export const builtInServices: Record<string, Record<string, GenericCallServiceHa
467484
return success(sdiff);
468485
},
469486
},
487+
488+
json: {
489+
obj: (req) => {
490+
let err;
491+
if ((err = checkForArgumentsCountEven(req, 1))) {
492+
return err;
493+
}
494+
495+
return makeJsonImpl([{}, ...req.args]);
496+
},
497+
498+
put: (req) => {
499+
let err;
500+
if ((err = checkForArgumentsCount(req, 3))) {
501+
return err;
502+
}
503+
504+
if((err = checkForArgumentType(req, 0, "object"))) {
505+
return err;
506+
}
507+
508+
return makeJsonImpl(req.args);
509+
},
510+
511+
puts: (req) => {
512+
let err;
513+
if ((err = checkForArgumentsCountOdd(req, 1))) {
514+
return err;
515+
}
516+
517+
if ((err = checkForArgumentsCountMoreThan(req, 3))) {
518+
return err;
519+
}
520+
521+
if((err = checkForArgumentType(req, 0, "object"))) {
522+
return err;
523+
}
524+
525+
return makeJsonImpl(req.args);
526+
},
527+
528+
stringify: (req) => {
529+
let err;
530+
if ((err = checkForArgumentsCount(req, 1))) {
531+
return err;
532+
}
533+
534+
if((err = checkForArgumentType(req, 0, "object"))) {
535+
return err;
536+
}
537+
538+
const [json] = req.args;
539+
const res = JSON.stringify(json);
540+
return success(res);
541+
},
542+
543+
parse: (req) => {
544+
let err;
545+
if ((err = checkForArgumentsCount(req, 1))) {
546+
return err;
547+
}
548+
549+
if((err = checkForArgumentType(req, 0, "string"))) {
550+
return err;
551+
}
552+
553+
const [raw] = req.args;
554+
try{
555+
const json = JSON.parse(raw);
556+
return success(json);
557+
} catch(err: any) {
558+
return error(err.message);
559+
}
560+
},
561+
},
470562
} as const;
471563

472564
const checkForArgumentsCount = (req: { args: Array<unknown> }, count: number) => {
473565
if (req.args.length !== count) {
474566
return error(`Expected ${count} argument(s). Got ${req.args.length}`);
475567
}
476568
};
569+
570+
const checkForArgumentsCountMoreThan = (req: { args: Array<unknown> }, count: number) => {
571+
if (req.args.length < count) {
572+
return error(`Expected more than ${count} argument(s). Got ${req.args.length}`);
573+
}
574+
};
575+
576+
const checkForArgumentsCountEven = (req: { args: Array<unknown> }, count: number) => {
577+
if (req.args.length % 2 === 1) {
578+
return error(`Expected even number of argument(s). Got ${req.args.length}`);
579+
}
580+
};
581+
582+
const checkForArgumentsCountOdd = (req: { args: Array<unknown> }, count: number) => {
583+
if (req.args.length % 2 === 0) {
584+
return error(`Expected odd number of argument(s). Got ${req.args.length}`);
585+
}
586+
};
587+
588+
const checkForArgumentType = (req: { args: Array<unknown> }, index: number, type: string) => {
589+
const actual = typeof req.args[index];
590+
if (actual !== type) {
591+
return error(`Argument ${index} expected to be of type ${type}, Got ${actual}`);
592+
}
593+
};
594+
595+
export const isString = (unknown: unknown): unknown is string => {
596+
return unknown !== null && typeof unknown === 'string';
597+
};
598+
599+
export const isObject = (unknown: unknown): unknown is object => {
600+
return unknown !== null && typeof unknown === 'object';
601+
};
602+

0 commit comments

Comments
 (0)