Skip to content

Commit

Permalink
reduce code duplication, add support for results
Browse files Browse the repository at this point in the history
  • Loading branch information
bdemann committed Sep 22, 2023
1 parent 2aba699 commit 299345d
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 185 deletions.
2 changes: 1 addition & 1 deletion src/lib_new/method_decorators.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ic } from './ic';
import { GuardResult, IDL } from './index';
import { GuardResult, IDL, Record } from './index';

import {
CandidClass,
Expand Down
131 changes: 24 additions & 107 deletions src/lib_new/visitors/decode_visitor.ts
Original file line number Diff line number Diff line change
@@ -1,157 +1,74 @@
import { IDL } from '@dfinity/candid';
import {
VisitorData,
VisitorResult,
visitOpt,
visitRec,
visitRecord,
visitTuple,
visitVariant,
visitVec
} from './encode_decode';

type VisitorData = { js_data: any; js_class: any };
type VisitorResult = any;

// We need the data to be the information we need make the class
// We need the return type to be the fully formed java classes.
// We need the type so that we can use the right constructor

/*
Lets take a look at how that would look for a Service
class MyService extends Service {
@update
myUpdate();
@query()
myQuery();
}
We would expect to receive a principal because that is all that we can get from the decoded values
So we don't actually care about the funcs. Any sub services will have to have the information on
how to make it's self because we will not and cannot have tht information. but any caniter id should
be known by the canister we are expanding
So we are going to have MyService the class be in the info. We will have the principal
be in the info and our return type will be an instance of MyService
data: {js_data: any, js_class: any}
*/

// This Visitor just needs to take a principal and turn it into a service
/**
* When we decode a Service we are given a principal. We need to use that
* principal to create a Service class. Same things applies to Funcs except
* that it has a principal and a name.
*/

export class DecodeVisitor extends IDL.Visitor<VisitorData, VisitorResult> {
visitService(t: IDL.ServiceClass, data: VisitorData): VisitorResult {
return new data.js_class(data.js_data);
}
visitFunc(t: IDL.FuncClass, data: VisitorData): VisitorResult {
return new data.js_class(data.js_data.principal, data.js_data.name);
}
visitPrimitive<T>(
t: IDL.PrimitiveType<T>,
data: VisitorData
): VisitorResult {
return data.js_data;
}
/**
* For visiting tuples we will want to visit each component and then spread the
* results of that into a tuple. In order to visit each one we need the Tuple
* data to look like this
* [value1, value2, value3]
* and the class is going to need to look like
* {_azleCandidComponents: [idlable1, idlable2, idlable3]}
* @param t
* @param components
* @param data
* @returns
*/
visitTuple<T extends any[]>(
t: IDL.TupleClass<T>,
components: IDL.Type<any>[],
data: VisitorData
): VisitorResult {
const fields = components.map((value, index) =>
value.accept(this, {
js_data: data.js_data[index],
js_class: data.js_class._azleTypes[index]
})
);
return [...fields];
return visitTuple(this, components, data);
}
visitOpt<T>(
t: IDL.OptClass<T>,
ty: IDL.Type<T>,
data: VisitorData
): VisitorResult {
if (data.js_data.length === 0) {
return data.js_data;
}
const candid = ty.accept(this, {
js_data: data.js_data[0],
js_class: data.js_class._azleType
});
return [candid];
return visitOpt(this, ty, data);
}
visitVec<T>(
t: IDL.VecClass<T>,
ty: IDL.Type<T>,
data: VisitorData
): VisitorResult {
if (ty instanceof IDL.PrimitiveType) {
return data.js_data;
}
const vec_elems = data.js_data.map((array_elem: any) => {
return ty.accept(this, {
js_data: array_elem,
js_class: data.js_class._azleType
});
});
return [...vec_elems];
}
/**
* Lets see the funcs are going to similar to the services....
* We have a function name and a principal id
* @param t
* @param data
* @returns
*/
visitFunc(t: IDL.FuncClass, data: VisitorData): VisitorResult {
return new data.js_class(data.js_data.principal, data.js_data.name);
return visitVec(this, ty, data);
}
visitRec<T>(
t: IDL.RecClass<T>,
ty: IDL.ConstructType<T>,
data: VisitorData
): VisitorResult {
// TODO I imagine that this will be the spot of much torment when we get
// to doing actual recursive types, maybe
return ty.accept(this, data);
return visitRec(this, ty, data);
}
visitRecord(
t: IDL.RecordClass,
fields: [string, IDL.Type<any>][],
data: VisitorData
): VisitorResult {
const candidFields = fields.reduce((acc, [memberName, memberIdl]) => {
const fieldData = data.js_data[memberName];
const fieldClass = data.js_class._azleCandidMap[memberName];

return {
...acc,
[memberName]: memberIdl.accept(this, {
js_data: fieldData,
js_class: fieldClass
})
};
}, {});
return data.js_class.create(candidFields);
return visitRecord(this, fields, data);
}
visitVariant(
t: IDL.VariantClass,
fields: [string, IDL.Type<any>][],
data: VisitorData
): VisitorResult {
const candidFields = fields.reduce((acc, [memberName, memberIdl]) => {
const fieldData = data.js_data[memberName];
const fieldClass = data.js_class._azleCandidMap[memberName];
if (fieldData === undefined) {
// If the field data is undefined then it is not the variant that was used
return acc;
}
return {
...acc,
[memberName]: memberIdl.accept(this, {
js_class: fieldClass,
js_data: fieldData
})
};
}, {});
return data.js_class.create(candidFields);
return visitVariant(this, fields, data);
}
}
145 changes: 145 additions & 0 deletions src/lib_new/visitors/encode_decode/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { IDL } from '@dfinity/candid';
import { DecodeVisitor } from '../decode_visitor';
import { EncodeVisitor } from '../encode_visitor';
import { AzleResult, Result } from '../../result';

/*
* The VisitorData gives us js_data which is the data that is about to be
* encoded or was just decoded. js_class is the CandidClass (IDLable) class that
* can be used to create the class.
*/
export type VisitorData = { js_data: any; js_class: any };
/**
* The VisitorResult is the transformed version of js_data that is ready to
* be consumed by the js or ready to be encoded.
*/
export type VisitorResult = any;

/*
* For most of the visitors the only thing that needs to happen is to visit each
* of the sub nodes. That is the same for both encoding and decoding. That logic
* is extracted into these helper methods.
*/

export function visitTuple(
visitor: DecodeVisitor | EncodeVisitor,
components: IDL.Type<any>[],
data: VisitorData
): VisitorResult {
const fields = components.map((value, index) =>
value.accept(visitor, {
js_data: data.js_data[index],
js_class: data.js_class._azleTypes[index]
})
);
return [...fields];
}

export function visitOpt(
visitor: DecodeVisitor | EncodeVisitor,
ty: IDL.Type<any>,
data: VisitorData
): VisitorResult {
if (data.js_data.length === 0) {
return data.js_data;
}
const candid = ty.accept(visitor, {
js_data: data.js_data[0],
js_class: data.js_class._azleType
});
return [candid];
}

export function visitVec(
visitor: DecodeVisitor | EncodeVisitor,
ty: IDL.Type<any>,
data: VisitorData
): VisitorResult {
if (ty instanceof IDL.PrimitiveType) {
return data.js_data;
}
const vec_elems = data.js_data.map((array_elem: any) => {
return ty.accept(visitor, {
js_data: array_elem,
js_class: data.js_class._azleType
});
});
return [...vec_elems];
}

export function visitRecord(
visitor: DecodeVisitor | EncodeVisitor,
fields: [string, IDL.Type<any>][],
data: VisitorData
): VisitorResult {
const candidFields = fields.reduce((acc, [memberName, memberIdl]) => {
const fieldData = data.js_data[memberName];
const fieldClass = data.js_class._azleCandidMap[memberName];

return {
...acc,
[memberName]: memberIdl.accept(visitor, {
js_data: fieldData,
js_class: fieldClass
})
};
}, {});
return data.js_class.create(candidFields);
}

export function visitVariant(
visitor: DecodeVisitor | EncodeVisitor,
fields: [string, IDL.Type<any>][],
data: VisitorData
): VisitorResult {
if (data.js_class instanceof AzleResult) {
if ('Ok' in data.js_data) {
const okField = fields[0];
const okData = data.js_data['Ok'];
const okClass = data.js_class._azleOk;
return Result.Ok(
okField[1].accept(visitor, {
js_data: okData,
js_class: okClass
})
);
}
if ('Err' in data.js_data) {
const errField = fields[0];
const errData = data.js_data['Err'];
const errClass = data.js_class._azleErr;
return Result.Err(
errField[1].accept(visitor, {
js_data: errData,
js_class: errClass
})
);
}
}
const candidFields = fields.reduce((acc, [memberName, memberIdl]) => {
const fieldData = data.js_data[memberName];
const fieldClass = data.js_class._azleCandidMap[memberName];
if (fieldData === undefined) {
// If the field data is undefined then it is not the variant that was used
return acc;
}
return {
...acc,
[memberName]: memberIdl.accept(visitor, {
js_class: fieldClass,
js_data: fieldData
})
};
}, {});
return data.js_class.create(candidFields);
}

export function visitRec<T>(
visitor: DecodeVisitor | EncodeVisitor,
ty: IDL.ConstructType<T>,
data: VisitorData
): VisitorResult {
// TODO I imagine that this will be the spot of much torment when we get
// to doing actual recursive types, maybe
return ty.accept(visitor, data);
}
Loading

0 comments on commit 299345d

Please sign in to comment.