From 4852099743338a2e3f692a4f8c28c592c7f16802 Mon Sep 17 00:00:00 2001 From: Jordan Last Date: Sat, 23 Sep 2023 14:17:31 -0500 Subject: [PATCH] service example tests passing --- .github/workflows/test.yml | 2 +- examples/service/src/index.ts | 69 +++---- examples/service/src/some_service.ts | 15 +- .../azle_generate_rearchitecture/src/main.rs | 5 +- .../generate_rust_canister.ts | 5 +- .../candid/reference/service.ts | 190 +++++++++++++++--- src/lib_functional/canister_methods/index.ts | 35 +++- src/lib_functional/canister_methods/query.ts | 12 +- src/lib_functional/canister_methods/update.ts | 11 +- src/lib_new/ic.ts | 2 +- .../visitors/encode_decode/decode_visitor.ts | 2 +- .../visitors/encode_decode/encode_visitor.ts | 2 +- src/lib_new/visitors/encode_decode/index.ts | 8 +- 13 files changed, 258 insertions(+), 100 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 840e71fb4f..b56d36d6af 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -66,7 +66,6 @@ # "examples/robust_imports", # "examples/run_time_errors", # "examples/rust_type_conversions", -# "examples/service", # "examples/stable_structures", # "examples/tuple_types", @@ -119,6 +118,7 @@ jobs: "examples/principal", "examples/query", "examples/randomness", + "examples/service", "examples/simple_erc20", "examples/simple_user_accounts", "examples/stable_memory", diff --git a/examples/service/src/index.ts b/examples/service/src/index.ts index 6b931e575e..c62cbb1770 100644 --- a/examples/service/src/index.ts +++ b/examples/service/src/index.ts @@ -1,42 +1,25 @@ -import { - candid, - ic, - Principal, - query, - Record, - Service, - text, - update, - Vec -} from 'azle'; - +import { ic, Principal, query, Record, Service, text, update, Vec } from 'azle'; import SomeService from './some_service'; -class Wrapper extends Record { - @candid(SomeService) - someService: SomeService; -} +const Wrapper = Record({ + someService: SomeService +}); -export default class extends Service { - @query([SomeService], SomeService) - serviceParam(someService: SomeService): SomeService { +export default Service({ + serviceParam: query([SomeService], SomeService, (someService) => { return someService; - } - - @query([], SomeService) - serviceReturnType(): SomeService { - return new SomeService( + }), + serviceReturnType: query([], SomeService, () => { + return SomeService( Principal.fromText( process.env.SOME_SERVICE_PRINCIPAL ?? ic.trap('process.env.SOME_SERVICE_PRINCIPAL is undefined') ) ); - } - - @update([], Wrapper) - serviceNestedReturnType(): Wrapper { + }), + serviceNestedReturnType: update([], Wrapper, () => { return { - someService: new SomeService( + someService: SomeService( Principal.fromText( process.env.SOME_SERVICE_PRINCIPAL ?? ic.trap( @@ -45,15 +28,19 @@ export default class extends Service { ) ) }; - } - - @update([Vec(SomeService)], Vec(SomeService)) - serviceList(someServices: Vec): Vec { - return someServices; - } - - @update([SomeService], text) - async serviceCrossCanisterCall(someService: SomeService): Promise { - return await ic.call(someService.update1); - } -} + }), + serviceList: update( + [Vec(SomeService)], + Vec(SomeService), + (someServices) => { + return someServices; + } + ), + serviceCrossCanisterCall: update( + [SomeService], + text, + async (someService) => { + return await ic.call(someService.update1); + } + ) +}); diff --git a/examples/service/src/some_service.ts b/examples/service/src/some_service.ts index f815dbc8a4..39a9a7e74c 100644 --- a/examples/service/src/some_service.ts +++ b/examples/service/src/some_service.ts @@ -1,13 +1,10 @@ import { bool, query, Service, text, update } from 'azle'; -export default class extends Service { - @query([], bool) - query1(): bool { +export default Service({ + query1: query([], bool, () => { return true; - } - - @update([], text) - update1(): text { + }), + update1: update([], text, () => { return 'SomeService update1'; - } -} + }) +}); diff --git a/src/compiler/typescript_to_rust/azle_generate_rearchitecture/src/main.rs b/src/compiler/typescript_to_rust/azle_generate_rearchitecture/src/main.rs index 44e0e24370..4c0b8ef8c4 100644 --- a/src/compiler/typescript_to_rust/azle_generate_rearchitecture/src/main.rs +++ b/src/compiler/typescript_to_rust/azle_generate_rearchitecture/src/main.rs @@ -280,7 +280,8 @@ fn main() -> Result<(), String> { let exports = global.get_property("exports").unwrap(); let canister_methods = exports.get_property("canisterMethods").unwrap(); let callbacks = canister_methods.get_property("callbacks").unwrap(); - let method = callbacks.get_property(function_name).unwrap(); + let methodCallbacks = callbacks.get_property(function_name).unwrap(); + let canisterMethodCallback = methodCallbacks.get_property("canisterCallback").unwrap(); let candid_args = if pass_arg_data { ic_cdk::api::call::arg_data_raw() } else { vec![] }; @@ -288,7 +289,7 @@ fn main() -> Result<(), String> { let candid_args_js_value_ref = to_qjs_value(&context, &candid_args_js_value).unwrap(); // TODO I am not sure what the first parameter to call is supposed to be - method.call(&method, &[candid_args_js_value_ref]).unwrap(); + canisterMethodCallback.call(&canisterMethodCallback, &[candid_args_js_value_ref]).unwrap(); context.execute_pending().unwrap(); }); diff --git a/src/compiler/typescript_to_rust/generate_rust_canister.ts b/src/compiler/typescript_to_rust/generate_rust_canister.ts index 813a5c8e06..1e26babf60 100644 --- a/src/compiler/typescript_to_rust/generate_rust_canister.ts +++ b/src/compiler/typescript_to_rust/generate_rust_canister.ts @@ -37,7 +37,10 @@ export function generateRustCanister( ts_root: join(process.cwd(), canisterConfig.ts), alias_tables: aliasTables, alias_lists: aliasLists, - canister_methods: canisterMethods + // TODO The spread is because canisterMethods is a function with properties + canister_methods: { + ...canisterMethods + } // TODO we should probably just grab the props out that we need }; const compilerInfoPath = join( diff --git a/src/lib_functional/candid/reference/service.ts b/src/lib_functional/candid/reference/service.ts index a5a1a72e67..186bf58a68 100644 --- a/src/lib_functional/candid/reference/service.ts +++ b/src/lib_functional/candid/reference/service.ts @@ -1,5 +1,10 @@ import { Principal, TypeMapping } from '../../'; -import { serviceCall } from '../../../lib_new'; +import { IDL, ServiceFunctionInfo, serviceCall } from '../../../lib_new'; +import { + Parent, + toParamIDLTypes, + toReturnIDLType +} from '../../../lib_new/utils'; import { CanisterMethodInfo } from '../../canister_methods'; type ServiceOptions = { @@ -17,32 +22,45 @@ type ServiceReturn = { : never; }; +type CallableObject = { + (principal: Principal): CallableObject; +} & ServiceReturn; + export function Service( - serviceOptions: T, - principal?: Principal -): ServiceReturn { + serviceOptions: T +): CallableObject { const callbacks = Object.entries(serviceOptions).reduce((acc, entry) => { const key = entry[0]; const value = entry[1]; - if (value.callback === undefined) { - return { - ...acc, - [key]: (...args: any[]) => { + // if (principal === undefined) { + // return { + // ...acc, + // [key]: (...args: any[]) => { + // return serviceCall( + // principal as any, + // key, + // value.paramsIdls, + // value.returnIdl + // )(...args); + // } + // }; + // } else { + return { + ...acc, + [key]: { + canisterCallback: value.callback, + crossCanisterCallback: (...args: any[]) => { return serviceCall( - principal as any, + this.principal as any, key, value.paramsIdls, value.returnIdl )(...args); } - }; - } else { - return { - ...acc, - [key]: value.callback - }; - } + } + }; + // } }, {}); const candidTypes = Object.values(serviceOptions).reduce( @@ -57,7 +75,7 @@ export function Service( const key = entry[0]; const value = entry[1]; - return value.type === 'query'; + return value.mode === 'query'; }) .map((entry) => { const key = entry[0]; @@ -73,7 +91,7 @@ export function Service( const key = entry[0]; const value = entry[1]; - return value.type === 'update'; + return value.mode === 'update'; }) .map((entry) => { const key = entry[0]; @@ -84,22 +102,134 @@ export function Service( }; }); - // TODO loop through each key and simply grab the candid off - // TODO grab the init/post_upgrade candid as well - return { - candid: `${ - candidTypes.length === 0 ? '' : candidTypes.join('\n') + '\n' - }service: () -> { + let returnFunction = (principal: Principal) => { + const callbacks = Object.entries(serviceOptions).reduce( + (acc, entry) => { + const key = entry[0]; + const value = entry[1]; + + // if (principal === undefined) { + // return { + // ...acc, + // [key]: (...args: any[]) => { + // return serviceCall( + // principal as any, + // key, + // value.paramsIdls, + // value.returnIdl + // )(...args); + // } + // }; + // } else { + return { + ...acc, + [key]: { + canisterCallback: value.callback, + crossCanisterCallback: (...args: any[]) => { + return serviceCall( + principal as any, + key, + value.paramsIdls, + value.returnIdl + )(...args); + } + } + }; + // } + }, + {} + ); + + return { + ...callbacks, + principal + }; + }; + + returnFunction.candid = `${ + candidTypes.length === 0 ? '' : candidTypes.join('\n') + '\n' + }service: () -> { ${Object.entries(serviceOptions) .map((entry) => { return `${entry[0]}: ${entry[1].candid}`; }) .join('\n ')} } -`, - queries, - updates, - callbacks, - ...callbacks // TODO then we can't use any names that could collide in this object - } as any; +`; + + returnFunction.queries = queries; + returnFunction.updates = updates; + returnFunction.callbacks = callbacks; + returnFunction.getIDL = (parents: Parent[]): IDL.ServiceClass => { + const serviceFunctionInfo: ServiceFunctionInfo = serviceOptions; + + const record = Object.entries(serviceFunctionInfo).reduce( + (accumulator, [methodName, functionInfo]) => { + const paramRealIdls = toParamIDLTypes(functionInfo.paramsIdls); + const returnRealIdl = toReturnIDLType(functionInfo.returnIdl); + + const annotations = + functionInfo.mode === 'update' ? [] : ['query']; + + return { + ...accumulator, + [methodName]: IDL.Func( + paramRealIdls, + returnRealIdl, + annotations + ) + }; + }, + {} as Record + ); + + return IDL.Service(record); + }; + + return returnFunction; + + // TODO loop through each key and simply grab the candid off + // TODO grab the init/post_upgrade candid as well + // return { + // candid: `${ + // candidTypes.length === 0 ? '' : candidTypes.join('\n') + '\n' + // }service: () -> { + // ${Object.entries(serviceOptions) + // .map((entry) => { + // return `${entry[0]}: ${entry[1].candid}`; + // }) + // .join('\n ')} + // } + // `, + // queries, + // updates, + // callbacks, + // principal, + // ...callbacks, // TODO then we can't use any names that could collide in this object + // getIDL(parents: Parent[]): IDL.ServiceClass { + // const serviceFunctionInfo: ServiceFunctionInfo = serviceOptions; + + // const record = Object.entries(serviceFunctionInfo).reduce( + // (accumulator, [methodName, functionInfo]) => { + // const paramRealIdls = toParamIDLTypes(functionInfo.paramsIdls); + // const returnRealIdl = toReturnIDLType(functionInfo.returnIdl); + + // const annotations = + // functionInfo.mode === 'update' ? [] : ['query']; + + // return { + // ...accumulator, + // [methodName]: IDL.Func( + // paramRealIdls, + // returnRealIdl, + // annotations + // ) + // }; + // }, + // {} as Record + // ); + + // return IDL.Service(record); + // } + // } as any; } diff --git a/src/lib_functional/canister_methods/index.ts b/src/lib_functional/canister_methods/index.ts index 1b62b957a9..9dfa1bbc55 100644 --- a/src/lib_functional/canister_methods/index.ts +++ b/src/lib_functional/canister_methods/index.ts @@ -1,12 +1,16 @@ import { IDL } from '../../lib_new/index'; import { ic } from '../../lib_new/ic'; import { TypeMapping } from '..'; +import { + DecodeVisitor, + EncodeVisitor +} from '../../lib_new/visitors/encode_decode'; export * from './query'; export * from './update'; export type CanisterMethodInfo, K> = { - type: 'query' | 'update'; + mode: 'query' | 'update'; callback?: (...args: any) => any; candid: string; candidTypes: string[]; @@ -22,11 +26,20 @@ export function executeMethod( paramCandid: any, returnCandid: any, args: any[], - callback: any + callback: any, + paramsIdls: any[], + returnIdl: any ) { const decoded = IDL.decode(paramCandid[0] as any, args[0]); - const result = callback(...decoded); + const myDecodedObject = paramCandid[0].map((idl, index) => { + return idl.accept(new DecodeVisitor(), { + js_class: paramsIdls[index], + js_data: decoded[index] + }); + }); + + const result = callback(...myDecodedObject); if ( result !== undefined && @@ -40,7 +53,13 @@ export function executeMethod( console.log(`final instructions: ${ic.instructionCounter()}`); // if (!manual) { - const encodeReadyResult = result === undefined ? [] : [result]; + // const encodeReadyResult = result === undefined ? [] : [result]; + const encodeReadyResult = returnCandid[0].map((idl) => { + return idl.accept(new EncodeVisitor(), { + js_class: returnIdl, + js_data: result + }); + }); const encoded = IDL.encode( returnCandid[0] as any, encodeReadyResult @@ -52,7 +71,13 @@ export function executeMethod( ic.trap(error.toString()); }); } else { - const encodeReadyResult = result === undefined ? [] : [result]; + // const encodeReadyResult = result === undefined ? [] : [result]; + const encodeReadyResult = returnCandid[0].map((idl) => { + return idl.accept(new EncodeVisitor(), { + js_class: returnIdl, + js_data: result + }); + }); // if (!manual) { const encoded = IDL.encode(returnCandid[0] as any, encodeReadyResult); diff --git a/src/lib_functional/canister_methods/query.ts b/src/lib_functional/canister_methods/query.ts index aa2bc11b52..25565892d7 100644 --- a/src/lib_functional/canister_methods/query.ts +++ b/src/lib_functional/canister_methods/query.ts @@ -23,15 +23,23 @@ export function query< paramCandid[2] ); + // TODO maybe the cross canister callback should be made here? const finalCallback = callback === undefined ? undefined : (...args: any[]) => { - executeMethod(paramCandid, returnCandid, args, callback); + executeMethod( + paramCandid, + returnCandid, + args, + callback, + paramsIdls as any, + returnIdl + ); }; return { - type: 'query', + mode: 'query', callback: finalCallback, candid: `(${paramCandid[1].join(', ')}) -> (${returnCandid[1]}) query;`, candidTypes: newTypesToStingArr(returnCandid[2]), diff --git a/src/lib_functional/canister_methods/update.ts b/src/lib_functional/canister_methods/update.ts index 9e109ec5c9..42f110ba0a 100644 --- a/src/lib_functional/canister_methods/update.ts +++ b/src/lib_functional/canister_methods/update.ts @@ -27,11 +27,18 @@ export function update< callback === undefined ? undefined : (...args: any[]) => { - executeMethod(paramCandid, returnCandid, args, callback); + executeMethod( + paramCandid, + returnCandid, + args, + callback, + paramsIdls as any, + returnIdl + ); }; return { - type: 'update', + mode: 'update', callback: finalCallback, candid: `(${paramCandid[1].join(', ')}) -> (${returnCandid[1]});`, candidTypes: newTypesToStingArr(returnCandid[2]), diff --git a/src/lib_new/ic.ts b/src/lib_new/ic.ts index 86966bc456..a585e50695 100644 --- a/src/lib_new/ic.ts +++ b/src/lib_new/ic.ts @@ -455,7 +455,7 @@ export const ic: Ic = globalThis._azleIc return new Uint8Array(globalThis._azleIc.argDataRaw()); }, call: (method, config) => { - return method( + return method.crossCanisterCallback( '_AZLE_CROSS_CANISTER_CALL', false, ic.callRaw, diff --git a/src/lib_new/visitors/encode_decode/decode_visitor.ts b/src/lib_new/visitors/encode_decode/decode_visitor.ts index 852bab5662..2c91d8342f 100644 --- a/src/lib_new/visitors/encode_decode/decode_visitor.ts +++ b/src/lib_new/visitors/encode_decode/decode_visitor.ts @@ -18,7 +18,7 @@ import { export class DecodeVisitor extends IDL.Visitor { visitService(t: IDL.ServiceClass, data: VisitorData): VisitorResult { - return new data.js_class(data.js_data); + return data.js_class(data.js_data); } visitFunc(t: IDL.FuncClass, data: VisitorData): VisitorResult { return new data.js_class(data.js_data[0], data.js_data[1]); diff --git a/src/lib_new/visitors/encode_decode/encode_visitor.ts b/src/lib_new/visitors/encode_decode/encode_visitor.ts index ba12ab7dcd..752c1063b8 100644 --- a/src/lib_new/visitors/encode_decode/encode_visitor.ts +++ b/src/lib_new/visitors/encode_decode/encode_visitor.ts @@ -19,7 +19,7 @@ import { export class EncodeVisitor extends IDL.Visitor { visitService(t: IDL.ServiceClass, data: VisitorData): VisitorResult { - return data.js_data.canisterId; + return data.js_data.principal; } visitFunc(t: IDL.FuncClass, data: VisitorData): VisitorResult { return [data.js_data.principal, data.js_data.name]; diff --git a/src/lib_new/visitors/encode_decode/index.ts b/src/lib_new/visitors/encode_decode/index.ts index 78d84715fd..c894691093 100644 --- a/src/lib_new/visitors/encode_decode/index.ts +++ b/src/lib_new/visitors/encode_decode/index.ts @@ -59,13 +59,12 @@ export function visitVec( if (ty instanceof IDL.PrimitiveType) { return data.js_data; } - const vec_elems = data.js_data.map((array_elem: any) => { + return 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( @@ -75,7 +74,7 @@ export function visitRecord( ): VisitorResult { const candidFields = fields.reduce((acc, [memberName, memberIdl]) => { const fieldData = data.js_data[memberName]; - const fieldClass = data.js_class._azleCandidMap[memberName]; + const fieldClass = data.js_class[memberName]; return { ...acc, @@ -85,7 +84,8 @@ export function visitRecord( }) }; }, {}); - return data.js_class.create(candidFields); + + return candidFields; } export function visitVariant(