Skip to content

Commit

Permalink
feat: Retain type information for functions and services on decoding (#…
Browse files Browse the repository at this point in the history
…563)

* Retain complete function and service types on decode
  • Loading branch information
frederikrothenberger committed Apr 20, 2022
1 parent 9cb4428 commit 2d15437
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 18 deletions.
4 changes: 2 additions & 2 deletions packages/candid/src/idl.test.ts
Expand Up @@ -534,7 +534,7 @@ test('decode unknown service', () => {
fromHexString('4449444c026a0171017d00690103666f6f0001010103caffee'),
)[0] as any;
expect(value).toEqual(Principal.fromText('w7x7r-cok77-xa'));
expect(value.type()).toEqual(IDL.Service({}));
expect(value.type()).toEqual(IDL.Service({ foo: IDL.Func([IDL.Text], [IDL.Nat], []) }));
});

test('decode unknown func', () => {
Expand All @@ -543,7 +543,7 @@ test('decode unknown func', () => {
fromHexString('4449444c016a0171017d01010100010103caffee03666f6f'),
)[0] as any;
expect(value).toEqual([Principal.fromText('w7x7r-cok77-xa'), 'foo']);
expect(value.type()).toEqual(IDL.Func([], [], []));
expect(value.type()).toEqual(IDL.Func([IDL.Text], [IDL.Nat], ['query']));
});

test('decode / encode unknown mutual recursive lists', () => {
Expand Down
76 changes: 60 additions & 16 deletions packages/candid/src/idl.ts
@@ -1,17 +1,20 @@
// tslint:disable:max-classes-per-file
import { Principal as PrincipalId } from '@dfinity/principal';
import { JsonValue } from './types';
import { concat, PipeArrayBuffer as Pipe, toHexString } from './utils/buffer';
import { concat, PipeArrayBuffer as Pipe } from './utils/buffer';
import { idlLabelToId } from './utils/hash';
import {
lebDecode,
lebEncode,
readIntLE,
readUIntLE,
safeRead,
safeReadUint8,
slebDecode,
slebEncode,
writeIntLE,
writeUIntLE,
} from './utils/leb128';
import { readIntLE, readUIntLE, writeIntLE, writeUIntLE } from './utils/leb128';

// tslint:disable:max-line-length
/**
Expand Down Expand Up @@ -1327,7 +1330,7 @@ export class FuncClass extends ConstructType<[PrincipalId, string]> {
} else if (ann === 'oneway') {
return new Uint8Array([2]);
} else {
throw new Error('Illeagal function annotation');
throw new Error('Illegal function annotation');
}
}
}
Expand Down Expand Up @@ -1471,25 +1474,46 @@ export function decode(retTypes: Type[], bytes: ArrayBuffer): JsonValue[] {
break;
}
case IDLTypeIds.Func: {
for (let k = 0; k < 2; k++) {
let funcLength = Number(lebDecode(pipe));
while (funcLength--) {
slebDecode(pipe);
const args = [];
let argLength = Number(lebDecode(pipe));
while (argLength--) {
args.push(Number(slebDecode(pipe)));
}
const returnValues = [];
let returnValuesLength = Number(lebDecode(pipe));
while (returnValuesLength--) {
returnValues.push(Number(slebDecode(pipe)));
}
const annotations = [];
let annotationLength = Number(lebDecode(pipe));
while (annotationLength--) {
const annotation = Number(lebDecode(pipe));
switch (annotation) {
case 1: {
annotations.push('query');
break;
}
case 2: {
annotations.push('oneway');
break;
}
default:
throw new Error('unknown annotation');
}
}
const annLen = Number(lebDecode(pipe));
safeRead(pipe, annLen);
typeTable.push([ty, undefined]);
typeTable.push([ty, [args, returnValues, annotations]]);
break;
}
case IDLTypeIds.Service: {
let servLength = Number(lebDecode(pipe));
const methods = [];
while (servLength--) {
const l = Number(lebDecode(pipe));
safeRead(pipe, l);
slebDecode(pipe);
const nameLength = Number(lebDecode(pipe));
const funcName = new TextDecoder().decode(safeRead(pipe, nameLength));
const funcType = slebDecode(pipe);
methods.push([funcName, funcType]);
}
typeTable.push([ty, undefined]);
typeTable.push([ty, methods]);
break;
}
default:
Expand Down Expand Up @@ -1594,15 +1618,35 @@ export function decode(retTypes: Type[], bytes: ArrayBuffer): JsonValue[] {
return Variant(fields);
}
case IDLTypeIds.Func: {
return Func([], [], []);
const [args, returnValues, annotations] = entry[1];
return Func(
args.map((t: number) => getType(t)),
returnValues.map((t: number) => getType(t)),
annotations,
);
}
case IDLTypeIds.Service: {
return Service({});
const rec: Record<string, FuncClass> = {};
const methods = entry[1] as [[string, number]];
for (const [name, typeRef] of methods) {
let type: Type | undefined = getType(typeRef);

if (type instanceof RecClass) {
// unpack reference type
type = type.getType();
}
if (!(type instanceof FuncClass)) {
throw new Error('Illegal service definition: services can only contain functions');
}
rec[name] = type;
}
return Service(rec);
}
default:
throw new Error('Illegal op_code: ' + entry[0]);
}
}

rawTable.forEach((entry, i) => {
const t = buildType(entry);
table[i].fill(t);
Expand Down

0 comments on commit 2d15437

Please sign in to comment.