Skip to content

feat: supports fragment in remote reference #83

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

Closed
Closed
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
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ jobs:
- name: Test & Build
run: |
pnpm build
pnpm run test:code:gen
pnpm test
env:
CI: true
3 changes: 3 additions & 0 deletions scripts/testCodeGen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ const main = () => {
Writer.generateTypedefWithTemplateCode("test/ref.access/index.yml", "test/code/typedef-with-template/ref-access.ts", false, {
sync: false,
});
Writer.generateTypedefWithTemplateCode("test/remote.ref.access/v1.yml", "test/code/typedef-with-template/remote-ref-access.ts", false, {
sync: false,
});
Writer.generateTypedefWithTemplateCode("test/kubernetes/openapi-v1.18.5.json", "test/code/kubernetes/client-v1.18.5.ts", false, {
sync: false,
});
Expand Down
8 changes: 7 additions & 1 deletion src/internal/FileSystem/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,17 @@ export class FileSystem {
}

public static existSync(entryPoint: string): boolean {
const fragmentIndex = entryPoint.indexOf(this.FRAGMENT);
const hasFragment = fragmentIndex !== -1;
if (hasFragment) {
const filename = entryPoint.substring(0, fragmentIndex);
return !!(fs.existsSync(filename) && fs.statSync(filename).isFile());
}
return !!(fs.existsSync(entryPoint) && fs.statSync(entryPoint).isFile());
}

public static loadJsonOrYaml(entryPoint: string): any {
const hasFragment: boolean = new RegExp(this.FRAGMENT).test(entryPoint);
const hasFragment = entryPoint.indexOf(this.FRAGMENT) !== -1;
Copy link
Owner

Choose a reason for hiding this comment

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

Oh, I see. Thankyou!

if (hasFragment) {
const [filename, fragment] = entryPoint.split(this.FRAGMENT);
const data = this.internalLoadJsonOrYaml(filename);
Expand Down
25 changes: 16 additions & 9 deletions src/internal/OpenApiTools/components/Reference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,22 +136,29 @@ export const generate = <T>(entryPoint: string, currentPoint: string, reference:
throw new NotFoundFileError(`Not found reference point from current point. \n Path: ${referencePoint}`);
}

const relativePathFromEntryPoint = path.relative(path.dirname(entryPoint), referencePoint); // components/hoge/fuga.yml
const ext = path.extname(relativePathFromEntryPoint); // .yml
const pathArray: string[] = relativePathFromEntryPoint.replace(ext, "").split(path.sep); // ["components", "hoge", "fuga"]
const targetPath: string = pathArray.join("/"); // components/hoge/fuga
const fragmentIndex = referencePoint.indexOf("#/");
let targetPath: string;
if (fragmentIndex !== -1) {
targetPath = referencePoint.substring(fragmentIndex + 2);
} else {
const relativePathFromEntryPoint = path.relative(path.dirname(entryPoint), referencePoint); // components/hoge/fuga.yml
const pathArray: string[] = relativePathFromEntryPoint.split(path.sep); // ["components", "hoge", "fuga"]
Copy link
Owner

Choose a reason for hiding this comment

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

Since this file also does not use the posix path, CI in the Windows environment is considered to have failed.

- import * as path from "path";
+ import { posix as path } from "path";

Copy link
Owner

Choose a reason for hiding this comment

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

This is another problem ...
#87

if (pathArray[0] != "components") {
throw new DevelopmentError(`targetPath is not start "components":\n${relativePathFromEntryPoint}`);
}

const ext = path.extname(relativePathFromEntryPoint); // .yml
targetPath = pathArray.join("/").substring(0, relativePathFromEntryPoint.length - ext.length); // components/hoge/fuga
}
const pathArray: string[] = targetPath.split("/"); // ["components", "hoge", "fuga"]
const schemaName = pathArray[pathArray.length - 1]; // fuga
const componentName = pathArray[0] === "components" ? pathArray[1] : "";
const data = FileSystem.loadJsonOrYaml(referencePoint);

const data = FileSystem.loadJsonOrYaml(referencePoint);
if (Guard.isReference(data)) {
return generate<T>(entryPoint, referencePoint, data);
}

if (!targetPath.startsWith("components")) {
throw new DevelopmentError(`targetPath is not start "components":\n${targetPath}`);
}

return {
type: "remote",
referencePoint,
Expand Down
145 changes: 145 additions & 0 deletions test/__tests__/__snapshots__/typedef-with-template-test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1216,3 +1216,148 @@ export class Client<RequestOption> {
}
"
`;

exports[`Typedef with template remote-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 namespace Book {
export namespace properties {
export interface author {
name?: string;
age?: string;
}
export interface publisher {
name?: any;
address?: string;
}
export namespace metadata {
export namespace properties {
export type description = string;
}
}
}
}
export type Author = Schemas.Book.properties.author;
export type Publisher = Schemas.Book.properties.publisher;
}
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\\": Schemas.Book.properties.metadata.properties.description;
}
export interface Parameter$getAuthor {
/** Author Id */
id: string;
}
export interface Response$getAuthor$Status$200 {
\\"application/json\\": Schemas.Author;
}
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("remote-ref-access", () => {
const generateCode = fs.readFileSync(path.join(__dirname, "../code/typedef-with-template/remote-ref-access.ts"), { encoding: "utf-8" });
Copy link
Owner

Choose a reason for hiding this comment

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

import { posix as path } from "path";

Oh, sorry. Please change import path. Like this.

https://github.com/Himenon/openapi-typescript-code-generator/blob/main/scripts/tools/cherry-pick.ts#L2

Copy link
Owner

@Himenon Himenon Oct 6, 2022

Choose a reason for hiding this comment

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

"path" does not work properly in Windows environment, use posix path.

https://nodejs.org/api/path.html#pathposix

Copy link
Owner

Choose a reason for hiding this comment

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

This is another problem ...
#87

const text = Utils.replaceVersionInfo(generateCode);
expect(text).toMatchSnapshot();
});
});
124 changes: 124 additions & 0 deletions test/remote.ref.access/v0.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
openapi: 3.1.0
info:
version: 1.0.0
title: v0.remote.ref.access.test
description: Library test schema
license:
name: MIT

