-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
reduce code duplication, add support for results
- Loading branch information
Showing
4 changed files
with
193 additions
and
185 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
Oops, something went wrong.