diff --git a/packages/type-safe-api/scripts/type-safe-api/generators/generate-next.ts b/packages/type-safe-api/scripts/type-safe-api/generators/generate-next.ts index ec816b5a8..e1b08b180 100755 --- a/packages/type-safe-api/scripts/type-safe-api/generators/generate-next.ts +++ b/packages/type-safe-api/scripts/type-safe-api/generators/generate-next.ts @@ -111,6 +111,17 @@ const resolveIfRef = (spec: OpenAPIV3.Document, possibleRef: T | OpenAPIV3.Re return resolved as T; }; +/** + * Copy vendor extensions from the first parameter to the second + */ +const copyVendorExtensions = (object: object, vendorExtensions: { [key: string]: any }) => { + Object.entries(object ?? {}).forEach(([key, value]) => { + if (key.startsWith('x-')) { + vendorExtensions[key] = value; + } + }); +}; + /** * Clean up any generated code that already exists */ @@ -377,6 +388,15 @@ const mutateWithOpenapiSchemaProperties = (spec: OpenAPIV3.Document, model: pars (model as any).openapiType = schema.type; (model as any).isNotSchema = !!schema.not; + // Copy any schema vendor extensions + (model as any).vendorExtensions = {}; + copyVendorExtensions(schema, (model as any).vendorExtensions); + + // Use our added vendor extension + (model as any).isHoisted = !!(model as any).vendorExtensions?.['x-tsapi-hoisted']; + + mutateModelWithAdditionalTypes(model); + visited.add(model); // Also apply to array items recursively @@ -397,6 +417,15 @@ const mutateWithOpenapiSchemaProperties = (spec: OpenAPIV3.Document, model: pars const subSchema = resolveIfRef(spec, schema.properties![property.name]); mutateWithOpenapiSchemaProperties(spec, property, subSchema, visited); }); + + if (COMPOSED_SCHEMA_TYPES.has(model.export)) { + model.properties.forEach((property, i) => { + const subSchema = resolveIfRef(spec, schema[_camelCase(model.export)]?.[i]); + if (subSchema) { + mutateWithOpenapiSchemaProperties(spec, property, subSchema, visited); + } + }); + } }; interface SubSchema { @@ -451,7 +480,10 @@ const hoistInlineObjectSubSchemas = (nameParts: string[], schema: OpenAPIV3.Sche const ref = { $ref, name, - schema: structuredClone(s.schema), + schema: structuredClone({ + ...s.schema, + "x-tsapi-hoisted": true, + }), }; // Replace each subschema with a ref in the spec @@ -469,7 +501,7 @@ const buildData = (inSpec: OpenAPIV3.Document, metadata: any) => { // In order for the new generator not to be breaking, we apply the same logic here, however this can be removed // in future since we have control to avoid the duplicate handlers while allowing an operation to be part of // multiple "services". - const spec = JSON.parse(JSON.stringify(inSpec, (key, value) => { + let spec = JSON.parse(JSON.stringify(inSpec, (key, value) => { // Keep only the first tag where we find a tag if (key === "tags" && value && value.length > 0 && typeof value[0] === "string") { return [value[0]]; @@ -516,6 +548,27 @@ const buildData = (inSpec: OpenAPIV3.Document, metadata: any) => { } }); + // "Inline" any refs to non objects/enums + const inlinedRefs: Set = new Set(); + spec = JSON.parse(JSON.stringify(spec, (k, v) => { + if (v && typeof v === "object" && v.$ref) { + const resolved = resolveRef(spec, v.$ref); + if (resolved && resolved.type && resolved.type !== "object" && !(resolved.type === "string" && resolved.enum)) { + inlinedRefs.add(v.$ref); + return resolved; + } + } + return v; + })); + + // Delete the non object schemas that were inlined + [...inlinedRefs].forEach(ref => { + const parts = splitRef(ref); + if (parts.length === 3 && parts[0] === "components" && parts[1] === "schemas") { + delete spec.components!.schemas![parts[2]]; + } + }); + // Start with the data from https://github.com/webpro/parse-openapi which extracts most of what we need const data = { ...parseOpenapi.parse(spec), metadata }; @@ -534,12 +587,8 @@ const buildData = (inSpec: OpenAPIV3.Document, metadata: any) => { const specOp = (spec as any)?.paths?.[op.path]?.[op.method.toLowerCase()] as OpenAPIV3.OperationObject | undefined; // Add vendor extensions - Object.entries(specOp ?? {}).forEach(([key, value]) => { - if (key.startsWith('x-')) { - (op as any).vendorExtensions = (op as any).vendorExtensions ?? {}; - (op as any).vendorExtensions[key] = value; - } - }); + (op as any).vendorExtensions = (op as any).vendorExtensions ?? {}; + copyVendorExtensions(specOp ?? {}, (op as any).vendorExtensions); if (specOp) { // parseOpenapi has a method to retrieve the operation responses, but later filters to only @@ -670,7 +719,9 @@ const buildData = (inSpec: OpenAPIV3.Document, metadata: any) => { const matchingSpecModel = spec?.components?.schemas?.[model.name] if (matchingSpecModel) { - const specModel = isRef(matchingSpecModel) ? resolveRef(spec, matchingSpecModel.$ref) as OpenAPIV3.SchemaObject : matchingSpecModel; + const specModel = resolveIfRef(spec, matchingSpecModel); + + mutateWithOpenapiSchemaProperties(spec, model, specModel); // Add unique imports (model as any).uniqueImports = _orderBy(_uniq([ @@ -713,11 +764,7 @@ const buildData = (inSpec: OpenAPIV3.Document, metadata: any) => { // Add top level vendor extensions const vendorExtensions: { [key: string]: any } = {}; - Object.entries(spec ?? {}).forEach(([key, value]) => { - if (key.startsWith('x-')) { - vendorExtensions[key] = value; - } - }); + copyVendorExtensions(spec ?? {}, vendorExtensions); return { ...data, diff --git a/packages/type-safe-api/test/scripts/generators/__snapshots__/typescript.test.ts.snap b/packages/type-safe-api/test/scripts/generators/__snapshots__/typescript.test.ts.snap index cc27dd297..8627f6d84 100644 --- a/packages/type-safe-api/test/scripts/generators/__snapshots__/typescript.test.ts.snap +++ b/packages/type-safe-api/test/scripts/generators/__snapshots__/typescript.test.ts.snap @@ -18,8 +18,7 @@ src/models/index.ts src/models/model-utils.ts src/models/Template.ts src/models/TemplateBase.ts -src/models/TemplateBody.ts -src/models/TemplateID.ts", +src/models/TemplateBody.ts", "src/apis/DefaultApi.ts": "/* tslint:disable */ /* eslint-disable */ /** @@ -90,9 +89,6 @@ import { TemplateBody, TemplateBodyFromJSON, TemplateBodyToJSON, - TemplateID, - TemplateIDFromJSON, - TemplateIDToJSON, } from '../../models'; // Import request parameter interfaces import { @@ -850,13 +846,6 @@ export function TemplateToJSON(value?: Template | null): any { * Do not edit the class manually. */ import { exists, mapValues } from './model-utils'; -import type { TemplateID } from './TemplateID'; -import { - TemplateIDFromJSON, - TemplateIDFromJSONTyped, - TemplateIDToJSON, - instanceOfTemplateID, -} from './TemplateID'; /** * Represents the base properties of a template. @@ -866,11 +855,11 @@ import { */ export interface TemplateBase { /** - * - * @type {TemplateID} + * The unique identifier for a template. + * @type {string} * @memberof TemplateBase */ - id: TemplateID; + id: string; } @@ -893,7 +882,7 @@ export function TemplateBaseFromJSONTyped(json: any, ignoreDiscriminator: boolea } return { - 'id': TemplateIDFromJSON(json['id']), + 'id': json['id'], }; } @@ -906,7 +895,7 @@ export function TemplateBaseToJSON(value?: TemplateBase | null): any { } return { - 'id': TemplateIDToJSON(value.id), + 'id': value.id, }; } @@ -924,13 +913,6 @@ export function TemplateBaseToJSON(value?: TemplateBase | null): any { * Do not edit the class manually. */ import { exists, mapValues } from './model-utils'; -import type { TemplateID } from './TemplateID'; -import { - TemplateIDFromJSON, - TemplateIDFromJSONTyped, - TemplateIDToJSON, - instanceOfTemplateID, -} from './TemplateID'; /** * Represents the body of a template. @@ -940,11 +922,11 @@ import { */ export interface TemplateBody { /** - * - * @type {TemplateID} + * The unique identifier for a template. + * @type {string} * @memberof TemplateBody */ - parent_id?: TemplateID; + parent_id?: string; /** * A boolean value. * @type {boolean} @@ -972,7 +954,7 @@ export function TemplateBodyFromJSONTyped(json: any, ignoreDiscriminator: boolea } return { - 'parent_id': !exists(json, 'parent_id') ? undefined : TemplateIDFromJSON(json['parent_id']), + 'parent_id': !exists(json, 'parent_id') ? undefined : json['parent_id'], 'boolean': !exists(json, 'boolean') ? undefined : json['boolean'], }; } @@ -986,62 +968,17 @@ export function TemplateBodyToJSON(value?: TemplateBody | null): any { } return { - 'parent_id': TemplateIDToJSON(value.parent_id), + 'parent_id': value.parent_id, 'boolean': value.boolean, }; } -", - "src/models/TemplateID.ts": "/* tslint:disable */ -/* eslint-disable */ -/** - * My API - * See https://github.com/aws/aws-pdk/issues/841 - * - * The version of the OpenAPI document: 1.0.0 - * - * - * NOTE: This class is auto generated. - * Do not edit the class manually. - */ -import { exists, mapValues } from './model-utils'; - -/** - * The unique identifier for a template. - * @export - * @interface TemplateID - */ -export interface TemplateID { -} - - -/** - * Check if a given object implements the TemplateID interface. - */ -export function instanceOfTemplateID(value: object): boolean { - let isInstance = true; - return isInstance; -} - -export function TemplateIDFromJSON(json: any): TemplateID { - return TemplateIDFromJSONTyped(json, false); -} - -export function TemplateIDFromJSONTyped(json: any, ignoreDiscriminator: boolean): TemplateID { - return json; -} - -export function TemplateIDToJSON(value?: TemplateID | null): any { - return value; -} - ", "src/models/index.ts": "/* tslint:disable */ /* eslint-disable */ export * from './Template'; export * from './TemplateBase'; export * from './TemplateBody'; -export * from './TemplateID'; ", "src/models/model-utils.ts": "/* tslint:disable */ /* eslint-disable */ @@ -6872,7 +6809,6 @@ src/apis/DefaultApi.ts src/apis/index.ts src/models/index.ts src/models/model-utils.ts -src/models/HelloId.ts src/models/SayHelloResponseContent.ts src/models/ServiceUnavailableErrorResponseContent.ts", "src/apis/DefaultApi.ts": "/* tslint:disable */ @@ -6950,9 +6886,6 @@ export class DefaultApi extends runtime.BaseAPI { ", "src/apis/DefaultApi/OperationConfig.ts": "// Import models import { - HelloId, - HelloIdFromJSON, - HelloIdToJSON, SayHelloResponseContent, SayHelloResponseContentFromJSON, SayHelloResponseContentToJSON, @@ -7644,50 +7577,6 @@ export const buildTryCatchInterceptor = ( * Path, Query and Header parameters for SayHello */ export interface SayHelloRequestParameters { - readonly id?: HelloId; + readonly id?: string; } /** @@ -11906,7 +11780,7 @@ export const sayHelloHandler = ( try { requestParameters = { - id: coerceParameter("id", "HelloId", false || false || false, rawSingleValueParameters, rawMultiValueParameters, false) as HelloId | undefined, + id: coerceParameter("id", "string", false || false || false, rawSingleValueParameters, rawMultiValueParameters, false) as string | undefined, }; } catch (e: any) { @@ -12301,50 +12175,6 @@ export const buildTryCatchInterceptor =