From 5f5ea2aa2e28c268f87be1851438f201e6137d5e Mon Sep 17 00:00:00 2001 From: Muhammad Azeez Date: Wed, 23 Oct 2024 18:11:36 +0300 Subject: [PATCH 1/2] initial --- mock/main.go | 26 +++++++++++++++++- runner/src/index.ts | 50 +++++++++++++++++++++++++++++++++ schema.yaml | 67 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+), 1 deletion(-) diff --git a/mock/main.go b/mock/main.go index 8c329b1..7273e28 100644 --- a/mock/main.go +++ b/mock/main.go @@ -6,12 +6,36 @@ import ( "github.com/extism/go-pdk" ) +//go:export reflectMapHost +func reflectMapHost(kPtr uint64) uint64 { + kMem := pdk.FindMemory(kPtr) + k := string(kMem.ReadBytes()) + + var obj map[string]interface{} + err := pdk.JSONFrom(kPtr, &obj) + if err != nil { + pdk.SetError(err) + return 0 + } + + fmt.Println(k) + + kRet := pdk.AllocateString(k) + return kRet.Offset() +} + //go:export reflectJsonObjectHost func reflectJsonObjectHost(kPtr uint64) uint64 { kMem := pdk.FindMemory(kPtr) k := string(kMem.ReadBytes()) - // TODO should validate that we get json by trying to parse it + var obj map[string]interface{} + err := pdk.JSONFrom(kPtr, &obj) + if err != nil { + pdk.SetError(err) + return 0 + } + fmt.Println(k) kRet := pdk.AllocateString(k) diff --git a/runner/src/index.ts b/runner/src/index.ts index e9c56fc..d324ba5 100644 --- a/runner/src/index.ts +++ b/runner/src/index.ts @@ -6,6 +6,14 @@ const EmbeddedObject = { anEnumArray: ["option1", "option2", "option3"], anIntArray: [1, 2, 3], aDate: "2024-07-23T16:03:34.000Z", + anIntMap: { + 'int1': 101, + 'int2': 108, + }, + aDateMap: { + 'date1': "2024-07-23T16:03:34.000Z", + 'date2': "2024-07-23T16:03:34.000Z", + } }; const inputBufferString = "Hello 🌍 World!🌍"; @@ -30,11 +38,29 @@ const KitchenSink = { new TextEncoder().encode(inputBufferString).buffer, ), // aBuffer: "NzIsMTAxLDEwOCwxMDgsMTExLDMyLDI0MCwxNTksMTQwLDE0MSwzMiw4NywxMTEsMTE0LDEwOCwxMDAsMzMsMjQwLDE1OSwxNDAsMTQx", + aStringMap: { + 'str1': "Hello", + 'str2': "🌍", + 'str3': "World!", + }, + anObjectMap: { + 'obj1': EmbeddedObject, + 'obj2': EmbeddedObject, + }, + anArrayMap: { + 'array1': ["Hello", "🌍", "World!"], + 'array2': ["Goodbye", "🌍", "World!"], + }, + anEnumMap: { + 'enum1': "option1", + 'enum2': "option2", + } }; export function test() { Test.group("schema v1-draft encoding types", () => { let input = JSON.stringify(KitchenSink); + let output: typeof KitchenSink = Test.call("reflectJsonObject", input) .json(); @@ -53,6 +79,24 @@ export function test() { KitchenSink.anOptionalString, ); + let inputM = JSON.stringify({ "k1": [KitchenSink], "k2": [KitchenSink] }); + let outputM: Record = Test.call("reflectMap", inputM) + .json() + + for (const [_, v] of Object.entries(outputM)) { + const m = v[0]; + matchIdenticalTopLevel(m); + matchIdenticalEmbedded(m.anEmbeddedObject); + m.anEmbeddedObjectArray.forEach(matchIdenticalEmbedded); + matchDate(m); + } + + Test.assertEqual( + `reflectMap preserved optional field semantics`, + outputM["k1"][0].anOptionalString || null, + KitchenSink.anOptionalString + ) + let inputS = KitchenSink.aString; let outputS = Test.call("reflectUtf8String", inputS).text(); Test.assertEqual("reflectUtf8String preserved the string", outputS, inputS); @@ -138,6 +182,10 @@ const matchIdenticalTopLevel = (output: any) => { "anUntypedObject", "anEnum", "aBuffer", + "anObjectMap", + "anArrayMap", + "anEnumMap", + "aStringMap", ] as const; matchIdentical.forEach((k: typeof matchIdentical[number]) => { let key: keyof typeof KitchenSink = k; @@ -170,6 +218,8 @@ const matchIdenticalEmbedded = (embedded: any) => { "aStringArray", "anEnumArray", "anIntArray", + "anIntMap", + "aDateMap", ] as const; matchIdenticalEmbedded.forEach((k: typeof matchIdenticalEmbedded[number]) => { let key: keyof typeof EmbeddedObject = k; diff --git a/schema.yaml b/schema.yaml index 1fd9f6f..1c5dd41 100644 --- a/schema.yaml +++ b/schema.yaml @@ -1,5 +1,25 @@ version: v1-draft exports: + reflectMap: + description: | + This function takes a map and returns the same map. + codeSamples: + - lang: typescript + source: | + // pass this through the host function and return it back + return reflectMapHost(input) + input: + contentType: application/json + additionalProperties: + type: array + items: + $ref: '#/components/schemas/KitchenSinkObject' + output: + contentType: application/json + additionalProperties: + type: array + items: + $ref: '#/components/schemas/KitchenSinkObject' reflectJsonObject: description: | This function takes a KitchenSinkObject and returns a KitchenSinkObject. @@ -276,6 +296,24 @@ exports: input.aBuffer = input.aBuffer.replace(b"Hello", b"Goodbye"); return input imports: + reflectMapHost: + description: | + This function takes a Map and returns a Map. + It should come out hte same way it came in. It's the same as the export. + But the export should call this. + input: + contentType: application/json + additionalProperties: + type: array + items: + $ref: '#/components/schemas/KitchenSinkObject' + output: + contentType: application/json + additionalProperties: + type: array + items: + $ref: '#/components/schemas/KitchenSinkObject' + reflectJsonObjectHost: description: | This function takes a KitchenSinkObject and returns a KitchenSinkObject. @@ -356,6 +394,12 @@ components: description: a date type: string format: date-time + aDateMap: + description: a map of integers + type: object + additionalProperties: + type: string + format: date-time AStringEnum: description: A string enum type: string @@ -408,3 +452,26 @@ components: aBuffer: description: a byte buffer type: buffer + aStringMap: + description: a map of strings + type: object + additionalProperties: + type: string + anObjectMap: + description: a map of objects + type: object + additionalProperties: + $ref: "#/components/schemas/EmbeddedObject" + anArrayMap: + description: a map of arrays + type: object + additionalProperties: + type: array + items: + type: string + anEnumMap: + description: a map of enums + type: object + additionalProperties: + $ref: "#/components/schemas/AStringEnum" + From dd32c3acc70de4bbfe231b22af880d2e5d9b25b4 Mon Sep 17 00:00:00 2001 From: Muhammad Azeez Date: Wed, 23 Oct 2024 18:50:06 +0300 Subject: [PATCH 2/2] fix tests --- runner/src/index.ts | 54 ++++++++++++++++++++++++--------------------- schema.yaml | 2 +- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/runner/src/index.ts b/runner/src/index.ts index d324ba5..4b5f452 100644 --- a/runner/src/index.ts +++ b/runner/src/index.ts @@ -13,7 +13,11 @@ const EmbeddedObject = { aDateMap: { 'date1': "2024-07-23T16:03:34.000Z", 'date2': "2024-07-23T16:03:34.000Z", - } + }, + anEnumMap: { + 'enum1': "option1", + 'enum2': "option2", + }, }; const inputBufferString = "Hello 🌍 World!🌍"; @@ -48,13 +52,9 @@ const KitchenSink = { 'obj2': EmbeddedObject, }, anArrayMap: { - 'array1': ["Hello", "🌍", "World!"], - 'array2': ["Goodbye", "🌍", "World!"], + 'array1': [EmbeddedObject], + 'array2': [EmbeddedObject], }, - anEnumMap: { - 'enum1': "option1", - 'enum2': "option2", - } }; export function test() { @@ -64,14 +64,16 @@ export function test() { let output: typeof KitchenSink = Test.call("reflectJsonObject", input) .json(); - matchIdenticalTopLevel(output); - matchIdenticalEmbedded(output.anEmbeddedObject); - output.anEmbeddedObjectArray.forEach(matchIdenticalEmbedded); + matchIdenticalTopLevel("reflectJsonObject", output); + matchIdenticalEmbedded("reflectJsonObject", output.anEmbeddedObject); + output.anEmbeddedObjectArray.forEach(x => matchIdenticalEmbedded("reflectJsonObject:anEmbeddedObjectArray", x)); + Object.entries(output.anObjectMap).forEach(([i, v]) => matchIdenticalEmbedded(`reflectJsonObject:anObjectMap:${i}`, v)); + Object.entries(output.anArrayMap).forEach(([i, v]) => matchIdenticalEmbedded(`reflectJsonObject:anArrayMap:${i}`, v[0])); // dates and JSON encodings between languages are a little fuzzy. // so, rather than test stringified equality, we test the value of // the date in various forms. - matchDate(output); + matchDate("reflectJsonObject", output); Test.assertEqual( "reflectJsonObject preserved optional field semantics", @@ -83,12 +85,12 @@ export function test() { let outputM: Record = Test.call("reflectMap", inputM) .json() - for (const [_, v] of Object.entries(outputM)) { + for (const [k, v] of Object.entries(outputM)) { const m = v[0]; - matchIdenticalTopLevel(m); - matchIdenticalEmbedded(m.anEmbeddedObject); - m.anEmbeddedObjectArray.forEach(matchIdenticalEmbedded); - matchDate(m); + matchIdenticalTopLevel(`reflectMap:${k}`, m); + matchIdenticalEmbedded(`reflectMap:${k}`, m.anEmbeddedObject); + m.anEmbeddedObjectArray.forEach((x, i) => matchIdenticalEmbedded(`reflectMap:${k}:anEmbeddedObjectArray${i}`, x)); + matchDate(`reflectMap:${k}`, m); } Test.assertEqual( @@ -170,7 +172,7 @@ export function test() { return 0; } -const matchIdenticalTopLevel = (output: any) => { +const matchIdenticalTopLevel = (func: string, output: any) => { // determine top-level fields that should be identical // NOTE: anEmbeddedObject, anEmbeddedObjectArray, and aDate are intentionally omitted here const matchIdentical = [ @@ -182,9 +184,6 @@ const matchIdenticalTopLevel = (output: any) => { "anUntypedObject", "anEnum", "aBuffer", - "anObjectMap", - "anArrayMap", - "anEnumMap", "aStringMap", ] as const; matchIdentical.forEach((k: typeof matchIdentical[number]) => { @@ -201,16 +200,20 @@ const matchIdenticalTopLevel = (output: any) => { if (actual === undefined) { actual = null; } + } else if (key === "aStringMap") { + actual = JSON.stringify(actual); + expected = JSON.stringify(expected); } + Test.assertEqual( - `reflectJsonObject preserved identical value '${key}'`, + `${func} preserved identical value '${key}'`, actual, expected, ); }); }; -const matchIdenticalEmbedded = (embedded: any) => { +const matchIdenticalEmbedded = (func: string, embedded: any) => { // determine flat embedded items that should be identical // NOTE: aDate is intentionally omitted here const matchIdenticalEmbedded = [ @@ -220,23 +223,24 @@ const matchIdenticalEmbedded = (embedded: any) => { "anIntArray", "anIntMap", "aDateMap", + "anEnumMap", ] as const; matchIdenticalEmbedded.forEach((k: typeof matchIdenticalEmbedded[number]) => { let key: keyof typeof EmbeddedObject = k; Test.assertEqual( - `reflectJsonObject preserved identical value '${key}'`, + `${func} preserved identical value '${key}'`, JSON.stringify(embedded[k]), JSON.stringify(EmbeddedObject[key]), ); }); }; -const matchDate = (output: any) => { +const matchDate = (func: string, output: any) => { let expected = new Date(KitchenSink.aDate); let actual = new Date(output.aDate); Test.assertEqual( - `reflectJsonObject preserves semantics of 'date-time' formatted value`, + `${func} preserves semantics of 'date-time' formatted value`, actual.getTime(), expected.getTime(), ); diff --git a/schema.yaml b/schema.yaml index 1c5dd41..1ca8084 100644 --- a/schema.yaml +++ b/schema.yaml @@ -468,7 +468,7 @@ components: additionalProperties: type: array items: - type: string + $ref: "#/components/schemas/EmbeddedObject" anEnumMap: description: a map of enums type: object