Skip to content

feat: support property access via reference object #59

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions scripts/testCodeGen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,16 @@ const main = () => {
sync: false,
});

generateTypedefWithTemplateCode("test/ref.access/index.yml", "test/code/typedef-with-template/ref-access.ts", false, {
sync: false,
});

generateSplitCode("test/api.test.domain/index.yml", "test/code/split");

generateParameter("test/api.test.domain/index.yml", "test/code/parameter/api.test.domain.json");
generateParameter("test/infer.domain/index.yml", "test/code/parameter/infer.domain.json");


};

main();
4 changes: 2 additions & 2 deletions src/internal/OpenApiTools/Parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class Parser {
private convertContext: ConvertContext.Types;
private store: Store;
private factory: TypeScriptCodeGenerator.Factory.Type;
constructor(private entryPoint: string, private rootSchema: OpenApi.Document, noReferenceOpenApiSchema: OpenApi.Document) {
constructor(private entryPoint: string, private readonly rootSchema: OpenApi.Document, noReferenceOpenApiSchema: OpenApi.Document) {
this.currentPoint = entryPoint;
this.convertContext = ConvertContext.create();
this.factory = TypeScriptCodeGenerator.Factory.create();
Expand All @@ -27,7 +27,7 @@ export class Parser {
}

private initialize(): void {
const toTypeNodeContext = TypeNodeContext.create(this.entryPoint, this.store, this.factory, this.convertContext);
const toTypeNodeContext = TypeNodeContext.create(this.entryPoint, this.rootSchema, this.store, this.factory, this.convertContext);
const rootSchema = this.rootSchema;
if (rootSchema.components) {
if (rootSchema.components.schemas) {
Expand Down
11 changes: 9 additions & 2 deletions src/internal/OpenApiTools/TypeNodeContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as Path from "path";

import ts from "typescript";

import type { OpenApi } from "../../types";
import { DevelopmentError } from "../Exception";
import * as TypeScriptCodeGenerator from "../TsGenerator";
import * as ConverterContext from "./ConverterContext";
Expand Down Expand Up @@ -66,15 +67,19 @@ const calculateReferencePath = (store: Walker.Store, base: string, pathArray: st
if (names.length === 0) {
throw new DevelopmentError("Local Reference Error \n" + JSON.stringify({ pathArray, names, base }, null, 2));
}
const maybeResolvedNameFragments = names.concat(unresolvedPaths).map(converterContext.escapeDeclarationText);
return {
name: names.map(converterContext.escapeDeclarationText).join("."),
maybeResolvedName: names.concat(unresolvedPaths).map(converterContext.escapeDeclarationText).join("."),
maybeResolvedName: maybeResolvedNameFragments.join("."),
unresolvedPaths,
depth: maybeResolvedNameFragments.length,
pathArray,
};
};

export const create = (
entryPoint: string,
rootSchema: OpenApi.Document,
store: Walker.Store,
factory: TypeScriptCodeGenerator.Factory.Type,
converterContext: ConverterContext.Types,
Expand All @@ -94,6 +99,7 @@ export const create = (
factory,
reference.data,
{
rootSchema,
setReferenceHandler,
resolveReferencePath,
},
Expand All @@ -119,6 +125,7 @@ export const create = (
factory,
reference.data,
{
rootSchema,
setReferenceHandler,
resolveReferencePath,
},
Expand Down Expand Up @@ -149,5 +156,5 @@ export const create = (
}
}
};
return { setReferenceHandler: setReferenceHandler, resolveReferencePath: resolveReferencePath };
return { rootSchema, setReferenceHandler: setReferenceHandler, resolveReferencePath: resolveReferencePath };
};
21 changes: 17 additions & 4 deletions src/internal/OpenApiTools/components/Schemas.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import DotProp from "dot-prop";

import type { OpenApi } from "../../../types";
import { UnSupportError } from "../../Exception";
import { Factory } from "../../TsGenerator";
Expand Down Expand Up @@ -45,16 +47,27 @@ export const generateNamespace = (
const schema = targetSchema;
const reference = Reference.generate<OpenApi.Schema>(entryPoint, currentPoint, schema);
if (reference.type === "local") {
const { maybeResolvedName } = context.resolveReferencePath(currentPoint, reference.path);
const { maybeResolvedName, depth, pathArray } = context.resolveReferencePath(currentPoint, reference.path);
// console.log({
// depth,
// pathArray,
// });
const createTypeNode = () => {
if (depth === 2) {
return factory.TypeReferenceNode.create({
name: convertContext.escapeReferenceDeclarationText(maybeResolvedName),
});
}
const schema = DotProp.get(context.rootSchema, pathArray.join(".")) as any;
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO

return ToTypeNode.convert(entryPoint, currentPoint, factory, schema, context, convertContext, { parent: schema });
};
store.addStatement(`${basePath}/${name}`, {
kind: "typeAlias",
name: convertContext.escapeDeclarationText(name),
value: factory.TypeAliasDeclaration.create({
export: true,
name: convertContext.escapeDeclarationText(name),
type: factory.TypeReferenceNode.create({
name: convertContext.escapeReferenceDeclarationText(maybeResolvedName),
}),
type: createTypeNode(),
}),
});
return;
Expand Down
17 changes: 15 additions & 2 deletions src/internal/OpenApiTools/toTypeNode.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import DotProp from "dot-prop";
import ts from "typescript";

import type { OpenApi } from "../../types";
Expand All @@ -15,9 +16,16 @@ export interface ResolveReferencePath {
name: string;
maybeResolvedName: string;
unresolvedPaths: string[];
/**
* @example components.a.b.c.dの場合 ["a", "b", "c", "d"].length = 4
**/
depth: number;
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: refactoring target

/** 入力$refを分解したモノ(#は除く) */
pathArray: string[];
}

export interface Context {
readonly rootSchema: OpenApi.Document;
setReferenceHandler: (currentPoint: string, reference: Reference.Type<OpenApi.Schema | OpenApi.JSONSchemaDefinition>) => void;
resolveReferencePath: (currentPoint: string, referencePath: string) => ResolveReferencePath;
}
Expand Down Expand Up @@ -96,8 +104,13 @@ export const convert: Convert = (
if (reference.type === "local") {
// Type Aliasを作成 (or すでにある場合は作成しない)
context.setReferenceHandler(currentPoint, reference);
const { maybeResolvedName } = context.resolveReferencePath(currentPoint, reference.path);
return factory.TypeReferenceNode.create({ name: converterContext.escapeReferenceDeclarationText(maybeResolvedName) });
const { maybeResolvedName, depth, pathArray } = context.resolveReferencePath(currentPoint, reference.path);
if (depth === 2) {
return factory.TypeReferenceNode.create({ name: converterContext.escapeReferenceDeclarationText(maybeResolvedName) });
} else {
const resolveSchema = DotProp.get(context.rootSchema, pathArray.join(".")) as any;
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO

return convert(entryPoint, currentPoint, factory, resolveSchema, context, converterContext, { parent: schema });
}
}
// サポートしているディレクトリに対して存在する場合
if (reference.componentName) {
Expand Down
137 changes: 137 additions & 0 deletions test/__tests__/__snapshots__/typedef-with-template-test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2986,3 +2986,140 @@ export class Client<RequestOption> {
}
"
`;

exports[`Typedef with template ref-access 1`] = `
"//
// Generated by @himenon/openapi-typescript-code-generator
//
// OpenApi : 3.1.0
//
// License : MIT
//


export namespace Schemas {
export interface Book {
author?: {
name?: string;
age?: string;
};
publisher?: {
name?: any;
address?: string;
};
metadata: {
description: string;
};
}
export type Author = {
name?: string;
age?: string;
};
export type Publisher = {
name?: any;
address?: string;
};
}
export interface Parameter$getBook {
/** Book ID */
id: string;
}
export interface Response$getBook$Status$200 {
\\"application/json\\": Schemas.Book;
}
export interface Parameter$getDescription {
/** Book ID */
id: string;
}
export interface Response$getDescription$Status$200 {
\\"application/json\\": string;
}
export interface Parameter$getAuthor {
/** Author Id */
id: string;
}
export interface Response$getAuthor$Status$200 {
\\"application/json\\": {
name?: string;
age?: string;
};
}
export interface Parameter$getPublisher {
/** Publisher ID */
id: string;
}
export interface Response$getPublisher$Status$200 {
\\"application/json\\": Schemas.Publisher;
}
export type ResponseContentType$getBook = keyof Response$getBook$Status$200;
export interface Params$getBook {
parameter: Parameter$getBook;
}
export type ResponseContentType$getDescription = keyof Response$getDescription$Status$200;
export interface Params$getDescription {
parameter: Parameter$getDescription;
}
export type ResponseContentType$getAuthor = keyof Response$getAuthor$Status$200;
export interface Params$getAuthor {
parameter: Parameter$getAuthor;
}
export type ResponseContentType$getPublisher = keyof Response$getPublisher$Status$200;
export interface Params$getPublisher {
parameter: Parameter$getPublisher;
}
export type HttpMethod = \\"GET\\" | \\"PUT\\" | \\"POST\\" | \\"DELETE\\" | \\"OPTIONS\\" | \\"HEAD\\" | \\"PATCH\\" | \\"TRACE\\";
export interface ObjectLike {
[key: string]: any;
}
export interface QueryParameter {
value: any;
style?: \\"form\\" | \\"spaceDelimited\\" | \\"pipeDelimited\\" | \\"deepObject\\";
explode: boolean;
}
export interface QueryParameters {
[key: string]: QueryParameter;
}
export type SuccessResponses = Response$getBook$Status$200 | Response$getDescription$Status$200 | Response$getAuthor$Status$200 | Response$getPublisher$Status$200;
export namespace ErrorResponse {
export type getBook = void;
export type getDescription = void;
export type getAuthor = void;
export type getPublisher = void;
}
export interface ApiClient<RequestOption> {
request: <T = SuccessResponses>(httpMethod: HttpMethod, url: string, headers: ObjectLike | any, requestBody: ObjectLike | any, queryParameters: QueryParameters | undefined, options?: RequestOption) => Promise<T>;
}
export class Client<RequestOption> {
private baseUrl: string;
constructor(private apiClient: ApiClient<RequestOption>, baseUrl: string) { this.baseUrl = baseUrl.replace(/\\\\/$/, \\"\\"); }
public async getBook(params: Params$getBook, option?: RequestOption): Promise<Response$getBook$Status$200[\\"application/json\\"]> {
const url = this.baseUrl + \`/get/book/\${params.parameter.id}\`;
const headers = {
Accept: \\"application/json\\"
};
return this.apiClient.request(\\"GET\\", url, headers, undefined, undefined, option);
}
public async getDescription(params: Params$getDescription, option?: RequestOption): Promise<Response$getDescription$Status$200[\\"application/json\\"]> {
const url = this.baseUrl + \`/get/book/\${params.parameter.id}/description\`;
const headers = {
Accept: \\"application/json\\"
};
return this.apiClient.request(\\"GET\\", url, headers, undefined, undefined, option);
}
public async getAuthor(params: Params$getAuthor, option?: RequestOption): Promise<Response$getAuthor$Status$200[\\"application/json\\"]> {
const url = this.baseUrl + \`/get/author/\${params.parameter.id}\`;
const headers = {
Accept: \\"application/json\\"
};
return this.apiClient.request(\\"GET\\", url, headers, undefined, undefined, option);
}
public async getPublisher(params: Params$getPublisher, option?: RequestOption): Promise<Response$getPublisher$Status$200[\\"application/json\\"]> {
const url = this.baseUrl + \`/get/publisher/\${params.parameter.id}\`;
const headers = {
Accept: \\"application/json\\"
};
return this.apiClient.request(\\"GET\\", url, headers, undefined, undefined, option);
}
}
"
`;
5 changes: 5 additions & 0 deletions test/__tests__/typedef-with-template-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,9 @@ describe("Typedef with template", () => {
const text = Utils.replaceVersionInfo(generateCode);
expect(text).toMatchSnapshot();
});
test("ref-access", () => {
const generateCode = fs.readFileSync(path.join(__dirname, "../code/typedef-with-template/ref-access.ts"), { encoding: "utf-8" });
const text = Utils.replaceVersionInfo(generateCode);
expect(text).toMatchSnapshot();
});
});
Loading