From d503afa9324f4fb7334c2a870e7b17efe4d7cf56 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Wed, 27 Mar 2024 14:10:25 -0400 Subject: [PATCH 01/13] Generator changes for improved encoding & decoding --- typescript_templates/model.vm | 172 +++++++++++++++++++++++++++------- 1 file changed, 140 insertions(+), 32 deletions(-) diff --git a/typescript_templates/model.vm b/typescript_templates/model.vm index 4c761ce..6f60488 100644 --- a/typescript_templates/model.vm +++ b/typescript_templates/model.vm @@ -9,8 +9,8 @@ $str.kebabToCamel($param.propertyName.replaceAll("_", "-"))## #set( $type_override_variable = "${d}${e}propFile.type_override_${className}_#paramName(${param})" ) #set( $type_override = "#evaluate($type_override_variable)" ) #if ( $param.algorandFormat == "SignedTransaction" ) -EncodedSignedTransaction## -#elseif ( $param.algorandFormat == "Address" ) ## No special handling for Address in go SDK +SignedTransaction## +#elseif ( $param.algorandFormat == "Address" )## No special handling for Address in go SDK string## #elseif ( $param.algorandFormat == "BlockHeader" ) BlockHeader## @@ -21,7 +21,7 @@ BlockHeader## bigint## #end #elseif ( $param.type == "object" ) -Record## +Map## #elseif ( $type_override == "number" || ( ( $param.type == "integer" || $param.arrayType == "integer" ) && $type_override.length() == 0 ) ) #if ( $isArgType ) (number | bigint)## @@ -46,7 +46,7 @@ Uint8Array## string## #elseif( $param.arrayType ) ${param.arrayType}## -#elseif( $param.refType ) ## This is second because the old code avoids typedef of references +#elseif( $param.refType )## This is second because the old code avoids typedef of references ${param.refType}## #else UNHANDLED TYPE @@ -131,26 +131,96 @@ UNHANDLED CONSTRUCTOR TYPE CONVERSION $unknown.type ## force a template failure with an unknown type #end #end +## Create an expression which checks if a field should be included in msgpackPrepare +#macro ( shouldMsgpackPrepareOptionalField $prop ) +#set( $value = "this.#paramName($prop)" ) +#if ( $prop.arrayType ) +$value && ${value}.length## +#else +$value## +#end +#end +## Create an expression to assign a field in the msgpackPrepare function +#macro ( msgpackPrepareField $prop ) +#set( $value = "this.#paramName($prop)" ) +#if ( $prop.algorandFormat == "BlockHeader" ) +blockHeaderMsgpackPrepare($value)## +#elseif ( "#isClassType($prop)" == "false" && $prop.algorandFormat != "SignedTransaction" ) +$value## +#elseif ( $prop.arrayType ) +${value}.map(v => v.msgpackPrepare())## +#else +${value}.msgpackPrepare()## +#end +#end +## Create an expression to assign a field in the jsonPrepare function +#macro ( jsonPrepareField $className $prop ) +#set( $value = "this.#paramName($prop)" ) +#set( $sdkType = "#toSdkType($className, $prop, false)" ) +#if ( $prop.algorandFormat == "BlockHeader" ) +$value## +#elseif ( "#isClassType($prop)" == "false" && $prop.algorandFormat != "SignedTransaction" && $sdkType != "Uint8Array" && $sdkType != "Uint8Array[]") +$value## +#elseif ( $prop.arrayType ) +#if ( $sdkType == "Uint8Array[]" ) +${value}.map(bytesToBase64)## +#else +${value}.map(v => v.jsonPrepare())## +#end +#elseif ( $sdkType == "Uint8Array" ) +bytesToBase64($value)## +#else +${value}.jsonPrepare()## +#end +#end ## Create an expression to assign a field in the from_obj_for_encoding function -#macro ( fromObjForEncodingAssignType $value $prop ) -#if ( "#isClassType($prop)" == "false" ) +#macro ( fromObjForEncodingAssignType $value $prop $className ) +#if ( $prop.algorandFormat == "BlockHeader" ) +blockHeaderFromDecodedMsgpack($value)## TODO: is required? +#elseif ( "#isClassType($prop)" == "false" ) +#if ($prop.required) +$value ?? #defaultValueForType($className, $prop)## +#else $value## +#end #elseif ( $prop.arrayType ) -#set ( $assignment = "${value}.map(${prop.arrayType}.from_obj_for_encoding)" ) +#set ( $mapping = ".map(${prop.arrayType}.fromDecodedMsgpack)" ) #if ($prop.required) -$assignment## +($value ?? [])$mapping## #else -typeof $value !== 'undefined' ? $assignment : undefined## +typeof $value !== 'undefined' ? $value$mapping : undefined## #end #else -#set ( $assignment = "${prop.refType}.from_obj_for_encoding($value)" ) +#set ( $assignment = "${prop.refType}.fromDecodedMsgpack" ) #if ($prop.required) -$assignment## +$assignment($value ?? {})## #else -typeof $value !== 'undefined' ? $assignment : undefined## +typeof $value !== 'undefined' ? $assignment($value) : undefined## #end #end #end +#macro ( defaultValueForType $className $prop ) +#set( $sdkType = "#toSdkType($className, $prop, false)" ) +#if ( $prop.arrayType ) +[]## +#elseif ( $sdkType == "string" ) +""## +#elseif ( $sdkType == "number" || $sdkType == "bigint" )## Any bigint type will accept numbers anyway +0## +#elseif ( $sdkType == "boolean" ) +false## +#elseif ( $sdkType == "Uint8Array" ) +new Uint8Array()## +#elseif ( $sdkType == "BlockHeader" || $sdkType == "Map" || $sdkType == "SignedTransaction" ) +{}## +#else +UNHANDLED DEFAULT TYPE +- class: $className +- property: $prop +- sdkType: $sdkType +$unknown.type ## force a template failure with an unknown type +#end +#end #macro ( questionMarkIfOptional $param ) #if ( ! $param.required ) ?## @@ -166,12 +236,13 @@ typeof $value !== 'undefined' ? $assignment : undefined## /* eslint-disable no-use-before-define */ import { ensureBigInt, ensureSafeInteger } from '../../../../utils/utils.js'; -import { base64ToBytes } from '../../../../encoding/binarydata.js'; +import { MsgpackEncodable, MsgpackEncodingData, JSONEncodable, JSONEncodingData } from '../../../../encoding/encoding.js'; +import { base64ToBytes, bytesToBase64 } from '../../../../encoding/binarydata.js'; #if ( $propFile.indexer == "false" ) -import BlockHeader from '../../../../types/blockHeader.js'; -import { EncodedSignedTransaction } from '../../../../types/transactions/encoded.js'; +import BlockHeader, { blockHeaderMsgpackPrepare, blockHeaderFromDecodedMsgpack } from '../../../../types/blockHeader.js'; +import { SignedTransaction } from '../../../../signedTransaction.js'; #end -import BaseModel from '../../basemodel.js'; +// import BaseModel from '../../basemodel.js'; #foreach( $modelEntry in $models.entrySet() ) #set( $def = $modelEntry.key ) @@ -190,7 +261,7 @@ import BaseModel from '../../basemodel.js'; * $str.formatDoc($def.doc, " * ") */ #end -export class $def.name extends BaseModel { +export class $def.name implements MsgpackEncodable, JSONEncodable { #foreach( $prop in $props ) #if ( !$prop.doc.isEmpty() ) /** @@ -228,48 +299,85 @@ export class $def.name extends BaseModel { #else ) { #end - super(); #foreach( $prop in $props ) #set( $var = "#paramName($prop)" ) this.$var = #constructorAssignType($def.name, $prop); #end + } - this.attribute_map = { + msgpackPrepare(): Map { + const data = new Map([ #foreach( $prop in $props ) - #paramName($prop): '$prop.propertyName', +#if ( $prop.required ) + ['$prop.propertyName', #msgpackPrepareField($prop)], #end +#end + ]); +#foreach( $prop in $props ) +#if ( ! $prop.required ) + if (#shouldMsgpackPrepareOptionalField($prop)) { + data.set('$prop.propertyName', #msgpackPrepareField($prop)); } +#end +#end + return data; } - // eslint-disable-next-line camelcase - static from_obj_for_encoding(data: Record): $def.name { + jsonPrepare(): Record { + const obj: Record = {}; + /* eslint-disable dot-notation */ -#set ( $d = "$" )## Create a variable in order to insert a $ into the code #foreach( $prop in $props ) -#if ($prop.required) -#if ($prop.arrayType) - if (!Array.isArray(data['$prop.propertyName'])) - throw new Error(`Response is missing required array field '${prop.propertyName}': ${d}{data}`); +#if ( $prop.required ) + obj['$prop.propertyName'] = #jsonPrepareField($def.name, $prop); #else - if (typeof data['$prop.propertyName'] === 'undefined') - throw new Error(`Response is missing required field '${prop.propertyName}': ${d}{data}`); + if (#shouldMsgpackPrepareOptionalField($prop)) { + obj['$prop.propertyName'] = #jsonPrepareField($def.name, $prop); + } #end #end + /* eslint-enable dot-notation */ + + return obj; + } + +// // eslint-disable-next-line camelcase +// static from_obj_for_encoding(data: Record): $def.name { +// /* eslint-disable dot-notation */ +#if ($use_object_params) +// return new ${def.name}({ +#foreach( $prop in $props ) +// #paramName($prop): #fromObjForEncodingAssignType("data['$prop.propertyName']", $prop, $def.name), +#end +// }); +#else +// return new ${def.name}( +#foreach( $prop in $props ) +// #fromObjForEncodingAssignType("data['$prop.propertyName']", $prop, $def.name), +#end +// ); #end +// /* eslint-enable dot-notation */ +// } + + static fromDecodedMsgpack(data: unknown): $def.name { +#set ( $d = "$" )## Create a variable in order to insert a $ into the code + if (!(data instanceof Map)) { + throw new Error(`Invalid decoded logic sig account: ${d}{data}`); + } #if ($use_object_params) return new ${def.name}({ #foreach( $prop in $props ) - #paramName($prop): #fromObjForEncodingAssignType("data['$prop.propertyName']", $prop), + #paramName($prop): #fromObjForEncodingAssignType("data.get('$prop.propertyName')", $prop, $def.name), #end }); #else return new ${def.name}( #foreach( $prop in $props ) - #fromObjForEncodingAssignType("data['$prop.propertyName']", $prop), + #fromObjForEncodingAssignType("data.get('$prop.propertyName')", $prop, $def.name), #end ); #end - /* eslint-enable dot-notation */ } } From b6ce561c140a9f7559b91053171e134f096373f6 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Thu, 28 Mar 2024 17:31:32 -0400 Subject: [PATCH 02/13] Implement fromDecodedJSON, fix bugs --- typescript_templates/model.vm | 80 +++++++++++++++++++++++++++-------- 1 file changed, 63 insertions(+), 17 deletions(-) diff --git a/typescript_templates/model.vm b/typescript_templates/model.vm index 6f60488..88f6c9b 100644 --- a/typescript_templates/model.vm +++ b/typescript_templates/model.vm @@ -173,25 +173,67 @@ bytesToBase64($value)## ${value}.jsonPrepare()## #end #end -## Create an expression to assign a field in the from_obj_for_encoding function -#macro ( fromObjForEncodingAssignType $value $prop $className ) +## Create an expression to assign a field in the fromDecodedMsgpack function +#macro ( fromDecodedMsgpackAssignType $value $prop $className ) #if ( $prop.algorandFormat == "BlockHeader" ) -blockHeaderFromDecodedMsgpack($value)## TODO: is required? -#elseif ( "#isClassType($prop)" == "false" ) +blockHeaderFromDecodedMsgpack($value)## +#elseif ( "#isClassType($prop)" == "false" && $prop.algorandFormat != "SignedTransaction" ) #if ($prop.required) $value ?? #defaultValueForType($className, $prop)## #else $value## #end #elseif ( $prop.arrayType ) +#if ( $prop.algorandFormat == "SignedTransaction" ) +#set ( $mapping = ".map(SignedTransaction.fromDecodedMsgpack)" ) +#else #set ( $mapping = ".map(${prop.arrayType}.fromDecodedMsgpack)" ) +#end #if ($prop.required) ($value ?? [])$mapping## #else typeof $value !== 'undefined' ? $value$mapping : undefined## #end #else +#if ( $prop.algorandFormat == "SignedTransaction" ) +#set ( $assignment = "SignedTransaction.fromDecodedMsgpack" ) +#else #set ( $assignment = "${prop.refType}.fromDecodedMsgpack" ) +#end +#if ($prop.required) +$assignment($value ?? {})## +#else +typeof $value !== 'undefined' ? $assignment($value) : undefined## +#end +#end +#end +## Create an expression to assign a field in the fromDecodedJSON function +#macro ( fromDecodedJSONAssignType $value $prop $className ) +#if ( $prop.algorandFormat == "BlockHeader" ) +$value as BlockHeader## +#elseif ( "#isClassType($prop)" == "false" && $prop.algorandFormat != "SignedTransaction" ) +#if ($prop.required) +$value ?? #defaultValueForType($className, $prop)## +#else +$value## +#end +#elseif ( $prop.arrayType ) +#if ( $prop.algorandFormat == "SignedTransaction" ) +#set ( $mapping = ".map(SignedTransaction.fromDecodedJSON)" ) +#else +#set ( $mapping = ".map(${prop.arrayType}.fromDecodedJSON)" ) +#end +#if ($prop.required) +($value ?? [])$mapping## +#else +typeof $value !== 'undefined' ? $value$mapping : undefined## +#end +#else +#if ( $prop.algorandFormat == "SignedTransaction" ) +#set ( $assignment = "SignedTransaction.fromDecodedJSON" ) +#else +#set ( $assignment = "${prop.refType}.fromDecodedJSON" ) +#end #if ($prop.required) $assignment($value ?? {})## #else @@ -341,24 +383,28 @@ export class $def.name implements MsgpackEncodable, JSONEncodable { return obj; } -// // eslint-disable-next-line camelcase -// static from_obj_for_encoding(data: Record): $def.name { -// /* eslint-disable dot-notation */ + static fromDecodedJSON(encoded: unknown): $def.name { + if (encoded === null || typeof encoded !== 'object') { +#set($d = "$") + throw new Error(`Invalid decoded $def.name: ${d}{encoded}`); + } + const data = encoded as Record; + /* eslint-disable dot-notation */ #if ($use_object_params) -// return new ${def.name}({ + return new ${def.name}({ #foreach( $prop in $props ) -// #paramName($prop): #fromObjForEncodingAssignType("data['$prop.propertyName']", $prop, $def.name), + #paramName($prop): #fromDecodedJSONAssignType("data['$prop.propertyName']", $prop, $def.name), #end -// }); + }); #else -// return new ${def.name}( + return new ${def.name}( #foreach( $prop in $props ) -// #fromObjForEncodingAssignType("data['$prop.propertyName']", $prop, $def.name), + #fromDecodedJSONAssignType("data['$prop.propertyName']", $prop, $def.name), #end -// ); + ); #end -// /* eslint-enable dot-notation */ -// } + /* eslint-enable dot-notation */ + } static fromDecodedMsgpack(data: unknown): $def.name { #set ( $d = "$" )## Create a variable in order to insert a $ into the code @@ -368,13 +414,13 @@ export class $def.name implements MsgpackEncodable, JSONEncodable { #if ($use_object_params) return new ${def.name}({ #foreach( $prop in $props ) - #paramName($prop): #fromObjForEncodingAssignType("data.get('$prop.propertyName')", $prop, $def.name), + #paramName($prop): #fromDecodedMsgpackAssignType("data.get('$prop.propertyName')", $prop, $def.name), #end }); #else return new ${def.name}( #foreach( $prop in $props ) - #fromObjForEncodingAssignType("data.get('$prop.propertyName')", $prop, $def.name), + #fromDecodedMsgpackAssignType("data.get('$prop.propertyName')", $prop, $def.name), #end ); #end From f1cc2698f35f8a8e7273bc121b4ae24253a6ae93 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Tue, 2 Apr 2024 13:48:40 -0400 Subject: [PATCH 03/13] UntypedValue and better signed transaction support --- typescript_templates/model.vm | 43 +++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/typescript_templates/model.vm b/typescript_templates/model.vm index 88f6c9b..9b61def 100644 --- a/typescript_templates/model.vm +++ b/typescript_templates/model.vm @@ -21,7 +21,7 @@ BlockHeader## bigint## #end #elseif ( $param.type == "object" ) -Map## +UntypedValue## #elseif ( $type_override == "number" || ( ( $param.type == "integer" || $param.arrayType == "integer" ) && $type_override.length() == 0 ) ) #if ( $isArgType ) (number | bigint)## @@ -145,7 +145,7 @@ $value## #set( $value = "this.#paramName($prop)" ) #if ( $prop.algorandFormat == "BlockHeader" ) blockHeaderMsgpackPrepare($value)## -#elseif ( "#isClassType($prop)" == "false" && $prop.algorandFormat != "SignedTransaction" ) +#elseif ( "#isClassType($prop)" == "false" && $prop.algorandFormat != "SignedTransaction" && $prop.type != "object" ) $value## #elseif ( $prop.arrayType ) ${value}.map(v => v.msgpackPrepare())## @@ -158,8 +158,8 @@ ${value}.msgpackPrepare()## #set( $value = "this.#paramName($prop)" ) #set( $sdkType = "#toSdkType($className, $prop, false)" ) #if ( $prop.algorandFormat == "BlockHeader" ) -$value## -#elseif ( "#isClassType($prop)" == "false" && $prop.algorandFormat != "SignedTransaction" && $sdkType != "Uint8Array" && $sdkType != "Uint8Array[]") +blockHeaderJSONPrepare($value)## +#elseif ( "#isClassType($prop)" == "false" && $prop.algorandFormat != "SignedTransaction" && $sdkType != "Uint8Array" && $sdkType != "Uint8Array[]" && $sdkType != "UntypedValue" ) $value## #elseif ( $prop.arrayType ) #if ( $sdkType == "Uint8Array[]" ) @@ -175,17 +175,21 @@ ${value}.jsonPrepare()## #end ## Create an expression to assign a field in the fromDecodedMsgpack function #macro ( fromDecodedMsgpackAssignType $value $prop $className ) +#set( $sdkType = "#toSdkType($className, $prop, false)" ) +#if ( $sdkType == "SignedTransaction[]" ) +#set ( $sdkType = "SignedTransaction" ) +#end #if ( $prop.algorandFormat == "BlockHeader" ) blockHeaderFromDecodedMsgpack($value)## -#elseif ( "#isClassType($prop)" == "false" && $prop.algorandFormat != "SignedTransaction" ) +#elseif ( "#isClassType($prop)" == "false" && $sdkType != "SignedTransaction" && $sdkType != "UntypedValue") #if ($prop.required) $value ?? #defaultValueForType($className, $prop)## #else $value## #end #elseif ( $prop.arrayType ) -#if ( $prop.algorandFormat == "SignedTransaction" ) -#set ( $mapping = ".map(SignedTransaction.fromDecodedMsgpack)" ) +#if ( $sdkType == "SignedTransaction" || $sdkType == "UntypedValue") +#set ( $mapping = ".map(${sdkType}.fromDecodedMsgpack)" ) #else #set ( $mapping = ".map(${prop.arrayType}.fromDecodedMsgpack)" ) #end @@ -195,8 +199,8 @@ $value## typeof $value !== 'undefined' ? $value$mapping : undefined## #end #else -#if ( $prop.algorandFormat == "SignedTransaction" ) -#set ( $assignment = "SignedTransaction.fromDecodedMsgpack" ) +#if ( $sdkType == "SignedTransaction" || $sdkType == "UntypedValue") +#set ( $assignment = "${sdkType}.fromDecodedMsgpack" ) #else #set ( $assignment = "${prop.refType}.fromDecodedMsgpack" ) #end @@ -209,17 +213,21 @@ typeof $value !== 'undefined' ? $assignment($value) : undefined## #end ## Create an expression to assign a field in the fromDecodedJSON function #macro ( fromDecodedJSONAssignType $value $prop $className ) +#set( $sdkType = "#toSdkType($className, $prop, false)" ) +#if ( $sdkType == "SignedTransaction[]" ) +#set ( $sdkType = "SignedTransaction" ) +#end #if ( $prop.algorandFormat == "BlockHeader" ) -$value as BlockHeader## -#elseif ( "#isClassType($prop)" == "false" && $prop.algorandFormat != "SignedTransaction" ) +blockHeaderFromDecodedJSON($value)## +#elseif ( "#isClassType($prop)" == "false" && $sdkType != "SignedTransaction" && $sdkType != "UntypedValue" ) #if ($prop.required) $value ?? #defaultValueForType($className, $prop)## #else $value## #end #elseif ( $prop.arrayType ) -#if ( $prop.algorandFormat == "SignedTransaction" ) -#set ( $mapping = ".map(SignedTransaction.fromDecodedJSON)" ) +#if ( $sdkType == "SignedTransaction" || $sdkType == "UntypedValue" ) +#set ( $mapping = ".map(${sdkType}.fromDecodedJSON)" ) #else #set ( $mapping = ".map(${prop.arrayType}.fromDecodedJSON)" ) #end @@ -229,8 +237,8 @@ $value## typeof $value !== 'undefined' ? $value$mapping : undefined## #end #else -#if ( $prop.algorandFormat == "SignedTransaction" ) -#set ( $assignment = "SignedTransaction.fromDecodedJSON" ) +#if ( $sdkType == "SignedTransaction" || $sdkType == "UntypedValue" ) +#set ( $assignment = "${sdkType}.fromDecodedJSON" ) #else #set ( $assignment = "${prop.refType}.fromDecodedJSON" ) #end @@ -253,7 +261,7 @@ typeof $value !== 'undefined' ? $assignment($value) : undefined## false## #elseif ( $sdkType == "Uint8Array" ) new Uint8Array()## -#elseif ( $sdkType == "BlockHeader" || $sdkType == "Map" || $sdkType == "SignedTransaction" ) +#elseif ( $sdkType == "BlockHeader" || $sdkType == "UntypedValue" || $sdkType == "SignedTransaction" ) {}## #else UNHANDLED DEFAULT TYPE @@ -281,9 +289,10 @@ import { ensureBigInt, ensureSafeInteger } from '../../../../utils/utils.js'; import { MsgpackEncodable, MsgpackEncodingData, JSONEncodable, JSONEncodingData } from '../../../../encoding/encoding.js'; import { base64ToBytes, bytesToBase64 } from '../../../../encoding/binarydata.js'; #if ( $propFile.indexer == "false" ) -import BlockHeader, { blockHeaderMsgpackPrepare, blockHeaderFromDecodedMsgpack } from '../../../../types/blockHeader.js'; +import BlockHeader, { blockHeaderMsgpackPrepare, blockHeaderFromDecodedMsgpack, blockHeaderJSONPrepare, blockHeaderFromDecodedJSON } from '../../../../types/blockHeader.js'; import { SignedTransaction } from '../../../../signedTransaction.js'; #end +import { UntypedValue } from '../../untypedmodel.js'; // import BaseModel from '../../basemodel.js'; #foreach( $modelEntry in $models.entrySet() ) From 414943f68a9c3044f1b7f9942b055fddeaac7e3b Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Thu, 11 Apr 2024 16:18:20 -0400 Subject: [PATCH 04/13] Make generated models encodable --- typescript_templates/model.vm | 139 +++++++++++++++++++++------------- 1 file changed, 85 insertions(+), 54 deletions(-) diff --git a/typescript_templates/model.vm b/typescript_templates/model.vm index 9b61def..150045d 100644 --- a/typescript_templates/model.vm +++ b/typescript_templates/model.vm @@ -59,6 +59,57 @@ $unknown.type ## force a template failure with an unknown type #end #if ($param.arrayType && $param.arrayType != "")[]#end## Add array postfix to arrays... #end +## Converts a parameter type into the SDK specific type. +#macro ( toSchema $className $param ) +#set( $sdkType = "#toSdkType($className, $param, false)" ) +#if ( $sdkType == "SignedTransaction[]" ) +#set ( $sdkType = "SignedTransaction" ) +#end +#if ( $param.algorandFormat == "BlockHeader" ) +BLOCK_HEADER_SCHEMA## +#elseif ( "#isClassType($param)" == "false" && $sdkType != "SignedTransaction" && $sdkType != "UntypedValue") +#if ( $param.algorandFormat == "Address" ) +new AddressSchema()## +#elseif ( $param.algorandFormat == "uint64" || $param.type == "integer" ) +new Uint64Schema()## +#elseif ( $param.arrayType == "integer" ) +new ArraySchema(new Uint64Schema())## +#elseif ( $param.type == "boolean" ) +new BooleanSchema()## +#elseif( $param.type == "address" ) +new AddressSchema()## +#elseif( $param.type == "binary" ) +new ByteArraySchema()## +#elseif($param.arrayType && $param.format == "byte") +new ByteArraySchema()## +#elseif( $param.type == "string" && $param.format == "byte" ) +new ByteArraySchema()## +#elseif( $param.type == "string" ) +new StringSchema()## +#elseif( $param.arrayType == "string" ) +new ArraySchema(new StringSchema())## +#else +UNHANDLED SCHEMA TYPE +- className: $className +- property: $param +- sdkType: $sdkType +$unknown.type ## force a template failure with an unknown type +#end +#elseif ( $param.arrayType ) +#if ( $sdkType == "SignedTransaction" || $sdkType == "UntypedValue") +#set ( $itemSchema = "${sdkType}.encodingSchema" ) +#else +#set ( $itemSchema = "${param.arrayType}.encodingSchema" ) +#end +new ArraySchema($itemSchema)## +#else +#if ( $sdkType == "SignedTransaction" || $sdkType == "UntypedValue") +${sdkType}.encodingSchema## +#else +${prop.refType}.encodingSchema## +#end +#end +#end ## Check if there's a class associated with this type #macro ( isClassType $param ) #if ( $param.algorandFormat == "SignedTransaction" ) @@ -148,9 +199,9 @@ blockHeaderMsgpackPrepare($value)## #elseif ( "#isClassType($prop)" == "false" && $prop.algorandFormat != "SignedTransaction" && $prop.type != "object" ) $value## #elseif ( $prop.arrayType ) -${value}.map(v => v.msgpackPrepare())## +${value}.map(v => v.toEncodingData())## #else -${value}.msgpackPrepare()## +${value}.toEncodingData()## #end #end ## Create an expression to assign a field in the jsonPrepare function @@ -189,9 +240,9 @@ $value## #end #elseif ( $prop.arrayType ) #if ( $sdkType == "SignedTransaction" || $sdkType == "UntypedValue") -#set ( $mapping = ".map(${sdkType}.fromDecodedMsgpack)" ) +#set ( $mapping = ".map(${sdkType}.fromEncodingData)" ) #else -#set ( $mapping = ".map(${prop.arrayType}.fromDecodedMsgpack)" ) +#set ( $mapping = ".map(${prop.arrayType}.fromEncodingData)" ) #end #if ($prop.required) ($value ?? [])$mapping## @@ -200,9 +251,9 @@ typeof $value !== 'undefined' ? $value$mapping : undefined## #end #else #if ( $sdkType == "SignedTransaction" || $sdkType == "UntypedValue") -#set ( $assignment = "${sdkType}.fromDecodedMsgpack" ) +#set ( $assignment = "${sdkType}.fromEncodingData" ) #else -#set ( $assignment = "${prop.refType}.fromDecodedMsgpack" ) +#set ( $assignment = "${prop.refType}.fromEncodingData" ) #end #if ($prop.required) $assignment($value ?? {})## @@ -286,10 +337,11 @@ $unknown.type ## force a template failure with an unknown type /* eslint-disable no-use-before-define */ import { ensureBigInt, ensureSafeInteger } from '../../../../utils/utils.js'; -import { MsgpackEncodable, MsgpackEncodingData, JSONEncodable, JSONEncodingData } from '../../../../encoding/encoding.js'; -import { base64ToBytes, bytesToBase64 } from '../../../../encoding/binarydata.js'; +import { Encodable, Schema } from '../../../../encoding/encoding.js'; +import { NamedMapSchema, ArraySchema, AddressSchema, Uint64Schema, StringSchema, BooleanSchema, ByteArraySchema } from '../../../../encoding/schema/index.js'; +import { base64ToBytes } from '../../../../encoding/binarydata.js'; #if ( $propFile.indexer == "false" ) -import BlockHeader, { blockHeaderMsgpackPrepare, blockHeaderFromDecodedMsgpack, blockHeaderJSONPrepare, blockHeaderFromDecodedJSON } from '../../../../types/blockHeader.js'; +import BlockHeader, { blockHeaderMsgpackPrepare, blockHeaderFromDecodedMsgpack, BLOCK_HEADER_SCHEMA } from '../../../../types/blockHeader.js'; import { SignedTransaction } from '../../../../signedTransaction.js'; #end import { UntypedValue } from '../../untypedmodel.js'; @@ -312,7 +364,22 @@ import { UntypedValue } from '../../untypedmodel.js'; * $str.formatDoc($def.doc, " * ") */ #end -export class $def.name implements MsgpackEncodable, JSONEncodable { +export class $def.name implements Encodable { + + private static encodingSchemaValue: Schema | undefined; + + static get encodingSchema(): Schema { + if (!this.encodingSchemaValue) { + this.encodingSchemaValue = new NamedMapSchema([]); + (this.encodingSchemaValue as NamedMapSchema).entries.push( +#foreach( $prop in $props ) + { key: '$prop.propertyName', valueSchema: #toSchema($def.name, $prop), required: $prop.required, omitEmpty: true }, +#end + ); + } + return this.encodingSchemaValue; + } + #foreach( $prop in $props ) #if ( !$prop.doc.isEmpty() ) /** @@ -356,8 +423,13 @@ export class $def.name implements MsgpackEncodable, JSONEncodable { #end } - msgpackPrepare(): Map { - const data = new Map([ + // eslint-disable-next-line class-methods-use-this + getEncodingSchema(): Schema { + return ${def.name}.encodingSchema; + } + + toEncodingData(): Map { + const data = new Map([ #foreach( $prop in $props ) #if ( $prop.required ) ['$prop.propertyName', #msgpackPrepareField($prop)], @@ -374,48 +446,7 @@ export class $def.name implements MsgpackEncodable, JSONEncodable { return data; } - jsonPrepare(): Record { - const obj: Record = {}; - - /* eslint-disable dot-notation */ -#foreach( $prop in $props ) -#if ( $prop.required ) - obj['$prop.propertyName'] = #jsonPrepareField($def.name, $prop); -#else - if (#shouldMsgpackPrepareOptionalField($prop)) { - obj['$prop.propertyName'] = #jsonPrepareField($def.name, $prop); - } -#end -#end - /* eslint-enable dot-notation */ - - return obj; - } - - static fromDecodedJSON(encoded: unknown): $def.name { - if (encoded === null || typeof encoded !== 'object') { -#set($d = "$") - throw new Error(`Invalid decoded $def.name: ${d}{encoded}`); - } - const data = encoded as Record; - /* eslint-disable dot-notation */ -#if ($use_object_params) - return new ${def.name}({ -#foreach( $prop in $props ) - #paramName($prop): #fromDecodedJSONAssignType("data['$prop.propertyName']", $prop, $def.name), -#end - }); -#else - return new ${def.name}( -#foreach( $prop in $props ) - #fromDecodedJSONAssignType("data['$prop.propertyName']", $prop, $def.name), -#end - ); -#end - /* eslint-enable dot-notation */ - } - - static fromDecodedMsgpack(data: unknown): $def.name { + static fromEncodingData(data: unknown): $def.name { #set ( $d = "$" )## Create a variable in order to insert a $ into the code if (!(data instanceof Map)) { throw new Error(`Invalid decoded logic sig account: ${d}{data}`); From 942ebc12b046f4c6eba707b0257b74b24bc0fba2 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Thu, 2 May 2024 15:17:59 -0400 Subject: [PATCH 05/13] Fix issues, support address --- typescript_templates/model.vm | 37 ++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/typescript_templates/model.vm b/typescript_templates/model.vm index 150045d..9b8fa74 100644 --- a/typescript_templates/model.vm +++ b/typescript_templates/model.vm @@ -10,8 +10,12 @@ $str.kebabToCamel($param.propertyName.replaceAll("_", "-"))## #set( $type_override = "#evaluate($type_override_variable)" ) #if ( $param.algorandFormat == "SignedTransaction" ) SignedTransaction## -#elseif ( $param.algorandFormat == "Address" )## No special handling for Address in go SDK -string## +#elseif ( $param.algorandFormat == "Address" ) +#if ( $isArgType ) +(Address | string)## +#else +Address## +#end #elseif ( $param.algorandFormat == "BlockHeader" ) BlockHeader## #elseif ( $type_override == "bigint" || ( $param.algorandFormat == "uint64" && $type_override.length() == 0 ) ) @@ -68,20 +72,18 @@ $unknown.type ## force a template failure with an unknown type #if ( $param.algorandFormat == "BlockHeader" ) BLOCK_HEADER_SCHEMA## #elseif ( "#isClassType($param)" == "false" && $sdkType != "SignedTransaction" && $sdkType != "UntypedValue") -#if ( $param.algorandFormat == "Address" ) -new AddressSchema()## +#if ( $param.type == "address" || $param.algorandFormat == "Address" ) +new StringSchema()## # To comply with existing msgpack REST API behavior, encode addresses as strings #elseif ( $param.algorandFormat == "uint64" || $param.type == "integer" ) new Uint64Schema()## #elseif ( $param.arrayType == "integer" ) new ArraySchema(new Uint64Schema())## #elseif ( $param.type == "boolean" ) new BooleanSchema()## -#elseif( $param.type == "address" ) -new AddressSchema()## #elseif( $param.type == "binary" ) new ByteArraySchema()## #elseif($param.arrayType && $param.format == "byte") -new ByteArraySchema()## +new ArraySchema(new ByteArraySchema())## #elseif( $param.type == "string" && $param.format == "byte" ) new ByteArraySchema()## #elseif( $param.type == "string" ) @@ -147,6 +149,14 @@ true## #set( $name = "#paramName($prop)" ) #if ( $argType == $fieldType ) $name## +#elseif ( $argType == "(Address | string)" && $fieldType == "Address" ) +typeof $name === 'string' ? Address.fromString($name) : $name## +#elseif ( $argType == "(Address | string)[]" && $fieldType == "Address[]" ) +#if ( $prop.required ) +${name}.map(addr => typeof addr === 'string' ? Address.fromString(addr) : addr)## +#else +typeof $name !== 'undefined' ? ${name}.map(addr => typeof addr === 'string' ? Address.fromString(addr) : addr) : undefined## +#end #elseif ( $argType == "string | Uint8Array" && $fieldType == "Uint8Array" ) typeof $name === 'string' ? base64ToBytes($name) : $name## #elseif ( $argType == "(number | bigint)" && $fieldType == "bigint" ) @@ -196,6 +206,12 @@ $value## #set( $value = "this.#paramName($prop)" ) #if ( $prop.algorandFormat == "BlockHeader" ) blockHeaderMsgpackPrepare($value)## +#elseif ( $prop.algorandFormat == "Address" || $prop.type == "Address" ) +#if ( $prop.arrayType ) +${value}.map(addr => addr.toString())## +#else +${value}.toString()## +#end #elseif ( "#isClassType($prop)" == "false" && $prop.algorandFormat != "SignedTransaction" && $prop.type != "object" ) $value## #elseif ( $prop.arrayType ) @@ -306,6 +322,8 @@ typeof $value !== 'undefined' ? $assignment($value) : undefined## []## #elseif ( $sdkType == "string" ) ""## +#elseif ( $sdkType == "Address" ) +Address.zeroAddress()## #elseif ( $sdkType == "number" || $sdkType == "bigint" )## Any bigint type will accept numbers anyway 0## #elseif ( $sdkType == "boolean" ) @@ -338,12 +356,13 @@ $unknown.type ## force a template failure with an unknown type /* eslint-disable no-use-before-define */ import { ensureBigInt, ensureSafeInteger } from '../../../../utils/utils.js'; import { Encodable, Schema } from '../../../../encoding/encoding.js'; -import { NamedMapSchema, ArraySchema, AddressSchema, Uint64Schema, StringSchema, BooleanSchema, ByteArraySchema } from '../../../../encoding/schema/index.js'; +import { NamedMapSchema, ArraySchema, Uint64Schema, StringSchema, BooleanSchema, ByteArraySchema } from '../../../../encoding/schema/index.js'; import { base64ToBytes } from '../../../../encoding/binarydata.js'; #if ( $propFile.indexer == "false" ) import BlockHeader, { blockHeaderMsgpackPrepare, blockHeaderFromDecodedMsgpack, BLOCK_HEADER_SCHEMA } from '../../../../types/blockHeader.js'; import { SignedTransaction } from '../../../../signedTransaction.js'; #end +import { Address } from '../../../../encoding/address.js'; import { UntypedValue } from '../../untypedmodel.js'; // import BaseModel from '../../basemodel.js'; @@ -373,7 +392,7 @@ export class $def.name implements Encodable { this.encodingSchemaValue = new NamedMapSchema([]); (this.encodingSchemaValue as NamedMapSchema).entries.push( #foreach( $prop in $props ) - { key: '$prop.propertyName', valueSchema: #toSchema($def.name, $prop), required: $prop.required, omitEmpty: true }, + { key: '$prop.propertyName', valueSchema: #toSchema($def.name, $prop), required: $prop.required, omitEmpty: !$prop.required }, #end ); } From 801f2c6d3862e7a578a45133a52c5f961df79c4b Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Fri, 10 May 2024 15:21:22 -0400 Subject: [PATCH 06/13] Simplify --- typescript_templates/model.vm | 111 ++++------------------------------ 1 file changed, 12 insertions(+), 99 deletions(-) diff --git a/typescript_templates/model.vm b/typescript_templates/model.vm index 9b8fa74..40971e8 100644 --- a/typescript_templates/model.vm +++ b/typescript_templates/model.vm @@ -201,11 +201,11 @@ $value && ${value}.length## $value## #end #end -## Create an expression to assign a field in the msgpackPrepare function -#macro ( msgpackPrepareField $prop ) +## Create an expression to assign a field in the toEncodingData function +#macro ( fieldToEncodingData $prop )## TODO: combine shouldMsgpackPrepareOptionalField with this, like fromEncodingDataAssignType #set( $value = "this.#paramName($prop)" ) #if ( $prop.algorandFormat == "BlockHeader" ) -blockHeaderMsgpackPrepare($value)## +blockHeaderToEncodingData($value)## #elseif ( $prop.algorandFormat == "Address" || $prop.type == "Address" ) #if ( $prop.arrayType ) ${value}.map(addr => addr.toString())## @@ -220,40 +220,16 @@ ${value}.map(v => v.toEncodingData())## ${value}.toEncodingData()## #end #end -## Create an expression to assign a field in the jsonPrepare function -#macro ( jsonPrepareField $className $prop ) -#set( $value = "this.#paramName($prop)" ) -#set( $sdkType = "#toSdkType($className, $prop, false)" ) -#if ( $prop.algorandFormat == "BlockHeader" ) -blockHeaderJSONPrepare($value)## -#elseif ( "#isClassType($prop)" == "false" && $prop.algorandFormat != "SignedTransaction" && $sdkType != "Uint8Array" && $sdkType != "Uint8Array[]" && $sdkType != "UntypedValue" ) -$value## -#elseif ( $prop.arrayType ) -#if ( $sdkType == "Uint8Array[]" ) -${value}.map(bytesToBase64)## -#else -${value}.map(v => v.jsonPrepare())## -#end -#elseif ( $sdkType == "Uint8Array" ) -bytesToBase64($value)## -#else -${value}.jsonPrepare()## -#end -#end -## Create an expression to assign a field in the fromDecodedMsgpack function -#macro ( fromDecodedMsgpackAssignType $value $prop $className ) +## Create an expression to assign a field in the fromEncodingData function +#macro ( fromEncodingDataAssignType $value $prop $className ) #set( $sdkType = "#toSdkType($className, $prop, false)" ) #if ( $sdkType == "SignedTransaction[]" ) #set ( $sdkType = "SignedTransaction" ) #end #if ( $prop.algorandFormat == "BlockHeader" ) -blockHeaderFromDecodedMsgpack($value)## +blockHeaderFromEncodingData($value)## #elseif ( "#isClassType($prop)" == "false" && $sdkType != "SignedTransaction" && $sdkType != "UntypedValue") -#if ($prop.required) -$value ?? #defaultValueForType($className, $prop)## -#else $value## -#end #elseif ( $prop.arrayType ) #if ( $sdkType == "SignedTransaction" || $sdkType == "UntypedValue") #set ( $mapping = ".map(${sdkType}.fromEncodingData)" ) @@ -278,68 +254,6 @@ typeof $value !== 'undefined' ? $assignment($value) : undefined## #end #end #end -## Create an expression to assign a field in the fromDecodedJSON function -#macro ( fromDecodedJSONAssignType $value $prop $className ) -#set( $sdkType = "#toSdkType($className, $prop, false)" ) -#if ( $sdkType == "SignedTransaction[]" ) -#set ( $sdkType = "SignedTransaction" ) -#end -#if ( $prop.algorandFormat == "BlockHeader" ) -blockHeaderFromDecodedJSON($value)## -#elseif ( "#isClassType($prop)" == "false" && $sdkType != "SignedTransaction" && $sdkType != "UntypedValue" ) -#if ($prop.required) -$value ?? #defaultValueForType($className, $prop)## -#else -$value## -#end -#elseif ( $prop.arrayType ) -#if ( $sdkType == "SignedTransaction" || $sdkType == "UntypedValue" ) -#set ( $mapping = ".map(${sdkType}.fromDecodedJSON)" ) -#else -#set ( $mapping = ".map(${prop.arrayType}.fromDecodedJSON)" ) -#end -#if ($prop.required) -($value ?? [])$mapping## -#else -typeof $value !== 'undefined' ? $value$mapping : undefined## -#end -#else -#if ( $sdkType == "SignedTransaction" || $sdkType == "UntypedValue" ) -#set ( $assignment = "${sdkType}.fromDecodedJSON" ) -#else -#set ( $assignment = "${prop.refType}.fromDecodedJSON" ) -#end -#if ($prop.required) -$assignment($value ?? {})## -#else -typeof $value !== 'undefined' ? $assignment($value) : undefined## -#end -#end -#end -#macro ( defaultValueForType $className $prop ) -#set( $sdkType = "#toSdkType($className, $prop, false)" ) -#if ( $prop.arrayType ) -[]## -#elseif ( $sdkType == "string" ) -""## -#elseif ( $sdkType == "Address" ) -Address.zeroAddress()## -#elseif ( $sdkType == "number" || $sdkType == "bigint" )## Any bigint type will accept numbers anyway -0## -#elseif ( $sdkType == "boolean" ) -false## -#elseif ( $sdkType == "Uint8Array" ) -new Uint8Array()## -#elseif ( $sdkType == "BlockHeader" || $sdkType == "UntypedValue" || $sdkType == "SignedTransaction" ) -{}## -#else -UNHANDLED DEFAULT TYPE -- class: $className -- property: $prop -- sdkType: $sdkType -$unknown.type ## force a template failure with an unknown type -#end -#end #macro ( questionMarkIfOptional $param ) #if ( ! $param.required ) ?## @@ -359,12 +273,11 @@ import { Encodable, Schema } from '../../../../encoding/encoding.js'; import { NamedMapSchema, ArraySchema, Uint64Schema, StringSchema, BooleanSchema, ByteArraySchema } from '../../../../encoding/schema/index.js'; import { base64ToBytes } from '../../../../encoding/binarydata.js'; #if ( $propFile.indexer == "false" ) -import BlockHeader, { blockHeaderMsgpackPrepare, blockHeaderFromDecodedMsgpack, BLOCK_HEADER_SCHEMA } from '../../../../types/blockHeader.js'; +import BlockHeader, { blockHeaderFromEncodingData, blockHeaderToEncodingData, BLOCK_HEADER_SCHEMA } from '../../../../types/blockHeader.js'; import { SignedTransaction } from '../../../../signedTransaction.js'; #end import { Address } from '../../../../encoding/address.js'; import { UntypedValue } from '../../untypedmodel.js'; -// import BaseModel from '../../basemodel.js'; #foreach( $modelEntry in $models.entrySet() ) #set( $def = $modelEntry.key ) @@ -392,7 +305,7 @@ export class $def.name implements Encodable { this.encodingSchemaValue = new NamedMapSchema([]); (this.encodingSchemaValue as NamedMapSchema).entries.push( #foreach( $prop in $props ) - { key: '$prop.propertyName', valueSchema: #toSchema($def.name, $prop), required: $prop.required, omitEmpty: !$prop.required }, + { key: '$prop.propertyName', valueSchema: #toSchema($def.name, $prop), required: $prop.required, omitEmpty: true }, #end ); } @@ -451,14 +364,14 @@ export class $def.name implements Encodable { const data = new Map([ #foreach( $prop in $props ) #if ( $prop.required ) - ['$prop.propertyName', #msgpackPrepareField($prop)], + ['$prop.propertyName', #fieldToEncodingData($prop)], #end #end ]); #foreach( $prop in $props ) #if ( ! $prop.required ) if (#shouldMsgpackPrepareOptionalField($prop)) { - data.set('$prop.propertyName', #msgpackPrepareField($prop)); + data.set('$prop.propertyName', #fieldToEncodingData($prop)); } #end #end @@ -473,13 +386,13 @@ export class $def.name implements Encodable { #if ($use_object_params) return new ${def.name}({ #foreach( $prop in $props ) - #paramName($prop): #fromDecodedMsgpackAssignType("data.get('$prop.propertyName')", $prop, $def.name), + #paramName($prop): #fromEncodingDataAssignType("data.get('$prop.propertyName')", $prop, $def.name), #end }); #else return new ${def.name}( #foreach( $prop in $props ) - #fromDecodedMsgpackAssignType("data.get('$prop.propertyName')", $prop, $def.name), + #fromEncodingDataAssignType("data.get('$prop.propertyName')", $prop, $def.name), #end ); #end From 1841f52da92e7aaf337d67ad304a0205000f2156 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Wed, 22 May 2024 14:16:30 -0400 Subject: [PATCH 07/13] Simplify toSchema --- typescript_templates/model.vm | 52 ++++++++++++++--------------------- 1 file changed, 21 insertions(+), 31 deletions(-) diff --git a/typescript_templates/model.vm b/typescript_templates/model.vm index 40971e8..9cbc75b 100644 --- a/typescript_templates/model.vm +++ b/typescript_templates/model.vm @@ -65,51 +65,41 @@ $unknown.type ## force a template failure with an unknown type #end ## Converts a parameter type into the SDK specific type. #macro ( toSchema $className $param ) -#set( $sdkType = "#toSdkType($className, $param, false)" ) -#if ( $sdkType == "SignedTransaction[]" ) -#set ( $sdkType = "SignedTransaction" ) +#if ( $param.arrayType ) +new ArraySchema(## #end #if ( $param.algorandFormat == "BlockHeader" ) BLOCK_HEADER_SCHEMA## -#elseif ( "#isClassType($param)" == "false" && $sdkType != "SignedTransaction" && $sdkType != "UntypedValue") -#if ( $param.type == "address" || $param.algorandFormat == "Address" ) +#elseif ( $param.algorandFormat == "SignedTransaction" ) +SignedTransaction.encodingSchema## +#elseif ( $param.type == "object" || $param.arrayType == "object" ) +UntypedValue.encodingSchema## +#elseif ( $param.algorandFormat == "Address" ) new StringSchema()## # To comply with existing msgpack REST API behavior, encode addresses as strings -#elseif ( $param.algorandFormat == "uint64" || $param.type == "integer" ) +#elseif ( $param.algorandFormat == "uint64" || $param.type == "integer" || $param.arrayType == "integer" ) new Uint64Schema()## -#elseif ( $param.arrayType == "integer" ) -new ArraySchema(new Uint64Schema())## -#elseif ( $param.type == "boolean" ) +#elseif ( $param.type == "boolean" || $param.arrayType == "boolean" ) new BooleanSchema()## -#elseif( $param.type == "binary" ) +#elseif( ( $param.type == "string" || $param.arrayType == "string" ) && ( $param.format == "byte" || $param.format == "binary" ) ) new ByteArraySchema()## -#elseif($param.arrayType && $param.format == "byte") -new ArraySchema(new ByteArraySchema())## -#elseif( $param.type == "string" && $param.format == "byte" ) -new ByteArraySchema()## -#elseif( $param.type == "string" ) +#elseif( $param.type == "string" || $param.arrayType == "string" ) new StringSchema()## -#elseif( $param.arrayType == "string" ) -new ArraySchema(new StringSchema())## +#elseif ( "#isClassType($param)" == "true" ) +#if ( $param.arrayType ) +#set( $propClassName = $param.arrayType ) +#else +#set( $propClassName = $param.refType ) +#end +${propClassName}.encodingSchema## #else UNHANDLED SCHEMA TYPE - className: $className - property: $param -- sdkType: $sdkType +- isClassType: #isClassType($param) $unknown.type ## force a template failure with an unknown type #end -#elseif ( $param.arrayType ) -#if ( $sdkType == "SignedTransaction" || $sdkType == "UntypedValue") -#set ( $itemSchema = "${sdkType}.encodingSchema" ) -#else -#set ( $itemSchema = "${param.arrayType}.encodingSchema" ) -#end -new ArraySchema($itemSchema)## -#else -#if ( $sdkType == "SignedTransaction" || $sdkType == "UntypedValue") -${sdkType}.encodingSchema## -#else -${prop.refType}.encodingSchema## -#end +#if ( $param.arrayType ) +)## #end #end ## Check if there's a class associated with this type From 952991f2e6eba05fbe2e264f8863d8685bcf1d4d Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Thu, 23 May 2024 11:13:05 -0400 Subject: [PATCH 08/13] Simplify toEncodingData --- typescript_templates/model.vm | 41 +++++++++++++++++------------------ 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/typescript_templates/model.vm b/typescript_templates/model.vm index 9cbc75b..a3e2961 100644 --- a/typescript_templates/model.vm +++ b/typescript_templates/model.vm @@ -182,32 +182,41 @@ UNHANDLED CONSTRUCTOR TYPE CONVERSION $unknown.type ## force a template failure with an unknown type #end #end -## Create an expression which checks if a field should be included in msgpackPrepare -#macro ( shouldMsgpackPrepareOptionalField $prop ) -#set( $value = "this.#paramName($prop)" ) -#if ( $prop.arrayType ) -$value && ${value}.length## -#else -$value## -#end -#end ## Create an expression to assign a field in the toEncodingData function -#macro ( fieldToEncodingData $prop )## TODO: combine shouldMsgpackPrepareOptionalField with this, like fromEncodingDataAssignType +#macro ( fieldToEncodingData $prop ) #set( $value = "this.#paramName($prop)" ) #if ( $prop.algorandFormat == "BlockHeader" ) blockHeaderToEncodingData($value)## #elseif ( $prop.algorandFormat == "Address" || $prop.type == "Address" ) +#if ( !$prop.required ) +typeof ${value} !== "undefined" ? ## +#end #if ( $prop.arrayType ) ${value}.map(addr => addr.toString())## #else ${value}.toString()## #end +#if ( !$prop.required ) +: undefined## +#end #elseif ( "#isClassType($prop)" == "false" && $prop.algorandFormat != "SignedTransaction" && $prop.type != "object" ) $value## #elseif ( $prop.arrayType ) +#if ( !$prop.required ) +typeof ${value} !== "undefined" ? ## +#end ${value}.map(v => v.toEncodingData())## +#if ( !$prop.required ) +: undefined## +#end #else +#if ( !$prop.required ) +typeof ${value} !== "undefined" ? ## +#end ${value}.toEncodingData()## +#if ( !$prop.required ) +: undefined## +#end #end #end ## Create an expression to assign a field in the fromEncodingData function @@ -351,21 +360,11 @@ export class $def.name implements Encodable { } toEncodingData(): Map { - const data = new Map([ + return new Map([ #foreach( $prop in $props ) -#if ( $prop.required ) ['$prop.propertyName', #fieldToEncodingData($prop)], -#end #end ]); -#foreach( $prop in $props ) -#if ( ! $prop.required ) - if (#shouldMsgpackPrepareOptionalField($prop)) { - data.set('$prop.propertyName', #fieldToEncodingData($prop)); - } -#end -#end - return data; } static fromEncodingData(data: unknown): $def.name { From bf8208f6b7329d8402ace8754c2733459cc60b7e Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Tue, 28 May 2024 14:31:48 -0400 Subject: [PATCH 09/13] Clena up toEncodingData and fromEncodingData generation --- typescript_templates/model.vm | 109 +++++++++++++++++++--------------- 1 file changed, 62 insertions(+), 47 deletions(-) diff --git a/typescript_templates/model.vm b/typescript_templates/model.vm index a3e2961..387a94d 100644 --- a/typescript_templates/model.vm +++ b/typescript_templates/model.vm @@ -132,6 +132,23 @@ false## true## #end #end +## Returns "true" if the type is a primative, meaning it's not an object that requires manipulation +## when converting to & from encoding data. +#macro ( isPrimativeType $param ) +#if ( $param.algorandFormat == "uint64" || $param.type == "integer" || $param.arrayType == "integer" ) +true## +#elseif ( $param.type == "boolean" || $param.arrayType == "boolean" ) +true## +#elseif( $param.type == "string" || $param.arrayType == "string" ) +#if ( $param.algorandFormat == "Address" || $param.algorandFormat == "SignedTransaction" ) +false## +#else +true## +#end +#else +false## +#end +#end ## Create an expression to assign a field in a constructor #macro ( constructorAssignType $className $prop ) #set( $argType = "#toSdkType($className, $prop, true)" ) @@ -185,72 +202,70 @@ $unknown.type ## force a template failure with an unknown type ## Create an expression to assign a field in the toEncodingData function #macro ( fieldToEncodingData $prop ) #set( $value = "this.#paramName($prop)" ) -#if ( $prop.algorandFormat == "BlockHeader" ) -blockHeaderToEncodingData($value)## -#elseif ( $prop.algorandFormat == "Address" || $prop.type == "Address" ) -#if ( !$prop.required ) +#set( $isPrimative = "#isPrimativeType($prop)" == "true" ) +#set( $needsUndefinedCheck = !$isPrimative && !$prop.required ) +#if ( $needsUndefinedCheck ) typeof ${value} !== "undefined" ? ## #end -#if ( $prop.arrayType ) -${value}.map(addr => addr.toString())## -#else -${value}.toString()## -#end -#if ( !$prop.required ) -: undefined## +#if ( $prop.arrayType && !$isPrimative )## No need to map a primative type +${value}.map(v => ## +#set ( $value = "v" ) #end -#elseif ( "#isClassType($prop)" == "false" && $prop.algorandFormat != "SignedTransaction" && $prop.type != "object" ) +#if ( $prop.algorandFormat == "BlockHeader" ) +blockHeaderToEncodingData($value)## +#elseif ( $prop.algorandFormat == "Address" ) +${value}.toString()## +#elseif ( $isPrimative ) $value## -#elseif ( $prop.arrayType ) -#if ( !$prop.required ) -typeof ${value} !== "undefined" ? ## -#end -${value}.map(v => v.toEncodingData())## -#if ( !$prop.required ) -: undefined## -#end #else -#if ( !$prop.required ) -typeof ${value} !== "undefined" ? ## -#end ${value}.toEncodingData()## -#if ( !$prop.required ) -: undefined## #end +#if ( $prop.arrayType && !$isPrimative ) +)## +#end +#if ( $needsUndefinedCheck ) +: undefined## #end #end ## Create an expression to assign a field in the fromEncodingData function #macro ( fromEncodingDataAssignType $value $prop $className ) -#set( $sdkType = "#toSdkType($className, $prop, false)" ) -#if ( $sdkType == "SignedTransaction[]" ) -#set ( $sdkType = "SignedTransaction" ) +#set( $isPrimative = "#isPrimativeType($prop)" == "true" || $prop.algorandFormat == "Address" )## Addresses are encoded as strings, so treat them as primatives here +#set( $needsUndefinedCheck = !$isPrimative && !$prop.required ) +#if ( $needsUndefinedCheck ) +typeof ${value} !== "undefined" ? ## #end -#if ( $prop.algorandFormat == "BlockHeader" ) -blockHeaderFromEncodingData($value)## -#elseif ( "#isClassType($prop)" == "false" && $sdkType != "SignedTransaction" && $sdkType != "UntypedValue") -$value## -#elseif ( $prop.arrayType ) -#if ( $sdkType == "SignedTransaction" || $sdkType == "UntypedValue") -#set ( $mapping = ".map(${sdkType}.fromEncodingData)" ) +#if ( $prop.arrayType && !$isPrimative ) +#if ( $prop.required ) +(${value} ?? [])## #else -#set ( $mapping = ".map(${prop.arrayType}.fromEncodingData)" ) +$value## #end -#if ($prop.required) -($value ?? [])$mapping## -#else -typeof $value !== 'undefined' ? $value$mapping : undefined## +.map((v: unknown) => ## +#set ( $value = "v" ) +#elseif ( $prop.required && !$isPrimative ) +#set ( $value = "($value ?? new Map())" )## #end +#if ( $prop.algorandFormat == "BlockHeader" ) +blockHeaderFromEncodingData($value)## +#elseif ( $isPrimative ) +${value}## #else -#if ( $sdkType == "SignedTransaction" || $sdkType == "UntypedValue") -#set ( $assignment = "${sdkType}.fromEncodingData" ) +#if ( $prop.algorandFormat == "SignedTransaction" ) +SignedTransaction## +#elseif ( $prop.type == "object" ) +UntypedValue## +#elseif ( $prop.arrayType ) +${prop.arrayType}## #else -#set ( $assignment = "${prop.refType}.fromEncodingData" ) +${prop.refType}## #end -#if ($prop.required) -$assignment($value ?? {})## -#else -typeof $value !== 'undefined' ? $assignment($value) : undefined## +.fromEncodingData($value) #end +#if ( $prop.arrayType && !$isPrimative ) +)## +#end +#if ( $needsUndefinedCheck ) +: undefined## #end #end #macro ( questionMarkIfOptional $param ) From 5fd9cec5ab123af8ef31d79e8e08fb99c07e8dfc Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Tue, 28 May 2024 14:54:46 -0400 Subject: [PATCH 10/13] Fix doc, add comment about recursive refs --- typescript_templates/model.vm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/typescript_templates/model.vm b/typescript_templates/model.vm index 387a94d..e97331b 100644 --- a/typescript_templates/model.vm +++ b/typescript_templates/model.vm @@ -63,7 +63,7 @@ $unknown.type ## force a template failure with an unknown type #end #if ($param.arrayType && $param.arrayType != "")[]#end## Add array postfix to arrays... #end -## Converts a parameter type into the SDK specific type. +## Gets the Schema object for a given type. #macro ( toSchema $className $param ) #if ( $param.arrayType ) new ArraySchema(## @@ -317,6 +317,8 @@ export class $def.name implements Encodable { static get encodingSchema(): Schema { if (!this.encodingSchemaValue) { this.encodingSchemaValue = new NamedMapSchema([]); +## By assigning a value to this.encodingSchemaValue before getting the .encodingSchema fields of other types, +## we allow circular references to be handled properly. (this.encodingSchemaValue as NamedMapSchema).entries.push( #foreach( $prop in $props ) { key: '$prop.propertyName', valueSchema: #toSchema($def.name, $prop), required: $prop.required, omitEmpty: true }, From b554d40ccdf5e02e2de9ce06e9d389666e835e9c Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Tue, 28 May 2024 15:10:24 -0400 Subject: [PATCH 11/13] Remove unnecessary params --- typescript_templates/model.vm | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/typescript_templates/model.vm b/typescript_templates/model.vm index e97331b..fc3e7ad 100644 --- a/typescript_templates/model.vm +++ b/typescript_templates/model.vm @@ -64,7 +64,7 @@ $unknown.type ## force a template failure with an unknown type #if ($param.arrayType && $param.arrayType != "")[]#end## Add array postfix to arrays... #end ## Gets the Schema object for a given type. -#macro ( toSchema $className $param ) +#macro ( toSchema $param ) #if ( $param.arrayType ) new ArraySchema(## #end @@ -93,7 +93,6 @@ new StringSchema()## ${propClassName}.encodingSchema## #else UNHANDLED SCHEMA TYPE -- className: $className - property: $param - isClassType: #isClassType($param) $unknown.type ## force a template failure with an unknown type @@ -228,7 +227,7 @@ ${value}.toEncodingData()## #end #end ## Create an expression to assign a field in the fromEncodingData function -#macro ( fromEncodingDataAssignType $value $prop $className ) +#macro ( fromEncodingDataAssignType $value $prop ) #set( $isPrimative = "#isPrimativeType($prop)" == "true" || $prop.algorandFormat == "Address" )## Addresses are encoded as strings, so treat them as primatives here #set( $needsUndefinedCheck = !$isPrimative && !$prop.required ) #if ( $needsUndefinedCheck ) @@ -321,7 +320,7 @@ export class $def.name implements Encodable { ## we allow circular references to be handled properly. (this.encodingSchemaValue as NamedMapSchema).entries.push( #foreach( $prop in $props ) - { key: '$prop.propertyName', valueSchema: #toSchema($def.name, $prop), required: $prop.required, omitEmpty: true }, + { key: '$prop.propertyName', valueSchema: #toSchema($prop), required: $prop.required, omitEmpty: true }, #end ); } @@ -392,13 +391,13 @@ export class $def.name implements Encodable { #if ($use_object_params) return new ${def.name}({ #foreach( $prop in $props ) - #paramName($prop): #fromEncodingDataAssignType("data.get('$prop.propertyName')", $prop, $def.name), + #paramName($prop): #fromEncodingDataAssignType("data.get('$prop.propertyName')", $prop), #end }); #else return new ${def.name}( #foreach( $prop in $props ) - #fromEncodingDataAssignType("data.get('$prop.propertyName')", $prop, $def.name), + #fromEncodingDataAssignType("data.get('$prop.propertyName')", $prop), #end ); #end From 83b3ecc33e2114fe0b24e3e7a1c19042aeeadc2a Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Tue, 28 May 2024 15:14:35 -0400 Subject: [PATCH 12/13] small simplification --- typescript_templates/model.vm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/typescript_templates/model.vm b/typescript_templates/model.vm index fc3e7ad..6bf91c0 100644 --- a/typescript_templates/model.vm +++ b/typescript_templates/model.vm @@ -86,11 +86,11 @@ new ByteArraySchema()## new StringSchema()## #elseif ( "#isClassType($param)" == "true" ) #if ( $param.arrayType ) -#set( $propClassName = $param.arrayType ) +${param.arrayType}## #else -#set( $propClassName = $param.refType ) +${param.refType}## #end -${propClassName}.encodingSchema## +.encodingSchema## #else UNHANDLED SCHEMA TYPE - property: $param From 2c9bf198053c1a3bbdfcd374c1d4a333a1d83dc7 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Thu, 30 May 2024 17:53:05 -0400 Subject: [PATCH 13/13] Stop using required, start using OptionalSchema --- typescript_templates/model.vm | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/typescript_templates/model.vm b/typescript_templates/model.vm index 6bf91c0..346088d 100644 --- a/typescript_templates/model.vm +++ b/typescript_templates/model.vm @@ -65,6 +65,9 @@ $unknown.type ## force a template failure with an unknown type #end ## Gets the Schema object for a given type. #macro ( toSchema $param ) +#if ( !$param.required ) +new OptionalSchema(## +#end #if ( $param.arrayType ) new ArraySchema(## #end @@ -100,6 +103,9 @@ $unknown.type ## force a template failure with an unknown type #if ( $param.arrayType ) )## #end +#if ( !$param.required ) +)## +#end #end ## Check if there's a class associated with this type #macro ( isClassType $param ) @@ -283,7 +289,7 @@ ${prop.refType}## /* eslint-disable no-use-before-define */ import { ensureBigInt, ensureSafeInteger } from '../../../../utils/utils.js'; import { Encodable, Schema } from '../../../../encoding/encoding.js'; -import { NamedMapSchema, ArraySchema, Uint64Schema, StringSchema, BooleanSchema, ByteArraySchema } from '../../../../encoding/schema/index.js'; +import { NamedMapSchema, ArraySchema, Uint64Schema, StringSchema, BooleanSchema, ByteArraySchema, OptionalSchema } from '../../../../encoding/schema/index.js'; import { base64ToBytes } from '../../../../encoding/binarydata.js'; #if ( $propFile.indexer == "false" ) import BlockHeader, { blockHeaderFromEncodingData, blockHeaderToEncodingData, BLOCK_HEADER_SCHEMA } from '../../../../types/blockHeader.js'; @@ -320,7 +326,7 @@ export class $def.name implements Encodable { ## we allow circular references to be handled properly. (this.encodingSchemaValue as NamedMapSchema).entries.push( #foreach( $prop in $props ) - { key: '$prop.propertyName', valueSchema: #toSchema($prop), required: $prop.required, omitEmpty: true }, + { key: '$prop.propertyName', valueSchema: #toSchema($prop), omitEmpty: true }, #end ); }