From 304a11518ce356cff86336a445edcfd404b9f03b Mon Sep 17 00:00:00 2001 From: Benjamin Eckel Date: Thu, 1 Aug 2024 12:58:55 -0500 Subject: [PATCH 01/10] Upgrade to new v1 schema def Addresses part of https://github.com/dylibso/xtp/issues/585 --- src/normalizer.ts | 70 ++++++++++++----- src/parser.ts | 21 +++-- test-runner/.gitignore | 3 + tests/index.test.ts | 172 +++++++++++++++++++---------------------- 4 files changed, 139 insertions(+), 127 deletions(-) create mode 100644 test-runner/.gitignore diff --git a/src/normalizer.ts b/src/normalizer.ts index bb91b95..c4dafb3 100644 --- a/src/normalizer.ts +++ b/src/normalizer.ts @@ -8,6 +8,7 @@ export interface Property extends Omit { '$ref': Schema | null; nullable: boolean; items?: XtpItemType; + name: string; } export function isProperty(p: any): p is Property { @@ -16,6 +17,7 @@ export function isProperty(p: any): p is Property { export interface Schema extends Omit { properties: Property[]; + name: string; } export type SchemaMap = { @@ -81,8 +83,9 @@ function normalizeV0Schema(parsed: parser.V0Schema): XtpSchema { function parseSchemaRef(ref: string): string { const parts = ref.split('/') if (parts[0] !== '#') throw Error("Not a valid ref " + ref) - if (parts[1] !== 'schemas') throw Error("Not a valid ref " + ref) - return parts[2] + if (parts[1] !== 'components') throw Error("Not a valid ref " + ref) + if (parts[2] !== 'schemas') throw Error("Not a valid ref " + ref) + return parts[3] } function normalizeProp(p: Property | XtpItemType, s: Schema) { @@ -99,42 +102,63 @@ function normalizeV1Schema(parsed: parser.V1Schema): XtpSchema { const schemas: SchemaMap = {} // need to index all the schemas first - parsed.schemas?.forEach(s => { - schemas[s.name] = s as Schema - }) + for (const name in parsed.components?.schemas) { + const s = parsed.components.schemas[name] + const properties: Property[] = [] + for (const pName in s.properties) { + const p = s.properties[pName] as Property + p.name = pName + properties.push(p) + } + + // overwrite the name + // overwrite new properties shape + schemas[name] = { + ...s, + name, + properties, + } + } // denormalize all the properties in a second loop - parsed.schemas?.forEach(s => { + for (const name in schemas) { + const s = schemas[name] + s.properties?.forEach((p, idx) => { // link the property with a reference to the schema if it has a ref - if (p.$ref) { + // need to get the ref from the parsed (raw) property + const rawProp = parsed.components!.schemas![name].properties![p.name] + + if (rawProp.$ref) { normalizeProp( - schemas[s.name].properties[idx], - schemas[parseSchemaRef(p.$ref)] + schemas[name].properties[idx], + schemas[parseSchemaRef(rawProp.$ref)] ) } - if (p.items?.$ref) { + if (rawProp.items?.$ref) { normalizeProp( //@ts-ignore p.items!, - schemas[parseSchemaRef(p.items!.$ref)] + schemas[parseSchemaRef(rawProp.items!.$ref)] ) } - // add set nullable property from the required array - // TODO: consider supporting nullable instead of required - // @ts-ignore - p.nullable = !s.required?.includes(p.name) + // coerce to false by default + p.nullable = p.nullable || false }) - }) + } // denormalize all the exports - parsed.exports.forEach(ex => { + for (const name in parsed.exports) { + let ex = parsed.exports[name] + if (parser.isComplexExport(ex)) { // they have the same type // deref input and output const normEx = ex as Export + normEx.name = name + if (ex.input?.$ref) { normalizeProp( normEx.input!, @@ -166,16 +190,20 @@ function normalizeV1Schema(parsed: parser.V1Schema): XtpSchema { exports.push(normEx) } else if (parser.isSimpleExport(ex)) { // it's just a name - exports.push({ name: ex }) + exports.push({ name }) } else { throw new NormalizerError("Unable to match export to a simple or a complex export") } - }) + } // denormalize all the imports - parsed.imports?.forEach(im => { + for (const name in parsed.imports) { + const im = parsed.imports![name] + // they have the same type const normIm = im as Import + normIm.name = name + // deref input and output if (im.input?.$ref) { normalizeProp( @@ -206,7 +234,7 @@ function normalizeV1Schema(parsed: parser.V1Schema): XtpSchema { } imports.push(normIm) - }) + } return { version, diff --git a/src/parser.ts b/src/parser.ts index 843cff6..326258a 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -6,9 +6,11 @@ export interface V0Schema { export interface V1Schema { version: Version; - exports: Export[]; - imports?: Import[]; - schemas?: Schema[]; + exports: { [name: string]: Export }; + imports?: { [name: string]: Import }; + components?: { + schemas?: { [name: string]: Schema }; + } } type VUnknownSchema = V0Schema | V1Schema @@ -21,7 +23,7 @@ export type Export = SimpleExport | ComplexExport; export type Import = ComplexExport export function isComplexExport(exportItem: Export): exportItem is ComplexExport { - return typeof exportItem === 'object' && 'name' in exportItem; + return typeof exportItem === 'object' && 'description' in exportItem; } export function isSimpleExport(exportItem: Export): exportItem is SimpleExport { @@ -47,19 +49,17 @@ export interface CodeSample { export type MimeType = 'application/json' | 'text/plain; charset=UTF-8' export interface Schema { - name: string; description: string; type?: XtpType; enum?: string[]; contentType?: MimeType; - required?: string[]; - properties?: Property[]; + properties?: { [name: string]: Property }; } export type XtpType = 'integer' | 'string' | 'number' | 'boolean' | 'object' | 'array' | 'buffer'; export type XtpFormat = - 'int32' | 'int64' | 'float' | 'double' | 'date' | 'date-time' | 'byte'; + 'int32' | 'int64' | 'float' | 'double' | 'date-time' | 'byte'; export interface XtpItemType { type: XtpType; @@ -75,15 +75,12 @@ export interface XtpItemType { } export interface Property { - name: string; type: XtpType; items?: XtpItemType; format?: XtpFormat; contentType?: MimeType; description?: string; - minimum?: number; - maximum?: number; - default?: string; + nullable?: boolean; // NOTE: needs to be any to satisfy type satisfy // type system in normalizer diff --git a/test-runner/.gitignore b/test-runner/.gitignore new file mode 100644 index 0000000..ea1e773 --- /dev/null +++ b/test-runner/.gitignore @@ -0,0 +1,3 @@ +exampleplugin +bundle +bundle.zip diff --git a/tests/index.test.ts b/tests/index.test.ts index 4fa9bfd..1c82e90 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -1,121 +1,105 @@ import { parse } from '../src/index'; const testSchema = { - "exports": [ - { - "name": "voidFunc", + "version": "v1-draft", + "exports": { + "voidFunc": { "description": "This demonstrates how you can create an export with\nno inputs or outputs.\n" }, - { - "name": "primitiveTypeFunc", + "primitiveTypeFunc": { + "description": "This demonstrates how you can accept or return primtive types.\nThis function takes a utf8 string and returns a json encoded boolean\n", "input": { "type": "string", - "contentType": "text/plain; charset=UTF-8", - "description": "A string passed into plugin input" + "description": "A string passed into plugin input", + "contentType": "text/plain; charset=utf-8" }, "output": { "type": "boolean", - "contentType": "application/json", - "description": "A boolean encoded as json" + "description": "A boolean encoded as json", + "contentType": "application/json" }, "codeSamples": [ { "lang": "typescript", "label": "Test if a string has more than one character.\nCode samples show up in documentation and inline in docstrings\n", - "source": "function primitiveTpeFunc(input: string): boolean {\n return input.length > 1\n}\n" + "source": "function primitiveTypeFunc(input: string): boolean {\n return input.length > 1\n}\n" } - ], - "description": "This demonstrates how you can accept or return primtive types.\nThis function takes a utf8 string and returns a json encoded boolean\n" + ] }, - { - "name": "referenceTypeFunc", + "referenceTypeFunc": { + "description": "This demonstrates how you can accept or return references to schema types.\nAnd it shows how you can define an enum to be used as a property or input/output.\n", "input": { - "$ref": "#/schemas/Fruit" + "$ref": "#/components/schemas/Fruit" }, "output": { - "$ref": "#/schemas/ComplexObject" - }, - "description": "This demonstrates how you can accept or return references to schema types.\nAnd it shows how you can define an enum to be used as a property or input/output.\n" + "$ref": "#/components/schemas/ComplexObject" + } } - ], - "imports": [ - { - "name": "eatAFruit", + }, + "imports": { + "eatAFruit": { + "description": "This is a host function. Right now host functions can only be the type (i64) -> i64.\nWe will support more in the future. Much of the same rules as exports apply.\n", "input": { - "$ref": "#/schemas/Fruit" + "$ref": "#/components/schemas/Fruit" }, "output": { "type": "boolean", - "contentType": "application/json", - "description": "boolean encoded as json" - }, - "description": "This is a host function. Right now host functions can only be the type (i64) -> i64.\nWe will support more in the future. Much of the same rules as exports apply.\n" + "description": "boolean encoded as json", + "contentType": "application/json" + } } - ], - "schemas": [ - { - "enum": [ - "apple", - "orange", - "banana", - "strawberry" - ], - "name": "Fruit", - "description": "A set of available fruits you can consume" - }, - { - "enum": [ - "blinky", - "pinky", - "inky", - "clyde" - ], - "name": "GhostGang", - "description": "A set of all the enemies of pac-man" - }, - { - "name": "ComplexObject", - "required": [ - "ghost", - "aBoolean", - "aString", - "anInt" - ], - "properties": [ - { - "$ref": "#/schemas/GhostGang", - "name": "ghost", - "description": "I can override the description for the property here" - }, - { - "name": "aBoolean", - "type": "boolean", - "description": "A boolean prop" - }, - { - "name": "aString", - "type": "integer", - "format": "int32", - "description": "An int prop" - }, - { - "name": "anInt", - "type": "integer", - "format": "int32", - "description": "An int prop" - }, - { - "name": "anOptionalDate", - "type": "string", - "format": "date-time", - "description": "A datetime object, we will automatically serialize and deserialize\nthis for you.\n" + }, + "components": { + "schemas": { + "Fruit": { + "description": "A set of available fruits you can consume", + "enum": [ + "apple", + "orange", + "banana", + "strawberry" + ] + }, + "GhostGang": { + "description": "A set of all the enemies of pac-man", + "enum": [ + "blinky", + "pinky", + "inky", + "clyde" + ] + }, + "ComplexObject": { + "contentType": "application/json", + "description": "A complex json object", + "properties": { + "ghost": { + "$ref": "#/components/schemas/GhostGang", + "description": "I can override the description for the property here" + }, + "aBoolean": { + "type": "boolean", + "description": "A boolean prop" + }, + "aString": { + "type": "string", + "description": "An string prop" + }, + "anInt": { + "type": "integer", + "format": "int32", + "description": "An int prop" + }, + "anOptionalDate": { + "type": "string", + "format": "date-time", + "description": "A datetime object, we will automatically serialize and deserialize\nthis for you.", + "nullable": true + } } - ], - "contentType": "application/json", - "description": "A complex json object" + } } - ], - "version": "v1-draft" + } } @@ -129,22 +113,22 @@ test('parse-v1-document', () => { expect(doc.imports.length).toBe(1) const enumSchema1 = doc.schemas['Fruit'] - expect(enumSchema1.enum).toStrictEqual(testSchema.schemas[0].enum) + expect(enumSchema1.enum).toStrictEqual(testSchema.components.schemas['Fruit'].enum) const enumSchema2 = doc.schemas['GhostGang'] - expect(enumSchema2.enum).toStrictEqual(testSchema.schemas[1].enum) + expect(enumSchema2.enum).toStrictEqual(testSchema.components.schemas['GhostGang'].enum) const schema3 = doc.schemas['ComplexObject'] const properties = schema3.properties // proves we derferenced it - expect(properties[0].$ref?.enum).toStrictEqual(testSchema.schemas[1].enum) + expect(properties[0].$ref?.enum).toStrictEqual(testSchema.components.schemas['GhostGang'].enum) expect(properties[0].$ref?.name).toBe('GhostGang') expect(properties[0].name).toBe('ghost') const exp = doc.exports[2] // proves we derferenced it - expect(exp.input?.$ref?.enum).toStrictEqual(testSchema.schemas[0].enum) + expect(exp.input?.$ref?.enum).toStrictEqual(testSchema.components.schemas['Fruit'].enum) expect(exp.output?.$ref?.contentType).toBe('application/json') expect(exp.output?.contentType).toBe('application/json') }) From 66e4899e9be790f448ca9e6ed8e42b284cb6da8b Mon Sep 17 00:00:00 2001 From: Benjamin Eckel Date: Thu, 1 Aug 2024 13:23:57 -0500 Subject: [PATCH 02/10] Bump to rc1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index fb4d3a4..77e4afe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@dylibso/xtp-bindgen", - "version": "0.0.12", + "version": "1.0.0-rc1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@dylibso/xtp-bindgen", - "version": "0.0.12", + "version": "1.0.0-rc1", "license": "BSD-3-Clause", "devDependencies": { "@extism/js-pdk": "^1.0.1", diff --git a/package.json b/package.json index 398a080..891a14e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dylibso/xtp-bindgen", - "version": "0.0.12", + "version": "1.0.0-rc.1", "description": "XTP bindgen helper library", "main": "dist/index.js", "types": "dist/index.d.ts", From 13fdd8bf6a80a8fec90f2c49ba5cb3b48e51b5e1 Mon Sep 17 00:00:00 2001 From: Benjamin Eckel Date: Fri, 2 Aug 2024 11:45:19 -0500 Subject: [PATCH 03/10] Support Parameter type --- src/index.ts | 7 ++++--- src/normalizer.ts | 10 +++++----- src/parser.ts | 12 ++++++------ tests/index.test.ts | 5 +++-- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/index.ts b/src/index.ts index 531618c..dc84fdc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ import { isProperty, parseAndNormalizeJson, Property, + Parameter, XtpSchema, } from "./normalizer"; import { CodeSample } from "./parser"; @@ -54,7 +55,7 @@ function codeSamples(ex: Export, lang: string): CodeSample[] { } // template helpers -function hasComment(p: Property | Export | Import | null | undefined): boolean { +function hasComment(p: Parameter | Property | Export | Import | null | undefined): boolean { if (!p) return false; if (isProperty(p)) { @@ -89,12 +90,12 @@ function isDateTime(p: Property | null): boolean { return p.type === 'string' && p.format === 'date-time' } -function isJsonEncoded(p: Property | null): boolean { +function isJsonEncoded(p: Parameter | null): boolean { if (!p) return false return p.contentType === 'application/json' } -function isUtf8Encoded(p: Property | null): boolean { +function isUtf8Encoded(p: Parameter | null): boolean { if (!p) return false return p.contentType === 'text/plain; charset=UTF-8' } diff --git a/src/normalizer.ts b/src/normalizer.ts index c4dafb3..c3c135c 100644 --- a/src/normalizer.ts +++ b/src/normalizer.ts @@ -4,7 +4,7 @@ export interface XtpItemType extends Omit { '$ref': Schema | null; } -export interface Property extends Omit { +export interface Property extends Omit { '$ref': Schema | null; nullable: boolean; items?: XtpItemType; @@ -36,13 +36,14 @@ export type Version = 'v0' | 'v1'; export type XtpType = parser.XtpType export type XtpFormat = parser.XtpFormat export type MimeType = parser.MimeType +export type Parameter = parser.Parameter export interface Export { name: string; description?: string; codeSamples?: parser.CodeSample[]; - input?: Property; - output?: Property; + input?: Parameter; + output?: Parameter; } export function isExport(e: any): e is Export { @@ -88,10 +89,9 @@ function parseSchemaRef(ref: string): string { return parts[3] } -function normalizeProp(p: Property | XtpItemType, s: Schema) { +function normalizeProp(p: Parameter | Property | XtpItemType, s: Schema) { p.$ref = s p.type = s.type || 'string' // TODO: revisit string default, isn't type required? - p.contentType = p.contentType || s.contentType p.description = p.description || s.description } diff --git a/src/parser.ts b/src/parser.ts index 326258a..a54d95c 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -36,8 +36,8 @@ export interface ComplexExport { name: string; description?: string; codeSamples?: CodeSample[]; - input?: Property; - output?: Property; + input?: Parameter; + output?: Parameter; } export interface CodeSample { @@ -52,7 +52,6 @@ export interface Schema { description: string; type?: XtpType; enum?: string[]; - contentType?: MimeType; properties?: { [name: string]: Property }; } @@ -66,19 +65,20 @@ export interface XtpItemType { // NOTE: needs to be any to satisfy type satisfy // type system in normalizer "$ref"?: any; - - contentType?: string; description?: string; // we only support one nested item type for now // type: XtpType | XtpItemType; } +export interface Parameter extends Property { + contentType: MimeType; +} + export interface Property { type: XtpType; items?: XtpItemType; format?: XtpFormat; - contentType?: MimeType; description?: string; nullable?: boolean; diff --git a/tests/index.test.ts b/tests/index.test.ts index 1c82e90..a857477 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -29,9 +29,11 @@ const testSchema = { "referenceTypeFunc": { "description": "This demonstrates how you can accept or return references to schema types.\nAnd it shows how you can define an enum to be used as a property or input/output.\n", "input": { + "contentType": "application/json", "$ref": "#/components/schemas/Fruit" }, "output": { + "contentType": "application/json", "$ref": "#/components/schemas/ComplexObject" } } @@ -40,6 +42,7 @@ const testSchema = { "eatAFruit": { "description": "This is a host function. Right now host functions can only be the type (i64) -> i64.\nWe will support more in the future. Much of the same rules as exports apply.\n", "input": { + "contentType": "text/plain; charset=utf-8", "$ref": "#/components/schemas/Fruit" }, "output": { @@ -70,7 +73,6 @@ const testSchema = { ] }, "ComplexObject": { - "contentType": "application/json", "description": "A complex json object", "properties": { "ghost": { @@ -129,6 +131,5 @@ test('parse-v1-document', () => { const exp = doc.exports[2] // proves we derferenced it expect(exp.input?.$ref?.enum).toStrictEqual(testSchema.components.schemas['Fruit'].enum) - expect(exp.output?.$ref?.contentType).toBe('application/json') expect(exp.output?.contentType).toBe('application/json') }) From 3a86a27a7b1ffbf29f0bf32f90b18937eac193a3 Mon Sep 17 00:00:00 2001 From: Benjamin Eckel Date: Fri, 2 Aug 2024 11:45:48 -0500 Subject: [PATCH 04/10] add mime type --- src/parser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parser.ts b/src/parser.ts index a54d95c..1ca265f 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -46,7 +46,7 @@ export interface CodeSample { label?: string; } -export type MimeType = 'application/json' | 'text/plain; charset=UTF-8' +export type MimeType = 'application/json' | 'text/plain; charset=UTF-8' | 'application/x-binary' export interface Schema { description: string; From adb4f77c79543040965bee886bda1ca71ef37012 Mon Sep 17 00:00:00 2001 From: Benjamin Eckel Date: Fri, 2 Aug 2024 12:08:47 -0500 Subject: [PATCH 05/10] bump to rc2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 891a14e..21ee324 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dylibso/xtp-bindgen", - "version": "1.0.0-rc.1", + "version": "1.0.0-rc.2", "description": "XTP bindgen helper library", "main": "dist/index.js", "types": "dist/index.d.ts", From 413b4ef36bed1b25464d06c6971498b95f9a0f1e Mon Sep 17 00:00:00 2001 From: Benjamin Eckel Date: Fri, 2 Aug 2024 12:29:41 -0500 Subject: [PATCH 06/10] bump rc3 --- package-lock.json | 4 ++-- package.json | 2 +- src/index.ts | 2 +- src/normalizer.ts | 6 +++++- src/parser.ts | 2 +- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 77e4afe..8fa9d88 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@dylibso/xtp-bindgen", - "version": "1.0.0-rc1", + "version": "1.0.0-rc.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@dylibso/xtp-bindgen", - "version": "1.0.0-rc1", + "version": "1.0.0-rc.3", "license": "BSD-3-Clause", "devDependencies": { "@extism/js-pdk": "^1.0.1", diff --git a/package.json b/package.json index 21ee324..22100a2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dylibso/xtp-bindgen", - "version": "1.0.0-rc.2", + "version": "1.0.0-rc.3", "description": "XTP bindgen helper library", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/index.ts b/src/index.ts index dc84fdc..97b408e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -97,7 +97,7 @@ function isJsonEncoded(p: Parameter | null): boolean { function isUtf8Encoded(p: Parameter | null): boolean { if (!p) return false - return p.contentType === 'text/plain; charset=UTF-8' + return p.contentType === 'text/plain; charset=utf-8' } function isPrimitive(p: Property): boolean { diff --git a/src/normalizer.ts b/src/normalizer.ts index c3c135c..f75065d 100644 --- a/src/normalizer.ts +++ b/src/normalizer.ts @@ -4,7 +4,7 @@ export interface XtpItemType extends Omit { '$ref': Schema | null; } -export interface Property extends Omit { +export interface Property extends Omit { '$ref': Schema | null; nullable: boolean; items?: XtpItemType; @@ -93,6 +93,10 @@ function normalizeProp(p: Parameter | Property | XtpItemType, s: Schema) { p.$ref = s p.type = s.type || 'string' // TODO: revisit string default, isn't type required? p.description = p.description || s.description + // double ensure that content types are lowercase + if ('contentType' in p) { + p.contentType = p.contentType.toLowerCase() as MimeType + } } function normalizeV1Schema(parsed: parser.V1Schema): XtpSchema { diff --git a/src/parser.ts b/src/parser.ts index 1ca265f..58104cb 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -46,7 +46,7 @@ export interface CodeSample { label?: string; } -export type MimeType = 'application/json' | 'text/plain; charset=UTF-8' | 'application/x-binary' +export type MimeType = 'application/json' | 'text/plain; charset=utf-8' | 'application/x-binary' export interface Schema { description: string; From c27b88de6fc73dc286369be7d3b0c4217bac70de Mon Sep 17 00:00:00 2001 From: Benjamin Eckel Date: Mon, 5 Aug 2024 14:30:07 -0500 Subject: [PATCH 07/10] rc4 --- package-lock.json | 4 ++-- package.json | 2 +- src/index.ts | 17 +++++++++-------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8fa9d88..d194e82 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@dylibso/xtp-bindgen", - "version": "1.0.0-rc.3", + "version": "1.0.0-rc.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@dylibso/xtp-bindgen", - "version": "1.0.0-rc.3", + "version": "1.0.0-rc.4", "license": "BSD-3-Clause", "devDependencies": { "@extism/js-pdk": "^1.0.1", diff --git a/package.json b/package.json index 22100a2..491520b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dylibso/xtp-bindgen", - "version": "1.0.0-rc.3", + "version": "1.0.0-rc.4", "description": "XTP bindgen helper library", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/index.ts b/src/index.ts index 97b408e..72a1b90 100644 --- a/src/index.ts +++ b/src/index.ts @@ -84,12 +84,6 @@ function formatCommentBlock(s: string | null, prefix?: string) { return s.trimEnd().replace(/\n/g, `\n${prefix}`); } - -function isDateTime(p: Property | null): boolean { - if (!p) return false - return p.type === 'string' && p.format === 'date-time' -} - function isJsonEncoded(p: Parameter | null): boolean { if (!p) return false return p.contentType === 'application/json' @@ -100,9 +94,16 @@ function isUtf8Encoded(p: Parameter | null): boolean { return p.contentType === 'text/plain; charset=utf-8' } -function isPrimitive(p: Property): boolean { +function isPrimitive(p: Property | Parameter): boolean { if (!p.$ref) return true - return !!p.$ref.enum && !p.$ref.properties + // enums are currently primitive (strings) + // schemas with props are not (needs to be encoded) + return !!p.$ref.enum || !p.$ref.properties +} + +function isDateTime(p: Property | Parameter | null): boolean { + if (!p) return false + return p.type === 'string' && p.format === 'date-time' } export const helpers = { From 1988fbd9ace6ef585e58a970f47f755913d0ae51 Mon Sep 17 00:00:00 2001 From: Benjamin Eckel Date: Tue, 6 Aug 2024 13:28:57 -0500 Subject: [PATCH 08/10] XtpSchemaType --- src/normalizer.ts | 8 +++++++- src/parser.ts | 3 ++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/normalizer.ts b/src/normalizer.ts index f75065d..9416871 100644 --- a/src/normalizer.ts +++ b/src/normalizer.ts @@ -91,12 +91,18 @@ function parseSchemaRef(ref: string): string { function normalizeProp(p: Parameter | Property | XtpItemType, s: Schema) { p.$ref = s - p.type = s.type || 'string' // TODO: revisit string default, isn't type required? p.description = p.description || s.description // double ensure that content types are lowercase if ('contentType' in p) { p.contentType = p.contentType.toLowerCase() as MimeType } + if (!p.type) p.type = 'string' + if (s.type) { + // if it's not an object assume it's a string + if (s.type === 'object') { + p.type = 'object' + } + } } function normalizeV1Schema(parsed: parser.V1Schema): XtpSchema { diff --git a/src/parser.ts b/src/parser.ts index 58104cb..88b384f 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -50,11 +50,12 @@ export type MimeType = 'application/json' | 'text/plain; charset=utf-8' | 'appli export interface Schema { description: string; - type?: XtpType; + type?: XtpSchemaType; enum?: string[]; properties?: { [name: string]: Property }; } +export type XtpSchemaType = 'object' | 'enum' export type XtpType = 'integer' | 'string' | 'number' | 'boolean' | 'object' | 'array' | 'buffer'; export type XtpFormat = From db674501f8774ebb62d6cc3680cf487488e173c9 Mon Sep 17 00:00:00 2001 From: Benjamin Eckel Date: Tue, 6 Aug 2024 13:36:29 -0500 Subject: [PATCH 09/10] rc5 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index d194e82..a4d3af7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@dylibso/xtp-bindgen", - "version": "1.0.0-rc.4", + "version": "1.0.0-rc.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@dylibso/xtp-bindgen", - "version": "1.0.0-rc.4", + "version": "1.0.0-rc.5", "license": "BSD-3-Clause", "devDependencies": { "@extism/js-pdk": "^1.0.1", diff --git a/package.json b/package.json index 491520b..594afc0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dylibso/xtp-bindgen", - "version": "1.0.0-rc.4", + "version": "1.0.0-rc.5", "description": "XTP bindgen helper library", "main": "dist/index.js", "types": "dist/index.d.ts", From b16cd53b632adab924dc57e14c96534a1c6356c6 Mon Sep 17 00:00:00 2001 From: Benjamin Eckel Date: Thu, 8 Aug 2024 13:21:56 -0500 Subject: [PATCH 10/10] Update the readme --- README.md | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 68 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 52bc16a..2161483 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,74 @@ # xtp-bindgen -This is a typescript library for parsing an [XTP schema document](https://docs.xtp.dylibso.com/docs/host-usage/xtp-schema). -This is experimental, more coming soon. +XTP Bindgen is an open source framework to generate PDK bindings for [Extism](https://extism.org) plug-ins. +It's used by the [XTP Platform](https://www.getxtp.com/), but can be used outside of the platform to define +any Extism compatible plug-in system. + +## How Does It Work? + +[Extism](https://extism.org) has a very simple bytes-in / bytes-out interface. The host and the guest must agree +on the interface used (exports functions, imports functions, and types). The purpose of this project is +to create a canonical document and set of tools for defining this interface and generating bindings. That +document is the [XTP Schema](https://docs.xtp.dylibso.com/docs/concepts/xtp-schema). This is an IDL of our creation. +It is similar to [OpenAPI](https://www.openapis.org/), but is focused on defining plug-in interfaces, not HTTP interfaces. + +Once you define your interface as a schema, you can use one of the bindgens to generate code. We have some official +bindgens available for writing PDKs, but more will be availble soon for a variety of purposes. There may also be community +bindgens you can use. + +* [TypeScript](https://github.com/dylibso/xtp-typescript-bindgen) +* [Go](https://github.com/dylibso/xtp-go-bindgen) +* [C#](https://github.com/dylibso/xtp-csharp-bindgen) + +## How Do I Use A Bindgen? + +You can use the [XTP CLI](https://docs.xtp.dylibso.com/docs/cli/) to generate plug-ins. + +> *Note*: You don't need to authenticate to XTP to use the plugin generator + +Use the `plugin init` command to generate a plugin: ``` -npm install @dylibso/xtp-bindgen +xtp plugin init \ + --schema myschema.yaml \ + --template @dylibso/xtp-typescript-bindgen \ + --path ./myplugin \ + --feature-flags none ``` +You can point to a bindgen template on github or directly to a bindgen bundle. + + +## How Do I Write A Bindgen? + + +A bindgen is simply a zip file with the following attributes: + +* `plugin.wasm` an extism plugin to generate the code +* `config.yaml` a config file for the generator +* `template` a template folder of files and templates that the generator will recursively process + +For reference, Here is what is inside the typescript bindgen: + +``` +$ tree bundle +bundle +├── config.yaml +├── plugin.wasm +└── template + ├── esbuild.js + ├── package.json.ejs + ├── src + │   ├── index.d.ts.ejs + │   ├── index.ts.ejs + │   ├── main.ts.ejs + │   └── pdk.ts.ejs + ├── tsconfig.json + └── xtp.toml.ejs +``` + +The XTP CLI will download and unpack this template, it will load the plugin, and it will recursively walk +through the template and pass each file through the plugin to be rendered. Our official bindgens +currently use typescript and EJS to render the projects, but these are not mandatory. +It can be any Extism plug-in. +