From ae626de8b8c6b0c45697a330ec1bb52befe22fda Mon Sep 17 00:00:00 2001 From: Muhammad Azeez Date: Tue, 22 Oct 2024 19:02:22 +0300 Subject: [PATCH 1/7] map properties --- src/index.ts | 84 +++++++++++++++++++++++++++++++++++++++- template/src/pdk.ts.ejs | 44 +++++++++++++-------- tests/schemas/fruit.yaml | 31 +++++++++++++++ 3 files changed, 140 insertions(+), 19 deletions(-) diff --git a/src/index.ts b/src/index.ts index 3c65f66..07b3b56 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,12 @@ import ejs from 'ejs' -import { helpers, getContext, Property, Parameter } from "@dylibso/xtp-bindgen" +import { helpers, getContext, Property, Parameter, } from "@dylibso/xtp-bindgen" function toTypeScriptType(property: Property | Parameter): string { let tp if (property.$ref) { tp = property.$ref.name + } else if (property.additionalProperties) { + tp = `Record` } else { switch (property.type) { case "string": @@ -49,6 +51,81 @@ function toTypeScriptType(property: Property | Parameter): string { return `${tp} | null` } +function getBaseRef(property: Property): string { + if (property.additionalProperties) { + if (property.additionalProperties.items?.$ref) { + return property.additionalProperties.items.$ref.name + } + + return property.additionalProperties.$ref?.name || '' + } + + if (property.items) { + return property.items.$ref?.name || '' + } + + return property.$ref?.name || '' +} + +function getPropertyValue(prop: Property): string | null { + const baseP = (prop.items ? prop.items : prop) as Property; + const baseRef = getBaseRef(prop); + + if (helpers.isDateTime(baseP)) { + return `cast(dateFromJson, obj.${prop.name})`; + } else if (isBuffer(baseP)) { + return `cast(bufferFromJson, obj.${prop.name})`; + } else if (helpers.isMap(prop)) { + return getMapConverter(prop); + } else if (!helpers.isPrimitive(baseP)) { + return `cast(${baseRef}.fromJson, obj.${prop.name})`; + } + return null; +} + +// Helper function to handle map conversion +function getMapConverter(prop: Property): string { + if (helpers.isMap(prop.additionalProperties as Property)) { + // Handle nested maps recursively + const innerConverter = getPropertyValue(prop.additionalProperties as Property); + return `mapFromJson(obj.${prop.name}, (v) => ${innerConverter})`; + } else if (!helpers.isPrimitive(prop.additionalProperties as Property)) { + const baseRef = getBaseRef(prop.additionalProperties as Property); + return `mapFromJson(obj.${prop.name}, ${baseRef}.fromJson)`; + } + + return `mapFromJson(obj.${prop.name}, (v: any) => v as ${toTypeScriptType(prop.additionalProperties as Property)})`; +} + +function getPropertyToJsonValue(prop: Property): string | null { + const baseP = (prop.items ? prop.items : prop) as Property; + const baseRef = getBaseRef(prop); + + if (helpers.isDateTime(baseP)) { + return `cast(dateToJson, obj.${prop.name})`; + } else if (isBuffer(baseP)) { + return `cast(bufferToJson, obj.${prop.name})`; + } else if (helpers.isMap(prop)) { + return getMapToJsonConverter(prop); + } else if (!helpers.isPrimitive(baseP)) { + return `cast(${baseRef}.toJson, obj.${prop.name})`; + } + return null; +} + +function getMapToJsonConverter(prop: Property): string { + if (helpers.isMap(prop.additionalProperties as Property)) { + // Handle nested maps recursively + const innerConverter = getPropertyToJsonValue(prop.additionalProperties as Property); + return `mapToJson(obj.${prop.name}, (v) => ${innerConverter})`; + } else if (!helpers.isPrimitive(prop.additionalProperties as Property)) { + const baseRef = getBaseRef(prop.additionalProperties as Property); + return `mapToJson(obj.${prop.name}, ${baseRef}.toJson)`; + } + return `obj.${prop.name}`; +} + + // TODO: can move this helper up to shared library? function isBuffer(property: Property | Parameter): boolean { return property.type === 'buffer' @@ -61,7 +138,10 @@ export function render() { ...helpers, isBuffer, toTypeScriptType, + getBaseRef, + getPropertyValue, + getPropertyToJsonValue } const output = ejs.render(tmpl, ctx) Host.outputString(output) -} +} \ No newline at end of file diff --git a/template/src/pdk.ts.ejs b/template/src/pdk.ts.ejs index f1f2a17..e336d7d 100644 --- a/template/src/pdk.ts.ejs +++ b/template/src/pdk.ts.ejs @@ -12,6 +12,26 @@ function cast(caster: (v: any) => any, v: any): any { return caster(v) } +function mapFromJson(obj: any, cast: (value: any) => T): Record { + const result: Record = {}; + + for (const [key, value] of Object.entries(obj)) { + result[key] = cast(value); + } + + return result; +} + +function mapToJson(obj: Record, cast: (value: T) => any): Record { + const result: Record = {}; + + for (const [key, value] of Object.entries(obj)) { + result[key] = cast(value); + } + + return result; +} + function dateToJson(v: Date): string { return v.toISOString() } @@ -48,31 +68,21 @@ export class <%- schema.name %> { return { ...obj, <% schema.properties.forEach(p => { -%> - <% let baseP = p.items ? p.items : p -%> - <% let baseRef = p.$ref ? p.$ref.name : (p.items && p.items.$ref ? p.items.$ref.name : null) -%> - <% if (isDateTime(baseP)) { -%> - <%- p.name -%>: cast(dateFromJson, obj.<%- p.name -%>), - <% } else if (isBuffer(baseP)) {-%> - <%- p.name -%>: cast(bufferFromJson, obj.<%- p.name -%>), - <% } else if (!isPrimitive(baseP)) {-%> - <%- p.name -%>: cast(<%- baseRef -%>.fromJson, obj.<%- p.name -%>), + <% let prop = getPropertyValue(p) -%> + <% if (prop) { -%> + <%- p.name -%>: <%- prop %>, <% } -%> <% }) -%> } } - static toJson(obj: <%- schema.name %>): any{ + static toJson(obj: <%- schema.name %>): any { return { ...obj, <% schema.properties.forEach(p => { -%> - <% let baseP = p.items ? p.items : p -%> - <% let baseRef = p.$ref ? p.$ref.name : (p.items && p.items.$ref ? p.items.$ref.name : null) -%> - <% if (isDateTime(baseP)) { -%> - <%- p.name -%>: cast(dateToJson, obj.<%- p.name -%>), - <% } else if (isBuffer(baseP)) {-%> - <%- p.name -%>: cast(bufferToJson, obj.<%- p.name -%>), - <% } else if (!isPrimitive(baseP)) {-%> - <%- p.name -%>: cast(<%- baseRef -%>.toJson, obj.<%- p.name -%>), + <% let prop = getPropertyToJsonValue(p) -%> + <% if (prop) { -%> + <%- p.name %>: <%- prop %>, <% } -%> <% }) -%> } diff --git a/tests/schemas/fruit.yaml b/tests/schemas/fruit.yaml index 27c17d6..69cbc16 100644 --- a/tests/schemas/fruit.yaml +++ b/tests/schemas/fruit.yaml @@ -1,4 +1,17 @@ exports: + mapExport: + description: This demonstrates how you can create an export that takes a map of + strings and returns a map of strings. + input: + type: object + additionalProperties: + type: string + description: A map of strings + output: + type: object + additionalProperties: + type: string + description: A map of strings topLevelBuffJSON: description: Top level json buffers input: @@ -85,6 +98,11 @@ imports: version: v1-draft components: schemas: + AMapSchema: + type: object + additionalProperties: + type: string + description: A map of strings WriteParams: properties: key: @@ -138,4 +156,17 @@ components: writeParams: "$ref": "#/components/schemas/WriteParams" nullable: true + anIntMap: + type: object + additionalProperties: + type: integer + format: int32 + description: A map of integers + anObjectMap: + type: object + additionalProperties: + $ref: "#/components/schemas/WriteParams" + refToMap: + "$ref": "#/components/schemas/AMapSchema" + description: A complex json object description: A complex json object From fb224b99543259d9304e4e27343f74e7996d4404 Mon Sep 17 00:00:00 2001 From: Muhammad Azeez Date: Tue, 22 Oct 2024 19:26:22 +0300 Subject: [PATCH 2/7] map schemas --- src/index.ts | 7 +++++++ template/src/pdk.ts.ejs | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/index.ts b/src/index.ts index 07b3b56..adac10e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -77,6 +77,8 @@ function getPropertyValue(prop: Property): string | null { return `cast(bufferFromJson, obj.${prop.name})`; } else if (helpers.isMap(prop)) { return getMapConverter(prop); + } else if (baseP.$ref?.additionalProperties) { + return getMapConverter((baseP.$ref as any) as Property); } else if (!helpers.isPrimitive(baseP)) { return `cast(${baseRef}.fromJson, obj.${prop.name})`; } @@ -106,6 +108,11 @@ function getPropertyToJsonValue(prop: Property): string | null { } else if (isBuffer(baseP)) { return `cast(bufferToJson, obj.${prop.name})`; } else if (helpers.isMap(prop)) { + return getMapToJsonConverter(prop); + } else if (baseP.$ref?.additionalProperties) { + let prop = (baseP.$ref as any) as Property + prop.name = baseP.name + return getMapToJsonConverter(prop); } else if (!helpers.isPrimitive(baseP)) { return `cast(${baseRef}.toJson, obj.${prop.name})`; diff --git a/template/src/pdk.ts.ejs b/template/src/pdk.ts.ejs index e336d7d..4bbffbf 100644 --- a/template/src/pdk.ts.ejs +++ b/template/src/pdk.ts.ejs @@ -106,6 +106,13 @@ export enum <%- schema.name %> { <% }) -%> } + <% } else if (isMap(schema)) { %> + +/** +* <%- formatCommentLine(schema.description) %> +*/ +export type <%- schema.name %> = <%- toTypeScriptType(schema) %> + <% } %> <% }) %> From 02fd6fdacba5153fd4965814027ef7de15b85434 Mon Sep 17 00:00:00 2001 From: Muhammad Azeez Date: Wed, 23 Oct 2024 14:37:47 +0300 Subject: [PATCH 3/7] aaaaa --- src/index.ts | 57 +++++++++++++++++++++------------------ template/src/index.ts.ejs | 11 +++++++- template/src/pdk.ts.ejs | 8 +++--- tests/schemas/fruit.yaml | 5 +++- 4 files changed, 49 insertions(+), 32 deletions(-) diff --git a/src/index.ts b/src/index.ts index adac10e..51cba54 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ import ejs from 'ejs' -import { helpers, getContext, Property, Parameter, } from "@dylibso/xtp-bindgen" +import { helpers, getContext, Property, Parameter, } from "@dylibso/xtp-bindgen" function toTypeScriptType(property: Property | Parameter): string { let tp @@ -46,7 +46,7 @@ function toTypeScriptType(property: Property | Parameter): string { } } - if (!tp) throw new Error("Cant convert property to typescript type: " + property.type) + if (!tp) throw new Error("Cant convert property to typescript type: " + JSON.stringify(property)) if (!property.nullable) return tp return `${tp} | null` } @@ -67,69 +67,72 @@ function getBaseRef(property: Property): string { return property.$ref?.name || '' } -function getPropertyValue(prop: Property): string | null { +function deserializeProperty(prop: Property): string | null { const baseP = (prop.items ? prop.items : prop) as Property; const baseRef = getBaseRef(prop); - + if (helpers.isDateTime(baseP)) { return `cast(dateFromJson, obj.${prop.name})`; } else if (isBuffer(baseP)) { return `cast(bufferFromJson, obj.${prop.name})`; } else if (helpers.isMap(prop)) { - return getMapConverter(prop); + return deserializeMapFromSource(`obj.${prop.name}`, prop); } else if (baseP.$ref?.additionalProperties) { - return getMapConverter((baseP.$ref as any) as Property); + return deserializeMapFromSource(`obj.${prop.name}`, (baseP.$ref as any) as Property); } else if (!helpers.isPrimitive(baseP)) { return `cast(${baseRef}.fromJson, obj.${prop.name})`; } return null; } -// Helper function to handle map conversion -function getMapConverter(prop: Property): string { +function deserializeMapFromSource(source: string, prop: Property): string { if (helpers.isMap(prop.additionalProperties as Property)) { // Handle nested maps recursively - const innerConverter = getPropertyValue(prop.additionalProperties as Property); - return `mapFromJson(obj.${prop.name}, (v) => ${innerConverter})`; - } else if (!helpers.isPrimitive(prop.additionalProperties as Property)) { - const baseRef = getBaseRef(prop.additionalProperties as Property); - return `mapFromJson(obj.${prop.name}, ${baseRef}.fromJson)`; + const innerConverter = deserializeProperty(prop.additionalProperties as Property); + return `mapFromJson(${source}, (v) => ${innerConverter})`; + } + + const baseP = (prop.additionalProperties?.items ? prop.additionalProperties.items : prop.additionalProperties) as Property; + const baseRef = getBaseRef(baseP); + + if (!helpers.isPrimitive(prop.additionalProperties as Property)) { + return `mapFromJson(${source}, ${baseRef}.fromJson)`; } - return `mapFromJson(obj.${prop.name}, (v: any) => v as ${toTypeScriptType(prop.additionalProperties as Property)})`; + return `mapFromJson(${source}, (v: any) => v as ${toTypeScriptType(baseP)})`; } -function getPropertyToJsonValue(prop: Property): string | null { +function serializeProperty(prop: Property): string | null { const baseP = (prop.items ? prop.items : prop) as Property; const baseRef = getBaseRef(prop); - + if (helpers.isDateTime(baseP)) { return `cast(dateToJson, obj.${prop.name})`; } else if (isBuffer(baseP)) { return `cast(bufferToJson, obj.${prop.name})`; } else if (helpers.isMap(prop)) { - return getMapToJsonConverter(prop); + return serializeMapFromSource(`obj.${prop.name}`, prop); } else if (baseP.$ref?.additionalProperties) { - let prop = (baseP.$ref as any) as Property + let prop = (baseP.$ref as any) as Property prop.name = baseP.name - return getMapToJsonConverter(prop); + return serializeMapFromSource(`obj.${prop.name}`,prop); } else if (!helpers.isPrimitive(baseP)) { return `cast(${baseRef}.toJson, obj.${prop.name})`; } return null; } -function getMapToJsonConverter(prop: Property): string { +function serializeMapFromSource(source: string, prop: Property): string { if (helpers.isMap(prop.additionalProperties as Property)) { // Handle nested maps recursively - const innerConverter = getPropertyToJsonValue(prop.additionalProperties as Property); - return `mapToJson(obj.${prop.name}, (v) => ${innerConverter})`; + const innerConverter = serializeProperty(prop.additionalProperties as Property); + return `mapToJson(${source}, (v) => ${innerConverter})`; } else if (!helpers.isPrimitive(prop.additionalProperties as Property)) { const baseRef = getBaseRef(prop.additionalProperties as Property); - return `mapToJson(obj.${prop.name}, ${baseRef}.toJson)`; + return `mapToJson(${source}, ${baseRef}.toJson)`; } - return `obj.${prop.name}`; + return source; } @@ -146,8 +149,10 @@ export function render() { isBuffer, toTypeScriptType, getBaseRef, - getPropertyValue, - getPropertyToJsonValue + serializeProperty, + serializeMapFromSource, + deserializeProperty, + deserializeMapFromSource } const output = ejs.render(tmpl, ctx) Host.outputString(output) diff --git a/template/src/index.ts.ejs b/template/src/index.ts.ejs index ee39b55..7be4b1d 100644 --- a/template/src/index.ts.ejs +++ b/template/src/index.ts.ejs @@ -4,6 +4,9 @@ import { <% Object.values(schema.schemas).forEach(schema => { -%> <%- schema.name %>, <% }) -%> + + mapFromJson, + mapToJson, } from './pdk' <% } %> @@ -14,7 +17,10 @@ export function <%- ex.name %>(): number { <% if (isBuffer(ex.input)) { -%> const input: <%- toTypeScriptType(ex.input) %> = Host.base64ToArrayBuffer(JSON.parse(Host.inputString())) <% } else if (isPrimitive(ex.input)) { -%> - const input: <%- toTypeScriptType(ex.input) %> = JSON.parse(Host.inputString()) + const input: <%- toTypeScriptType(ex.input) %> = JSON.parse(Host.inputString()) + <% } else if (isMap(ex.input)) { -%> + const untypedInput = JSON.parse(Host.inputString()) + const input = <%- deserializeMapFromSource('untypedInput', ex.input) %> <% } else { -%> const untypedInput = JSON.parse(Host.inputString()) const input = <%- toTypeScriptType(ex.input) %>.fromJson(untypedInput) @@ -44,6 +50,9 @@ export function <%- ex.name %>(): number { Host.outputString(JSON.stringify(Host.arrayBufferToBase64(output))) <% } else if (isPrimitive(ex.output)) { -%> Host.outputString(JSON.stringify(output)) + <% } else if (isMap(ex.input)) { -%> + const untypedOutput = <%- serializeMapFromSource('output', ex.input) %> + Host.outputString(JSON.stringify(untypedOutput)) <% } else { -%> const untypedOutput = <%- toTypeScriptType(ex.output) %>.toJson(output) Host.outputString(JSON.stringify(untypedOutput)) diff --git a/template/src/pdk.ts.ejs b/template/src/pdk.ts.ejs index 4bbffbf..e1e14b6 100644 --- a/template/src/pdk.ts.ejs +++ b/template/src/pdk.ts.ejs @@ -12,7 +12,7 @@ function cast(caster: (v: any) => any, v: any): any { return caster(v) } -function mapFromJson(obj: any, cast: (value: any) => T): Record { +export function mapFromJson(obj: any, cast: (value: any) => T): Record { const result: Record = {}; for (const [key, value] of Object.entries(obj)) { @@ -22,7 +22,7 @@ function mapFromJson(obj: any, cast: (value: any) => T): Record { return result; } -function mapToJson(obj: Record, cast: (value: T) => any): Record { +export function mapToJson(obj: Record, cast: (value: T) => any): Record { const result: Record = {}; for (const [key, value] of Object.entries(obj)) { @@ -68,7 +68,7 @@ export class <%- schema.name %> { return { ...obj, <% schema.properties.forEach(p => { -%> - <% let prop = getPropertyValue(p) -%> + <% let prop = deserializeProperty(p) -%> <% if (prop) { -%> <%- p.name -%>: <%- prop %>, <% } -%> @@ -80,7 +80,7 @@ export class <%- schema.name %> { return { ...obj, <% schema.properties.forEach(p => { -%> - <% let prop = getPropertyToJsonValue(p) -%> + <% let prop = serializeProperty(p) -%> <% if (prop) { -%> <%- p.name %>: <%- prop %>, <% } -%> diff --git a/tests/schemas/fruit.yaml b/tests/schemas/fruit.yaml index 69cbc16..743299a 100644 --- a/tests/schemas/fruit.yaml +++ b/tests/schemas/fruit.yaml @@ -5,13 +5,16 @@ exports: input: type: object additionalProperties: - type: string + items: + type: string description: A map of strings + contentType: application/json output: type: object additionalProperties: type: string description: A map of strings + contentType: application/json topLevelBuffJSON: description: Top level json buffers input: From 0f0038e98623f894d01f81a153aee122b4bc0fb3 Mon Sep 17 00:00:00 2001 From: Muhammad Azeez Date: Wed, 23 Oct 2024 16:02:49 +0300 Subject: [PATCH 4/7] handle imports and exports --- src/index.ts | 95 +++++++++++++++++++++++++++++---------- template/src/index.ts.ejs | 4 +- template/src/pdk.ts.ejs | 15 +++---- tests/schemas/fruit.yaml | 33 +++++++++----- 4 files changed, 102 insertions(+), 45 deletions(-) diff --git a/src/index.ts b/src/index.ts index 51cba54..4a8d7d8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -77,8 +77,6 @@ function deserializeProperty(prop: Property): string | null { return `cast(bufferFromJson, obj.${prop.name})`; } else if (helpers.isMap(prop)) { return deserializeMapFromSource(`obj.${prop.name}`, prop); - } else if (baseP.$ref?.additionalProperties) { - return deserializeMapFromSource(`obj.${prop.name}`, (baseP.$ref as any) as Property); } else if (!helpers.isPrimitive(baseP)) { return `cast(${baseRef}.fromJson, obj.${prop.name})`; } @@ -86,16 +84,41 @@ function deserializeProperty(prop: Property): string | null { } function deserializeMapFromSource(source: string, prop: Property): string { - if (helpers.isMap(prop.additionalProperties as Property)) { - // Handle nested maps recursively - const innerConverter = deserializeProperty(prop.additionalProperties as Property); - return `mapFromJson(${source}, (v) => ${innerConverter})`; - } - - const baseP = (prop.additionalProperties?.items ? prop.additionalProperties.items : prop.additionalProperties) as Property; + // Handle array of maps + if (prop.additionalProperties?.items) { + const baseP = prop.additionalProperties.items as Property; + const baseRef = getBaseRef(baseP); + + // If the array contains maps + if (helpers.isMap(baseP)) { + const valueP = baseP.additionalProperties as Property; + const valueRef = getBaseRef(valueP); + + // For non-primitive map values + if (!helpers.isPrimitive(valueP)) { + return `mapFromJson(${source}, (arr: any[]): ${baseRef}[] => arr.map((v: any) => + mapFromJson(v, ${valueRef}.fromJson)))`; + } + + // For primitive map values (like strings, numbers) + return `mapFromJson(${source}, (arr: any[]): ${baseRef}[] => arr.map((v: any) => + mapFromJson(v, (vv) => vv as ${toTypeScriptType(valueP)})))`; + } + + // Handle array of non-map objects + if (!helpers.isPrimitive(baseP)) { + return `mapFromJson(${source}, (arr: any[]): ${baseRef}[] => arr.map((v: any) => ${baseRef}.fromJson(v)))`; + } + + // Handle array of primitives + return `mapFromJson(${source}, (arr: any[]): ${toTypeScriptType(baseP)}[] => arr.map((v: any) => v as ${toTypeScriptType(baseP)}))`; + } + + // Handle base case (simple map) + const baseP = prop.additionalProperties as Property; const baseRef = getBaseRef(baseP); - - if (!helpers.isPrimitive(prop.additionalProperties as Property)) { + + if (!helpers.isPrimitive(baseP)) { return `mapFromJson(${source}, ${baseRef}.fromJson)`; } @@ -112,11 +135,6 @@ function serializeProperty(prop: Property): string | null { return `cast(bufferToJson, obj.${prop.name})`; } else if (helpers.isMap(prop)) { return serializeMapFromSource(`obj.${prop.name}`, prop); - } else if (baseP.$ref?.additionalProperties) { - let prop = (baseP.$ref as any) as Property - prop.name = baseP.name - - return serializeMapFromSource(`obj.${prop.name}`,prop); } else if (!helpers.isPrimitive(baseP)) { return `cast(${baseRef}.toJson, obj.${prop.name})`; } @@ -124,17 +142,46 @@ function serializeProperty(prop: Property): string | null { } function serializeMapFromSource(source: string, prop: Property): string { - if (helpers.isMap(prop.additionalProperties as Property)) { - // Handle nested maps recursively - const innerConverter = serializeProperty(prop.additionalProperties as Property); - return `mapToJson(${source}, (v) => ${innerConverter})`; - } else if (!helpers.isPrimitive(prop.additionalProperties as Property)) { - const baseRef = getBaseRef(prop.additionalProperties as Property); + // Handle array of maps + if (prop.additionalProperties?.items) { + const baseP = prop.additionalProperties.items as Property; + const baseRef = getBaseRef(baseP); + + // If the array contains maps + if (helpers.isMap(baseP)) { + const valueP = baseP.additionalProperties as Property; + const valueRef = getBaseRef(valueP); + + // For non-primitive map values + if (!helpers.isPrimitive(valueP)) { + return `mapToJson(${source}, (arr) => arr.map((v) => + mapToJson(v, ${valueRef}.toJson)))`; + } + + // For primitive map values (like strings, numbers) + return `mapToJson(${source}, (arr) => arr.map((v) => + mapToJson(v, (vv) => vv)))`; + } + + // Handle array of non-map objects + if (!helpers.isPrimitive(baseP)) { + return `mapToJson(${source}, (arr) => arr.map((v) => ${baseRef}.toJson(v)))`; + } + + // Handle array of primitives + return `mapToJson(${source}, (arr) => arr.map((v) => v))`; + } + + // Handle base case (simple map) + const baseP = prop.additionalProperties as Property; + const baseRef = getBaseRef(baseP); + + if (!helpers.isPrimitive(baseP)) { return `mapToJson(${source}, ${baseRef}.toJson)`; } - return source; -} + return `mapToJson(${source}, (v) => v)`; +} // TODO: can move this helper up to shared library? function isBuffer(property: Property | Parameter): boolean { diff --git a/template/src/index.ts.ejs b/template/src/index.ts.ejs index 7be4b1d..fe8e735 100644 --- a/template/src/index.ts.ejs +++ b/template/src/index.ts.ejs @@ -50,8 +50,8 @@ export function <%- ex.name %>(): number { Host.outputString(JSON.stringify(Host.arrayBufferToBase64(output))) <% } else if (isPrimitive(ex.output)) { -%> Host.outputString(JSON.stringify(output)) - <% } else if (isMap(ex.input)) { -%> - const untypedOutput = <%- serializeMapFromSource('output', ex.input) %> + <% } else if (isMap(ex.output)) { -%> + const untypedOutput = <%- serializeMapFromSource('output', ex.output) %> Host.outputString(JSON.stringify(untypedOutput)) <% } else { -%> const untypedOutput = <%- toTypeScriptType(ex.output) %>.toJson(output) diff --git a/template/src/pdk.ts.ejs b/template/src/pdk.ts.ejs index e1e14b6..3f35c9d 100644 --- a/template/src/pdk.ts.ejs +++ b/template/src/pdk.ts.ejs @@ -22,7 +22,7 @@ export function mapFromJson(obj: any, cast: (value: any) => T): Record(obj: Record, cast: (value: T) => any): Record { +export function mapToJson(obj: Record, cast: (value: T) => any): any { const result: Record = {}; for (const [key, value] of Object.entries(obj)) { @@ -106,13 +106,6 @@ export enum <%- schema.name %> { <% }) -%> } - <% } else if (isMap(schema)) { %> - -/** -* <%- formatCommentLine(schema.description) %> -*/ -export type <%- schema.name %> = <%- toTypeScriptType(schema) %> - <% } %> <% }) %> @@ -134,6 +127,9 @@ export function <%- imp.name %>(<%- imp.input ? `input: ${toTypeScriptType(imp.i <% if (isJsonEncoded(imp.input)) { -%> <% if (isPrimitive(imp.input)) { %> const mem = Memory.fromJsonObject(input as any) + <% } else if (isMap(imp.input)) { %> + const serialized = <%- serializeMapFromSource('input', imp.input) %> + const mem = Memory.fromJsonObject(serialized) <% } else { %> const casted = <%- toTypeScriptType(imp.input) %>.toJson(input) const mem = Memory.fromJsonObject(casted) @@ -155,6 +151,9 @@ export function <%- imp.name %>(<%- imp.input ? `input: ${toTypeScriptType(imp.i <% if (isJsonEncoded(imp.output)) { -%> <% if (isPrimitive(imp.output)) { -%> return Memory.find(ptr).readJsonObject(); + <% } else if (isMap(imp.output)) { -%> + const output = Memory.find(ptr).readJsonObject(); + return <%- deserializeMapFromSource('output', imp.output) %> <% } else { -%> const output = Memory.find(ptr).readJsonObject(); return <%- toTypeScriptType(imp.output) %>.fromJson(output) diff --git a/tests/schemas/fruit.yaml b/tests/schemas/fruit.yaml index 743299a..a074a01 100644 --- a/tests/schemas/fruit.yaml +++ b/tests/schemas/fruit.yaml @@ -5,14 +5,17 @@ exports: input: type: object additionalProperties: + type: array items: - type: string + $ref: "#/components/schemas/WriteParams" description: A map of strings contentType: application/json output: type: object additionalProperties: - type: string + type: array + items: + type: string description: A map of strings contentType: application/json topLevelBuffJSON: @@ -71,6 +74,23 @@ exports: contentType: application/json $ref: "#/components/schemas/ComplexObject" imports: + mapImport: + input: + type: object + additionalProperties: + type: array + items: + $ref: "#/components/schemas/WriteParams" + description: A map of strings + contentType: application/json + output: + type: object + additionalProperties: + type: array + items: + type: string + description: A map of strings + contentType: application/json eatAFruit: input: contentType: text/plain; charset=utf-8 @@ -94,18 +114,12 @@ imports: description: the raw byte values at key kv_write: description: kvwrite - contentType: application/json input: contentType: application/json "$ref": "#/components/schemas/WriteParams" version: v1-draft components: schemas: - AMapSchema: - type: object - additionalProperties: - type: string - description: A map of strings WriteParams: properties: key: @@ -169,7 +183,4 @@ components: type: object additionalProperties: $ref: "#/components/schemas/WriteParams" - refToMap: - "$ref": "#/components/schemas/AMapSchema" - description: A complex json object description: A complex json object From 65b3f292e5273e594438385acd932aec6c4d8cf6 Mon Sep 17 00:00:00 2001 From: Muhammad Azeez Date: Wed, 23 Oct 2024 18:13:06 +0300 Subject: [PATCH 5/7] check for null --- template/src/pdk.ts.ejs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/template/src/pdk.ts.ejs b/template/src/pdk.ts.ejs index 3f35c9d..bd6e0e1 100644 --- a/template/src/pdk.ts.ejs +++ b/template/src/pdk.ts.ejs @@ -14,6 +14,8 @@ function cast(caster: (v: any) => any, v: any): any { export function mapFromJson(obj: any, cast: (value: any) => T): Record { const result: Record = {}; + + if (!obj) return result for (const [key, value] of Object.entries(obj)) { result[key] = cast(value); @@ -25,6 +27,8 @@ export function mapFromJson(obj: any, cast: (value: any) => T): Record(obj: Record, cast: (value: T) => any): any { const result: Record = {}; + if (!obj) return result + for (const [key, value] of Object.entries(obj)) { result[key] = cast(value); } From 492b6974a1797604b8b75a52b84badf1516af76d Mon Sep 17 00:00:00 2001 From: Muhammad Azeez Date: Thu, 24 Oct 2024 20:44:18 +0300 Subject: [PATCH 6/7] address PR comments --- src/index.ts | 24 ++++++++++++------------ template/src/index.ts.ejs | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/index.ts b/src/index.ts index 4a8d7d8..1434ea4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ import ejs from 'ejs' -import { helpers, getContext, Property, Parameter, } from "@dylibso/xtp-bindgen" +import { helpers, getContext, Property, Parameter } from "@dylibso/xtp-bindgen" function toTypeScriptType(property: Property | Parameter): string { let tp @@ -67,7 +67,7 @@ function getBaseRef(property: Property): string { return property.$ref?.name || '' } -function deserializeProperty(prop: Property): string | null { +function propertyParsingSnippet(prop: Property): string | null { const baseP = (prop.items ? prop.items : prop) as Property; const baseRef = getBaseRef(prop); @@ -76,15 +76,15 @@ function deserializeProperty(prop: Property): string | null { } else if (isBuffer(baseP)) { return `cast(bufferFromJson, obj.${prop.name})`; } else if (helpers.isMap(prop)) { - return deserializeMapFromSource(`obj.${prop.name}`, prop); + return mapParsingSnippet(`obj.${prop.name}`, prop); } else if (!helpers.isPrimitive(baseP)) { return `cast(${baseRef}.fromJson, obj.${prop.name})`; } return null; } -function deserializeMapFromSource(source: string, prop: Property): string { - // Handle array of maps +function mapParsingSnippet(source: string, prop: Property): string { + // Handle map of arrays if (prop.additionalProperties?.items) { const baseP = prop.additionalProperties.items as Property; const baseRef = getBaseRef(baseP); @@ -125,7 +125,7 @@ function deserializeMapFromSource(source: string, prop: Property): string { return `mapFromJson(${source}, (v: any) => v as ${toTypeScriptType(baseP)})`; } -function serializeProperty(prop: Property): string | null { +function propertyToJsonSnippet(prop: Property): string | null { const baseP = (prop.items ? prop.items : prop) as Property; const baseRef = getBaseRef(prop); @@ -134,14 +134,14 @@ function serializeProperty(prop: Property): string | null { } else if (isBuffer(baseP)) { return `cast(bufferToJson, obj.${prop.name})`; } else if (helpers.isMap(prop)) { - return serializeMapFromSource(`obj.${prop.name}`, prop); + return mapToJsonSnippet(`obj.${prop.name}`, prop); } else if (!helpers.isPrimitive(baseP)) { return `cast(${baseRef}.toJson, obj.${prop.name})`; } return null; } -function serializeMapFromSource(source: string, prop: Property): string { +function mapToJsonSnippet(source: string, prop: Property): string { // Handle array of maps if (prop.additionalProperties?.items) { const baseP = prop.additionalProperties.items as Property; @@ -196,10 +196,10 @@ export function render() { isBuffer, toTypeScriptType, getBaseRef, - serializeProperty, - serializeMapFromSource, - deserializeProperty, - deserializeMapFromSource + propertyToJsonSnippet, + mapToJsonSnippet, + propertyParsingSnippet, + mapParsingSnippet } const output = ejs.render(tmpl, ctx) Host.outputString(output) diff --git a/template/src/index.ts.ejs b/template/src/index.ts.ejs index fe8e735..3c83830 100644 --- a/template/src/index.ts.ejs +++ b/template/src/index.ts.ejs @@ -20,7 +20,7 @@ export function <%- ex.name %>(): number { const input: <%- toTypeScriptType(ex.input) %> = JSON.parse(Host.inputString()) <% } else if (isMap(ex.input)) { -%> const untypedInput = JSON.parse(Host.inputString()) - const input = <%- deserializeMapFromSource('untypedInput', ex.input) %> + const input = <%- mapParsingSnippet('untypedInput', ex.input) %> <% } else { -%> const untypedInput = JSON.parse(Host.inputString()) const input = <%- toTypeScriptType(ex.input) %>.fromJson(untypedInput) @@ -51,7 +51,7 @@ export function <%- ex.name %>(): number { <% } else if (isPrimitive(ex.output)) { -%> Host.outputString(JSON.stringify(output)) <% } else if (isMap(ex.output)) { -%> - const untypedOutput = <%- serializeMapFromSource('output', ex.output) %> + const untypedOutput = <%- mapToJsonSnippet('output', ex.output) %> Host.outputString(JSON.stringify(untypedOutput)) <% } else { -%> const untypedOutput = <%- toTypeScriptType(ex.output) %>.toJson(output) From b09ed5cb6f43e2b9ba7a4c3906b463fdc115944b Mon Sep 17 00:00:00 2001 From: Muhammad Azeez Date: Thu, 24 Oct 2024 21:12:44 +0300 Subject: [PATCH 7/7] refactor --- src/index.ts | 134 ++++++++++++++++++++-------------------- template/src/pdk.ts.ejs | 8 +-- 2 files changed, 71 insertions(+), 71 deletions(-) diff --git a/src/index.ts b/src/index.ts index 1434ea4..1dd463a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -82,47 +82,52 @@ function propertyParsingSnippet(prop: Property): string | null { } return null; } - function mapParsingSnippet(source: string, prop: Property): string { - // Handle map of arrays - if (prop.additionalProperties?.items) { - const baseP = prop.additionalProperties.items as Property; - const baseRef = getBaseRef(baseP); - - // If the array contains maps - if (helpers.isMap(baseP)) { - const valueP = baseP.additionalProperties as Property; - const valueRef = getBaseRef(valueP); - - // For non-primitive map values - if (!helpers.isPrimitive(valueP)) { - return `mapFromJson(${source}, (arr: any[]): ${baseRef}[] => arr.map((v: any) => - mapFromJson(v, ${valueRef}.fromJson)))`; - } - - // For primitive map values (like strings, numbers) - return `mapFromJson(${source}, (arr: any[]): ${baseRef}[] => arr.map((v: any) => - mapFromJson(v, (vv) => vv as ${toTypeScriptType(valueP)})))`; - } - - // Handle array of non-map objects - if (!helpers.isPrimitive(baseP)) { - return `mapFromJson(${source}, (arr: any[]): ${baseRef}[] => arr.map((v: any) => ${baseRef}.fromJson(v)))`; - } + // Extract map's value type, handling both direct and array cases + const valueType = prop.additionalProperties?.items || prop.additionalProperties; + if (!valueType) return `mapFromJson(${source}, (v) => v)`; + + // Get type annotation for the resulting map values + const typeAnnotation = getTypeAnnotation(valueType as Property); + + // Generate converter based on value type + const valueConverter = buildParsingConverter(valueType as Property); - // Handle array of primitives - return `mapFromJson(${source}, (arr: any[]): ${toTypeScriptType(baseP)}[] => arr.map((v: any) => v as ${toTypeScriptType(baseP)}))`; + // Handle arrays + if (prop.additionalProperties?.items) { + return `mapFromJson(${source}, (arr: any[]): ${typeAnnotation}[] => arr.map((v: any) => ${valueConverter}(v)))`; } - // Handle base case (simple map) - const baseP = prop.additionalProperties as Property; - const baseRef = getBaseRef(baseP); + // Handle simple maps + return `mapFromJson(${source}, ${valueConverter})`; +} + +function buildParsingConverter(prop: Property): string { + // Handle nested maps + if (helpers.isMap(prop)) { + const innerValueType = prop.additionalProperties as Property; + const valueRef = getBaseRef(innerValueType); - if (!helpers.isPrimitive(baseP)) { - return `mapFromJson(${source}, ${baseRef}.fromJson)`; + if (!helpers.isPrimitive(innerValueType)) { + return `(v: any) => mapFromJson(v, ${valueRef}.fromJson)`; + } + return `(v: any) => mapFromJson(v, (vv) => vv as ${toTypeScriptType(innerValueType)})`; } - return `mapFromJson(${source}, (v: any) => v as ${toTypeScriptType(baseP)})`; + // Handle non-map types + const ref = getBaseRef(prop); + if (!helpers.isPrimitive(prop)) { + return `${ref}.fromJson`; + } + return `(v: any) => v as ${toTypeScriptType(prop)}`; +} + +function getTypeAnnotation(prop: Property): string { + if (helpers.isMap(prop)) { + const valueType = prop.additionalProperties as Property; + return getBaseRef(valueType) || toTypeScriptType(valueType); + } + return getBaseRef(prop) || toTypeScriptType(prop); } function propertyToJsonSnippet(prop: Property): string | null { @@ -142,45 +147,40 @@ function propertyToJsonSnippet(prop: Property): string | null { } function mapToJsonSnippet(source: string, prop: Property): string { - // Handle array of maps + // Extract map's value type, handling both direct and array cases + const valueType = prop.additionalProperties?.items || prop.additionalProperties; + if (!valueType) return `mapToJson(${source}, (v) => v)`; + + // Generate converter based on value type + const valueConverter = buildValueConverter(valueType as Property); + + // Handle arrays if (prop.additionalProperties?.items) { - const baseP = prop.additionalProperties.items as Property; - const baseRef = getBaseRef(baseP); - - // If the array contains maps - if (helpers.isMap(baseP)) { - const valueP = baseP.additionalProperties as Property; - const valueRef = getBaseRef(valueP); - - // For non-primitive map values - if (!helpers.isPrimitive(valueP)) { - return `mapToJson(${source}, (arr) => arr.map((v) => - mapToJson(v, ${valueRef}.toJson)))`; - } - - // For primitive map values (like strings, numbers) - return `mapToJson(${source}, (arr) => arr.map((v) => - mapToJson(v, (vv) => vv)))`; - } - - // Handle array of non-map objects - if (!helpers.isPrimitive(baseP)) { - return `mapToJson(${source}, (arr) => arr.map((v) => ${baseRef}.toJson(v)))`; - } - - // Handle array of primitives - return `mapToJson(${source}, (arr) => arr.map((v) => v))`; + return `mapToJson(${source}, (arr) => arr.map((v) => ${valueConverter}(v)))`; } - // Handle base case (simple map) - const baseP = prop.additionalProperties as Property; - const baseRef = getBaseRef(baseP); + // Handle simple maps + return `mapToJson(${source}, ${valueConverter})`; +} + +function buildValueConverter(prop: Property): string { + // Handle nested maps + if (helpers.isMap(prop)) { + const innerValueType = prop.additionalProperties as Property; + const valueRef = getBaseRef(innerValueType); - if (!helpers.isPrimitive(baseP)) { - return `mapToJson(${source}, ${baseRef}.toJson)`; + if (!helpers.isPrimitive(innerValueType)) { + return `(v) => mapToJson(v, ${valueRef}.toJson)`; + } + return `(v) => mapToJson(v, (vv) => vv)`; } - return `mapToJson(${source}, (v) => v)`; + // Handle non-map types + const ref = getBaseRef(prop); + if (!helpers.isPrimitive(prop)) { + return `${ref}.toJson`; + } + return `(v) => v`; } // TODO: can move this helper up to shared library? diff --git a/template/src/pdk.ts.ejs b/template/src/pdk.ts.ejs index bd6e0e1..8d0bb69 100644 --- a/template/src/pdk.ts.ejs +++ b/template/src/pdk.ts.ejs @@ -72,7 +72,7 @@ export class <%- schema.name %> { return { ...obj, <% schema.properties.forEach(p => { -%> - <% let prop = deserializeProperty(p) -%> + <% let prop = propertyParsingSnippet(p) -%> <% if (prop) { -%> <%- p.name -%>: <%- prop %>, <% } -%> @@ -84,7 +84,7 @@ export class <%- schema.name %> { return { ...obj, <% schema.properties.forEach(p => { -%> - <% let prop = serializeProperty(p) -%> + <% let prop = propertyToJsonSnippet(p) -%> <% if (prop) { -%> <%- p.name %>: <%- prop %>, <% } -%> @@ -132,7 +132,7 @@ export function <%- imp.name %>(<%- imp.input ? `input: ${toTypeScriptType(imp.i <% if (isPrimitive(imp.input)) { %> const mem = Memory.fromJsonObject(input as any) <% } else if (isMap(imp.input)) { %> - const serialized = <%- serializeMapFromSource('input', imp.input) %> + const serialized = <%- mapToJsonSnippet('input', imp.input) %> const mem = Memory.fromJsonObject(serialized) <% } else { %> const casted = <%- toTypeScriptType(imp.input) %>.toJson(input) @@ -157,7 +157,7 @@ export function <%- imp.name %>(<%- imp.input ? `input: ${toTypeScriptType(imp.i return Memory.find(ptr).readJsonObject(); <% } else if (isMap(imp.output)) { -%> const output = Memory.find(ptr).readJsonObject(); - return <%- deserializeMapFromSource('output', imp.output) %> + return <%- mapParsingSnippet('output', imp.output) %> <% } else { -%> const output = Memory.find(ptr).readJsonObject(); return <%- toTypeScriptType(imp.output) %>.fromJson(output)