servers:
- url: "http://dev.remote.ref.access.test/v0/"
description: Development Environment
- url: "https://ref,remote.access.test/v0/"
description: Production Environment

tags:
- name: test

components:
schemas:
Book:
type: object
required:
- metadata
properties:
author:
type: object
properties:
name:
type: string
age:
type: string
publisher:
type: object
properties:
name:
type: String
address:
type: string
metadata:
type: object
required:
- description
properties:
description:
type: string
Author:
$ref: "#/components/schemas/Book/properties/author"
Publisher:
$ref: "#/components/schemas/Book/properties/publisher"

paths:
/get/book/{id}:
parameters:
- name: id
in: path
required: true
description: Book ID
schema:
type: string
format: uuid
get:
operationId: getBook
responses:
200:
description: Get Books
content:
application/json:
schema:
$ref: "#/components/schemas/Book"
/get/book/{id}/description:
parameters:
- name: id
in: path
required: true
description: Book ID
schema:
type: string
format: uuid
get:
operationId: getDescription
responses:
200:
description: Get Book Description
content:
application/json:
schema:
$ref: "#/components/schemas/Book/properties/metadata/properties/description"

/get/author/{id}:
parameters:
- name: id
in: path
required: true
description: Author Id
schema:
type: string
format: uuid
get:
operationId: getAuthor
responses:
200:
description: Get Author
content:
application/json:
schema:
$ref: "#/components/schemas/Book/properties/author"
/get/publisher/{id}:
parameters:
- name: id
in: path
required: true
description: Publisher ID
schema:
type: string
format: uuid
get:
operationId: getPublisher
responses:
200:
description: Get Publisher
content:
application/json:
schema:
$ref: "#/components/schemas/Publisher"
Loading