Skip to content
This repository was archived by the owner on Jul 10, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/fluence-js/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
75 changes: 75 additions & 0 deletions packages/fluence-js/src/__test__/integration/jsonBuiltin.spec.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
23 changes: 23 additions & 0 deletions packages/fluence-js/src/__test__/unit/builtInHandler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
128 changes: 127 additions & 1 deletion packages/fluence-js/src/internal/builtins/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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: () => {
Expand Down Expand Up @@ -467,10 +484,119 @@ 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) => {
if (req.args.length !== count) {
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';
};