diff --git a/scripts/testCodeGen.ts b/scripts/testCodeGen.ts index 7554c5e9..83443f1a 100644 --- a/scripts/testCodeGen.ts +++ b/scripts/testCodeGen.ts @@ -3,6 +3,7 @@ import * as Writer from "./writer"; const main = () => { Writer.generateTypedefCodeOnly("test/api.test.domain/index.yml", "test/code/typedef-only/api.test.domain.ts", true); Writer.generateTypedefCodeOnly("test/infer.domain/index.yml", "test/code/typedef-only/infer.domain.ts", false); + Writer.generateTypedefCodeOnly("test/json.properties/index.yml", "test/code/typedef-only/json.properties.ts", false); Writer.generateTemplateCodeOnly("test/api.test.domain/index.yml", "test/code/template-only/api.test.domain.ts", true, { sync: false }); Writer.generateTemplateCodeOnly("test/api.test.domain/index.yml", "test/code/template-only/sync-api.test.domain.ts", true, { sync: true }); diff --git a/src/internal/OpenApiTools/TypeNodeContext.ts b/src/internal/OpenApiTools/TypeNodeContext.ts index c595fe30..4516456e 100644 --- a/src/internal/OpenApiTools/TypeNodeContext.ts +++ b/src/internal/OpenApiTools/TypeNodeContext.ts @@ -1,4 +1,5 @@ import * as Path from "path"; +import DotProp from "dot-prop"; import ts from "typescript"; @@ -8,6 +9,8 @@ import * as TypeScriptCodeGenerator from "../TsGenerator"; import * as ConverterContext from "./ConverterContext"; import * as ToTypeNode from "./toTypeNode"; import type * as Walker from "./Walker"; +import * as Guard from "./Guard"; +import * as Reference from "./components/Reference"; export interface ReferencePathSet { pathArray: string[]; @@ -93,6 +96,27 @@ export const create = ( const { pathArray, base } = generatePath(entryPoint, currentPoint, referencePath); return calculateReferencePath(store, base, pathArray, converterContext); }; + const findSchemaByPathArray = ( + pathArray: string[], + remainPathArray: string[] = [], + ): OpenApi.Schema | OpenApi.Reference | OpenApi.JSONSchemaDefinition => { + const schema = DotProp.get(rootSchema, pathArray.join(".")); + if (!schema) { + return findSchemaByPathArray(pathArray.slice(0, pathArray.length - 1), [pathArray[pathArray.length - 1], ...remainPathArray]); + } + if (Guard.isReference(schema)) { + const ref = Reference.generate(entryPoint, entryPoint, schema); + return findSchemaByPathArray(ref.path.split("/"), remainPathArray); + } + if (remainPathArray.length) { + const moreNestSchema = DotProp.get(schema, remainPathArray.join(".")); + if (!moreNestSchema) { + throw new Error("Not found"); + } + return moreNestSchema; + } + return schema; + }; const setReferenceHandler: ToTypeNode.Context["setReferenceHandler"] = (currentPoint, reference) => { if (store.hasStatement(reference.path, ["interface", "typeAlias"])) { return; @@ -107,6 +131,7 @@ export const create = ( rootSchema, setReferenceHandler, resolveReferencePath, + findSchemaByPathArray, }, converterContext, ); @@ -133,6 +158,7 @@ export const create = ( rootSchema, setReferenceHandler, resolveReferencePath, + findSchemaByPathArray, }, converterContext, ), @@ -161,5 +187,10 @@ export const create = ( } } }; - return { rootSchema, setReferenceHandler: setReferenceHandler, resolveReferencePath: resolveReferencePath }; + return { + rootSchema, + setReferenceHandler: setReferenceHandler, + resolveReferencePath: resolveReferencePath, + findSchemaByPathArray: findSchemaByPathArray, + }; }; diff --git a/src/internal/OpenApiTools/components/Schemas.ts b/src/internal/OpenApiTools/components/Schemas.ts index a3621e79..48f066b6 100644 --- a/src/internal/OpenApiTools/components/Schemas.ts +++ b/src/internal/OpenApiTools/components/Schemas.ts @@ -1,5 +1,3 @@ -import DotProp from "dot-prop"; - import type { OpenApi } from "../../../types"; import * as Logger from "../..//Logger"; import { UnSupportError } from "../../Exception"; @@ -39,7 +37,7 @@ export const generateNamespace = ( name: convertContext.escapeReferenceDeclarationText(maybeResolvedName), }); } - const schema = DotProp.get(context.rootSchema, pathArray.join(".")) as any; + const schema = context.findSchemaByPathArray(pathArray); return ToTypeNode.convert(entryPoint, currentPoint, factory, schema, context, convertContext, { parent: schema }); }; return store.addStatement(`${basePath}/${name}`, { @@ -82,7 +80,7 @@ export const generateNamespace = ( const schema = InferredType.getInferredType(targetSchema); const path = `${basePath}/${name}`; if (!schema) { - // Schemaが特定できないためWarningを出力する + // Outputs Warning because Schema cannot be identified Logger.warn(`Warning: Schema could not be identified. Therefore, it is treated as any. ${name}`); return store.addStatement( path, diff --git a/src/internal/OpenApiTools/toTypeNode.ts b/src/internal/OpenApiTools/toTypeNode.ts index 405561b3..08843f47 100644 --- a/src/internal/OpenApiTools/toTypeNode.ts +++ b/src/internal/OpenApiTools/toTypeNode.ts @@ -1,4 +1,3 @@ -import DotProp from "dot-prop"; import ts from "typescript"; import type { OpenApi } from "../../types"; @@ -19,7 +18,9 @@ export interface ResolveReferencePath { * @example components.a.b.c.dの場合 ["a", "b", "c", "d"].length = 4 **/ depth: number; - /** 入力$refを分解したモノ(#は除く) */ + /** + * Input $ref divided by / (except #) + */ pathArray: string[]; } @@ -27,6 +28,7 @@ export interface Context { readonly rootSchema: OpenApi.Document; setReferenceHandler: (currentPoint: string, reference: Reference.Type) => void; resolveReferencePath: (currentPoint: string, referencePath: string) => ResolveReferencePath; + findSchemaByPathArray: (paths: string[]) => OpenApi.Schema | OpenApi.Reference | OpenApi.JSONSchemaDefinition; } export type Convert = ( @@ -111,7 +113,7 @@ export const convert: Convert = ( if (depth === 2) { return factory.TypeReferenceNode.create({ name: converterContext.escapeReferenceDeclarationText(maybeResolvedName) }); } else { - const resolveSchema = DotProp.get(context.rootSchema, pathArray.join(".")) as any; + const resolveSchema = context.findSchemaByPathArray(pathArray); return convert(entryPoint, currentPoint, factory, resolveSchema, context, converterContext, { parent: schema }); } } diff --git a/test/__tests__/__snapshots__/typedef-only-test.ts.snap b/test/__tests__/__snapshots__/typedef-only-test.ts.snap index e28cf1f1..cf757e5a 100644 --- a/test/__tests__/__snapshots__/typedef-only-test.ts.snap +++ b/test/__tests__/__snapshots__/typedef-only-test.ts.snap @@ -1,5 +1,39 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Typedef only Reference test that include nested properties 1`] = ` +"// +// Generated by @himenon/openapi-typescript-code-generator +// +// OpenApi : 3.0.0 +// +// License : MIT +// + + +export namespace Schemas { + export interface Foo { + bar?: { + baz?: string; + bazbaz?: { + hoge?: number; + }; + }; + } + export type Bar = { + baz?: string; + bazbaz?: { + hoge?: number; + }; + }; + export type Baz = string; + export type Hoge = number; + export type BazBaz = { + hoge?: number; + }; +} +" +`; + exports[`Typedef only typedef-api.test.domain 1`] = ` "// // Generated by @himenon/openapi-typescript-code-generator diff --git a/test/__tests__/typedef-only-test.ts b/test/__tests__/typedef-only-test.ts index 0ea8314a..4d8f8e5c 100644 --- a/test/__tests__/typedef-only-test.ts +++ b/test/__tests__/typedef-only-test.ts @@ -14,4 +14,9 @@ describe("Typedef only", () => { const text = Utils.replaceVersionInfo(generateCode); expect(text).toMatchSnapshot(); }); + test("Reference test that include nested properties", () => { + const generateCode = fs.readFileSync("test/code/typedef-only/json.properties.ts", { encoding: "utf-8" }); + const text = Utils.replaceVersionInfo(generateCode); + expect(text).toMatchSnapshot(); + }); }); diff --git a/test/json.properties/index.yml b/test/json.properties/index.yml new file mode 100644 index 00000000..f4d8a3ea --- /dev/null +++ b/test/json.properties/index.yml @@ -0,0 +1,40 @@ +openapi: 3.0.0 +info: + version: 1.0.0 + title: api.test.domain + description: Library test schema + license: + name: MIT + +servers: + - url: "http://dev.api.test.domain/" + description: Development Environment + - url: "https://api.test.domain/" + description: Production Environment + +tags: + - name: test + +components: + schemas: + Foo: + type: object + properties: + bar: + type: object + properties: + baz: + type: string + bazbaz: + type: object + properties: + hoge: + type: number + Bar: + $ref: "#/components/schemas/Foo/properties/bar" + Baz: + $ref: "#/components/schemas/Bar/properties/baz" + Hoge: + $ref: "#/components/schemas/Bar/properties/bazbaz/properties/hoge" + BazBaz: + $ref: "#/components/schemas/Bar/properties/bazbaz"