Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generator changes for improved encoding & decoding #73

Merged
merged 13 commits into from
Jun 5, 2024
221 changes: 179 additions & 42 deletions typescript_templates/model.vm
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ $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
string##
SignedTransaction##
#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 ) )
Expand All @@ -21,7 +25,7 @@ BlockHeader##
bigint##
#end
#elseif ( $param.type == "object" )
Record<string, any>##
UntypedValue##
#elseif ( $type_override == "number" || ( ( $param.type == "integer" || $param.arrayType == "integer" ) && $type_override.length() == 0 ) )
#if ( $isArgType )
(number | bigint)##
Expand All @@ -46,7 +50,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
Expand All @@ -59,6 +63,50 @@ $unknown.type ## force a template failure with an unknown type
#end
#if ($param.arrayType && $param.arrayType != "")[]#end## Add array postfix to arrays...
#end
## Gets the Schema object for a given type.
#macro ( toSchema $param )
#if ( !$param.required )
new OptionalSchema(##
#end
#if ( $param.arrayType )
new ArraySchema(##
#end
#if ( $param.algorandFormat == "BlockHeader" )
BLOCK_HEADER_SCHEMA##
#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" || $param.arrayType == "integer" )
new Uint64Schema()##
#elseif ( $param.type == "boolean" || $param.arrayType == "boolean" )
new BooleanSchema()##
#elseif( ( $param.type == "string" || $param.arrayType == "string" ) && ( $param.format == "byte" || $param.format == "binary" ) )
new ByteArraySchema()##
#elseif( $param.type == "string" || $param.arrayType == "string" )
new StringSchema()##
#elseif ( "#isClassType($param)" == "true" )
#if ( $param.arrayType )
${param.arrayType}##
#else
${param.refType}##
#end
.encodingSchema##
#else
UNHANDLED SCHEMA TYPE
- property: $param
- isClassType: #isClassType($param)
$unknown.type ## force a template failure with an unknown type
#end
#if ( $param.arrayType )
)##
#end
#if ( !$param.required )
)##
#end
#end
## Check if there's a class associated with this type
#macro ( isClassType $param )
#if ( $param.algorandFormat == "SignedTransaction" )
Expand Down Expand Up @@ -89,13 +137,38 @@ 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)" )
#set( $fieldType = "#toSdkType($className, $prop, false)" )
#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" )
Expand Down Expand Up @@ -131,24 +204,73 @@ UNHANDLED CONSTRUCTOR TYPE CONVERSION
$unknown.type ## force a template failure with an unknown type
#end
#end
## Create an expression to assign a field in the from_obj_for_encoding function
#macro ( fromObjForEncodingAssignType $value $prop )
#if ( "#isClassType($prop)" == "false" )
## Create an expression to assign a field in the toEncodingData function
#macro ( fieldToEncodingData $prop )
#set( $value = "this.#paramName($prop)" )
#set( $isPrimative = "#isPrimativeType($prop)" == "true" )
#set( $needsUndefinedCheck = !$isPrimative && !$prop.required )
#if ( $needsUndefinedCheck )
typeof ${value} !== "undefined" ? ##
#end
#if ( $prop.arrayType && !$isPrimative )## No need to map a primative type
${value}.map(v => ##
#set ( $value = "v" )
#end
#if ( $prop.algorandFormat == "BlockHeader" )
blockHeaderToEncodingData($value)##
#elseif ( $prop.algorandFormat == "Address" )
${value}.toString()##
#elseif ( $isPrimative )
$value##
#elseif ( $prop.arrayType )
#set ( $assignment = "${value}.map(${prop.arrayType}.from_obj_for_encoding)" )
#if ($prop.required)
$assignment##
#else
typeof $value !== 'undefined' ? $assignment : undefined##
${value}.toEncodingData()##
#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 )
#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.arrayType && !$isPrimative )
#if ( $prop.required )
(${value} ?? [])##
#else
$value##
#end
.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
#set ( $assignment = "${prop.refType}.from_obj_for_encoding($value)" )
#if ($prop.required)
$assignment##
#if ( $prop.algorandFormat == "SignedTransaction" )
SignedTransaction##
#elseif ( $prop.type == "object" )
UntypedValue##
#elseif ( $prop.arrayType )
${prop.arrayType}##
#else
typeof $value !== 'undefined' ? $assignment : undefined##
${prop.refType}##
#end
.fromEncodingData($value)
#end
#if ( $prop.arrayType && !$isPrimative )
)##
#end
#if ( $needsUndefinedCheck )
: undefined##
#end
#end
#macro ( questionMarkIfOptional $param )
Expand All @@ -166,12 +288,15 @@ typeof $value !== 'undefined' ? $assignment : undefined##

/* 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, OptionalSchema } from '../../../../encoding/schema/index.js';
import { base64ToBytes } from '../../../../encoding/binarydata.js';
#if ( $propFile.indexer == "false" )
import BlockHeader from '../../../../types/blockHeader.js';
import { EncodedSignedTransaction } from '../../../../types/transactions/encoded.js';
import BlockHeader, { blockHeaderFromEncodingData, blockHeaderToEncodingData, BLOCK_HEADER_SCHEMA } from '../../../../types/blockHeader.js';
import { SignedTransaction } from '../../../../signedTransaction.js';
#end
import BaseModel from '../../basemodel.js';
import { Address } from '../../../../encoding/address.js';
import { UntypedValue } from '../../untypedmodel.js';

#foreach( $modelEntry in $models.entrySet() )
#set( $def = $modelEntry.key )
Expand All @@ -190,7 +315,24 @@ import BaseModel from '../../basemodel.js';
* $str.formatDoc($def.doc, " * ")
*/
#end
export class $def.name extends BaseModel {
export class $def.name implements Encodable {

private static encodingSchemaValue: Schema | undefined;

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($prop), omitEmpty: true },
#end
);
}
return this.encodingSchemaValue;
}

#foreach( $prop in $props )
#if ( !$prop.doc.isEmpty() )
/**
Expand Down Expand Up @@ -228,48 +370,43 @@ export class $def.name extends BaseModel {
#else
) {
#end
super();
#foreach( $prop in $props )
#set( $var = "#paramName($prop)" )
this.$var = #constructorAssignType($def.name, $prop);
#end
}

// eslint-disable-next-line class-methods-use-this
getEncodingSchema(): Schema {
return ${def.name}.encodingSchema;
}

this.attribute_map = {
toEncodingData(): Map<string, unknown> {
return new Map<string, unknown>([
#foreach( $prop in $props )
#paramName($prop): '$prop.propertyName',
['$prop.propertyName', #fieldToEncodingData($prop)],
#end
}
]);
}

// eslint-disable-next-line camelcase
static from_obj_for_encoding(data: Record<string, any>): $def.name {
/* eslint-disable dot-notation */
static fromEncodingData(data: unknown): $def.name {
#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}`);
#else
if (typeof data['$prop.propertyName'] === 'undefined')
throw new Error(`Response is missing required field '${prop.propertyName}': ${d}{data}`);
#end
#end
#end
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): #fromEncodingDataAssignType("data.get('$prop.propertyName')", $prop),
#end
});
#else
return new ${def.name}(
#foreach( $prop in $props )
#fromObjForEncodingAssignType("data['$prop.propertyName']", $prop),
#fromEncodingDataAssignType("data.get('$prop.propertyName')", $prop),
#end
);
#end
/* eslint-enable dot-notation */
}
}

Expand Down
Loading