diff --git a/docs/6.x/node.md b/docs/6.x/node.md index 54394d740..9a5909aaa 100644 --- a/docs/6.x/node.md +++ b/docs/6.x/node.md @@ -122,6 +122,16 @@ Resultant diff with correctly-typed `file` property: + file?: Blob; ``` -Any [Schema Object](https://spec.openapis.org/oas/latest.html#schema-object) present in your schema will be run through this formatter (even remote ones!). Also be sure to check the `metadata` parameter for additional context that may be helpful. +#### transform / postTransform metadata -There are many other uses for this besides checking `format`. Because this must return a **string** you can produce any arbitrary TypeScript code you’d like (even your own custom types). +Any [Schema Object](https://spec.openapis.org/oas/latest.html#schema-object) present in your schema will be run through `transform`, prior to its conversion to a TypeScript AST node, and `postTransform` after its conversion, including remote schemas! + +The `metadata` parameter present on both `transform` and `postTransform` has additional context that may be helpful. + +| Property | Description | +|-|-| +| `metadata.path` | A [`$ref`](https://json-schema.org/understanding-json-schema/structuring#dollarref) URI string, pointing to the current schema object | +| `metadata.schema` | The schema object being transformed (only present for `postTransform`) | +| `metadata.ctx` | The GlobalContext object, containing + +There are many other uses for this besides checking `format`. Because `tranform` may return a **string** you can produce any arbitrary TypeScript code you’d like (even your own custom types). diff --git a/packages/openapi-typescript/src/transform/components-object.ts b/packages/openapi-typescript/src/transform/components-object.ts index 47a5a8f71..e5c100d26 100644 --- a/packages/openapi-typescript/src/transform/components-object.ts +++ b/packages/openapi-typescript/src/transform/components-object.ts @@ -34,16 +34,18 @@ export default function transformComponentsObject(componentsObject: ComponentsOb const items: ts.TypeElement[] = []; if (componentsObject[key]) { - for (const [name, item] of getEntries(componentsObject[key], ctx)) { + for (const [name, item] of getEntries(componentsObject[key], ctx)) { let subType = transformers[key](item, { path: createRef(["components", key, name]), + schema: item, ctx, }); let hasQuestionToken = false; if (ctx.transform) { - const result = ctx.transform(item as SchemaObject, { + const result = ctx.transform(item, { path: createRef(["components", key, name]), + schema: item, ctx, }); if (result) { diff --git a/packages/openapi-typescript/src/transform/index.ts b/packages/openapi-typescript/src/transform/index.ts index 9ad0b9ae3..0c4a7c823 100644 --- a/packages/openapi-typescript/src/transform/index.ts +++ b/packages/openapi-typescript/src/transform/index.ts @@ -14,7 +14,7 @@ const transformers: Record transformSchemaObject(node, { path: createRef(["$defs"]), ctx: options }), + $defs: (node, options) => transformSchemaObject(node, { path: createRef(["$defs"]), ctx: options, schema: node }), }; export default function transformSchema(schema: OpenAPI3, ctx: GlobalContext) { diff --git a/packages/openapi-typescript/src/types.ts b/packages/openapi-typescript/src/types.ts index 1d33dd6bb..23e23bf99 100644 --- a/packages/openapi-typescript/src/types.ts +++ b/packages/openapi-typescript/src/types.ts @@ -707,5 +707,6 @@ export type $defs = Record; /** generic options for most internal transform* functions */ export interface TransformNodeOptions { path?: string; + schema?: SchemaObject | ReferenceObject; ctx: GlobalContext; } diff --git a/packages/openapi-typescript/test/node-api.test.ts b/packages/openapi-typescript/test/node-api.test.ts index b0ff0c8f3..9f1a04438 100644 --- a/packages/openapi-typescript/test/node-api.test.ts +++ b/packages/openapi-typescript/test/node-api.test.ts @@ -1,7 +1,7 @@ import { fileURLToPath } from "node:url"; import ts from "typescript"; import openapiTS, { COMMENT_HEADER, astToString } from "../src/index.js"; -import type { OpenAPITSOptions } from "../src/types.js"; +import type { OpenAPITSOptions, ReferenceObject, SchemaObject } from "../src/types.js"; import type { TestCase } from "./test-helpers.js"; const EXAMPLES_DIR = new URL("../examples/", import.meta.url); @@ -537,6 +537,11 @@ export type operations = Record;`, components: { schemas: { Date: { type: "string", format: "date-time" }, + Set: { + ["x-string-enum-to-set"]: true, + type: "string", + enum: ["low", "medium", "high"], + }, }, }, }, @@ -546,6 +551,8 @@ export interface components { schemas: { /** Format: date-time */ Date: DateOrTime; + /** @enum {string} */ + Set: Set<"low" | "medium" | "high">; }; responses: never; parameters: never; @@ -563,7 +570,40 @@ export type operations = Record;`, * then use the `typescript` parser and it will tell you the desired * AST */ - return ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("DateOrTime")); + return ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier("DateOrTime") + ); + } + + // Previously, in order to access the schema in postTransform, + // you could resolve the schema using the path. + // Now, the schema is made available directly on the options. + // const schema = options.path + // ? options.ctx.resolve(options.path) + // : undefined; + const schema = options.schema; + + if ( + schema && + !("$ref" in schema) && + Object.hasOwn(schema, "x-string-enum-to-set") && + schema.type === "string" && + schema.enum?.every((enumMember) => { + return typeof enumMember === "string"; + }) + ) { + return ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier("Set"), + [ + ts.factory.createUnionTypeNode( + schema.enum.map((value) => { + return ts.factory.createLiteralTypeNode( + ts.factory.createStringLiteral(value) + ); + }) + ), + ] + ); } }, },