Skip to content

Commit

Permalink
Generator changes for improved encoding & decoding (#73)
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonpaulos authored Jun 5, 2024
1 parent b9042f4 commit 14cf1f2
Showing 1 changed file with 179 additions and 42 deletions.
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

0 comments on commit 14cf1f2

Please sign in to comment.