Skip to content

Commit 53385c5

Browse files
authored
Implement handlers for built-in calls from avm (#52)
1 parent c01c647 commit 53385c5

File tree

11 files changed

+289
-63
lines changed

11 files changed

+289
-63
lines changed

package-lock.json

Lines changed: 120 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@
3737
"uuid": "8.3.0"
3838
},
3939
"devDependencies": {
40+
"@types/bs58": "^4.0.1",
4041
"@types/jest": "^26.0.22",
4142
"jest": "^26.6.3",
43+
"jest-each": "^27.0.2",
4244
"ts-jest": "^26.5.4",
4345
"typescript": "^3.9.5"
4446
}

src/FluenceClient.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,14 +133,14 @@ export const checkConnection = async (client: FluenceClient, ttl?: number): Prom
133133
.withVariables({
134134
msg,
135135
})
136-
.buildAsFetch<[[string]]>(callbackService, callbackFn);
136+
.buildAsFetch<[string]>(callbackService, callbackFn);
137137

138138
await client.initiateFlow(request);
139139

140140
try {
141-
const [[result]] = await promise;
141+
const [result] = await promise;
142142
if (result != msg) {
143-
log.warn("unexpected behavior. 'identity' must return arguments the passed arguments.");
143+
log.warn("unexpected behavior. 'identity' must return the passed arguments.");
144144
}
145145
return true;
146146
} catch (e) {

src/__test__/integration/client.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ describe('Typescript usage suite', () => {
2626
(call %init_peer_id% ("callback" "callback") [result])
2727
)`,
2828
)
29-
.buildAsFetch<[[string]]>('callback', 'callback');
29+
.buildAsFetch<[string]>('callback', 'callback');
3030
await client.initiateFlow(request);
3131

3232
// assert
33-
const [[result]] = await promise;
33+
const [result] = await promise;
3434
expect(result).toBe('hello world!');
3535
});
3636

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import each from 'jest-each';
2+
import { CallServiceData } from '../../internal/CallServiceHandler';
3+
import makeDefaultClientHandler from '../../internal/defaultClientHandler';
4+
5+
describe('Tests for default handler', () => {
6+
// prettier-ignore
7+
each`
8+
fnName | args | retCode | result
9+
${'identity'} | ${[]} | ${0} | ${{}}
10+
${'identity'} | ${[1]} | ${0} | ${1}
11+
${'identity'} | ${[1, 2]} | ${1} | ${'identity accepts up to 1 arguments, received 2 arguments'}
12+
13+
${'noop'} | ${[1, 2]} | ${0} | ${{}}
14+
15+
${'array'} | ${[1, 2, 3]} | ${0} | ${[1, 2, 3]}
16+
17+
${'concat'} | ${[[1, 2], [3, 4], [5, 6]]} | ${0} | ${[1, 2, 3, 4, 5, 6]}
18+
${'concat'} | ${[[1, 2]]} | ${0} | ${[1, 2]}
19+
${'concat'} | ${[]} | ${0} | ${[]}
20+
${'concat'} | ${[1, [1, 2], 1]} | ${1} | ${"All arguments of 'concat' must be arrays: arguments 0, 2 are not"}
21+
22+
${'string_to_b58'} | ${["test"]} | ${0} | ${"3yZe7d"}
23+
${'string_to_b58'} | ${["test", 1]} | ${1} | ${"string_to_b58 accepts only one string argument"}
24+
25+
${'string_from_b58'} | ${["3yZe7d"]} | ${0} | ${"test"}
26+
${'string_from_b58'} | ${["3yZe7d", 1]} | ${1} | ${"string_from_b58 accepts only one string argument"}
27+
28+
${'bytes_to_b58'} | ${[[116, 101, 115, 116]]} | ${0} | ${"3yZe7d"}
29+
${'bytes_to_b58'} | ${[[116, 101, 115, 116], 1]} | ${1} | ${"bytes_to_b58 accepts only single argument: array of numbers"}
30+
31+
${'bytes_from_b58'} | ${["3yZe7d"]} | ${0} | ${[116, 101, 115, 116]}
32+
${'bytes_from_b58'} | ${["3yZe7d", 1]} | ${1} | ${"bytes_from_b58 accepts only one string argument"}
33+
34+
`.test(
35+
//
36+
'$fnName with $args expected retcode: $retCode and result: $result',
37+
({ fnName, args, retCode, result }) => {
38+
// arrange
39+
const req: CallServiceData = {
40+
serviceId: 'Op',
41+
fnName: fnName,
42+
args: args,
43+
tetraplets: [],
44+
particleContext: {
45+
particleId: 'some',
46+
},
47+
};
48+
49+
// act
50+
const res = makeDefaultClientHandler().execute(req);
51+
52+
// assert
53+
expect(res).toMatchObject({
54+
retCode: retCode,
55+
result: result,
56+
});
57+
const handler = makeDefaultClientHandler();
58+
},
59+
);
60+
});

src/internal/CallServiceHandler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ interface ParticleContext {
2121
/**
2222
* Represents the information passed from AVM when a `call` air instruction is executed on the local peer
2323
*/
24-
interface CallServiceData {
24+
export interface CallServiceData {
2525
/**
2626
* Service ID as specified in `call` air instruction
2727
*/
@@ -58,7 +58,7 @@ export type CallServiceResultType = object | boolean | number | string;
5858
/**
5959
* Represents the result of the `call` air instruction to be returned into AVM
6060
*/
61-
interface CallServiceResult {
61+
export interface CallServiceResult {
6262
/**
6363
* Return code to be returned to AVM
6464
*/

src/internal/ClientImpl.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,12 @@ import { FluenceConnection, FluenceConnectionOptions } from './FluenceConnection
2121
import { PeerIdB58 } from './commonTypes';
2222
import { FluenceClient } from '../FluenceClient';
2323
import { RequestFlow } from './RequestFlow';
24-
import { CallServiceHandler, errorHandler, fnHandler } from './CallServiceHandler';
24+
import { CallServiceHandler } from './CallServiceHandler';
2525
import { loadRelayFn, loadVariablesService } from './RequestFlowBuilder';
2626
import { logParticle, Particle } from './particle';
2727
import log from 'loglevel';
28-
import { AirInterpreter, CallServiceResult, ParticleHandler, SecurityTetraplet } from '@fluencelabs/avm';
29-
30-
const makeDefaultClientHandler = (): CallServiceHandler => {
31-
const res = new CallServiceHandler();
32-
res.use(errorHandler);
33-
res.use(fnHandler('op', 'identity', (args, _) => args));
34-
return res;
35-
};
28+
import { AirInterpreter, ParticleHandler, SecurityTetraplet, CallServiceResult } from '@fluencelabs/avm';
29+
import makeDefaultClientHandler from './defaultClientHandler';
3630

3731
export class ClientImpl implements FluenceClient {
3832
readonly selfPeerIdFull: PeerId;

src/internal/defaultClientHandler.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { encode, decode } from 'bs58';
2+
import {
3+
CallServiceData,
4+
CallServiceHandler,
5+
CallServiceResult,
6+
CallServiceResultType,
7+
errorHandler,
8+
Middleware,
9+
} from './CallServiceHandler';
10+
11+
const makeDefaultClientHandler = (): CallServiceHandler => {
12+
const success = (resp: CallServiceResult, result: CallServiceResultType) => {
13+
resp.retCode = 0;
14+
resp.result = result;
15+
};
16+
const error = (resp: CallServiceResult, errorMsg: string) => {
17+
resp.retCode = 1;
18+
resp.result = errorMsg;
19+
};
20+
const mw: Middleware = (req: CallServiceData, resp: CallServiceResult, next: Function) => {
21+
if (req.serviceId === 'Op') {
22+
switch (req.fnName) {
23+
case 'noop':
24+
success(resp, {});
25+
return;
26+
27+
case 'array':
28+
success(resp, req.args);
29+
return;
30+
31+
case 'identity':
32+
if (req.args.length > 1) {
33+
error(resp, `identity accepts up to 1 arguments, received ${req.args.length} arguments`);
34+
} else {
35+
success(resp, req.args.length === 0 ? {} : req.args[0]);
36+
}
37+
return;
38+
39+
case 'concat':
40+
const incorrectArgIndices = req.args //
41+
.map((x, i) => [Array.isArray(x), i])
42+
.filter(([isArray, _]) => !isArray)
43+
.map(([_, index]) => index);
44+
45+
if (incorrectArgIndices.length > 0) {
46+
const str = incorrectArgIndices.join(', ');
47+
error(resp, `All arguments of 'concat' must be arrays: arguments ${str} are not`);
48+
} else {
49+
success(resp, [].concat.apply([], req.args));
50+
}
51+
return;
52+
53+
case 'string_to_b58':
54+
if (req.args.length !== 1) {
55+
error(resp, 'string_to_b58 accepts only one string argument');
56+
} else {
57+
success(resp, encode(new TextEncoder().encode(req.args[0])));
58+
}
59+
return;
60+
61+
case 'string_from_b58':
62+
if (req.args.length !== 1) {
63+
error(resp, 'string_from_b58 accepts only one string argument');
64+
} else {
65+
success(resp, new TextDecoder().decode(decode(req.args[0])));
66+
}
67+
return;
68+
69+
case 'bytes_to_b58':
70+
if (req.args.length !== 1 || !Array.isArray(req.args[0])) {
71+
error(resp, 'bytes_to_b58 accepts only single argument: array of numbers');
72+
} else {
73+
const argumentArray = req.args[0] as number[];
74+
success(resp, encode(new Uint8Array(argumentArray)));
75+
}
76+
return;
77+
78+
case 'bytes_from_b58':
79+
if (req.args.length !== 1) {
80+
error(resp, 'bytes_from_b58 accepts only one string argument');
81+
} else {
82+
success(resp, Array.from(decode(req.args[0])));
83+
}
84+
return;
85+
}
86+
}
87+
88+
next();
89+
};
90+
91+
const res = new CallServiceHandler();
92+
res.use(errorHandler);
93+
res.use(mw);
94+
return res;
95+
};
96+
97+
export default makeDefaultClientHandler;

0 commit comments

Comments
 (0)