Skip to content
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

feat: Support Kubernetes OpenAPI Schema #62

Merged
merged 14 commits into from
Nov 7, 2021
60 changes: 31 additions & 29 deletions scripts/testCodeGen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ const generateSplitCode = (inputFilename: string, outputDir: string) => {
const apiClientCode = codeGenerator.generateCode([
{
generator: () => {
return [`import { Schemas } from "./types";`];
return [`import { Schemas, RequestBodies } from "./types";`];
},
},
codeGenerator.getAdditionalTypeDefinitionCustomCodeGenerator(),
Expand All @@ -98,34 +98,36 @@ const generateParameter = (inputFilename: string, outputFilename: string) => {
};

const main = () => {
generateTypedefCodeOnly("test/api.test.domain/index.yml", "test/code/typedef-only/api.test.domain.ts", true);
generateTypedefCodeOnly("test/infer.domain/index.yml", "test/code/typedef-only/infer.domain.ts", false);

generateTemplateCodeOnly("test/api.test.domain/index.yml", "test/code/template-only/api.test.domain.ts", true, { sync: false });
generateTemplateCodeOnly("test/api.test.domain/index.yml", "test/code/template-only/sync-api.test.domain.ts", true, { sync: true });
generateTemplateCodeOnly("test/infer.domain/index.yml", "test/code/template-only/infer.domain.ts", false, { sync: true });

generateTypedefWithTemplateCode("test/api.v2.domain/index.yml", "test/code/typedef-with-template/api.v2.domain.ts", false, { sync: false });
generateTypedefWithTemplateCode("test/api.test.domain/index.yml", "test/code/typedef-with-template/api.test.domain.ts", true, {
sync: false,
});
generateTypedefWithTemplateCode("test/api.test.domain/index.yml", "test/code/typedef-with-template/sync-api.test.domain.ts", true, {
sync: true,
});
generateTypedefWithTemplateCode("test/infer.domain/index.yml", "test/code/typedef-with-template/infer.domain.ts", false, { sync: false });

generateTypedefWithTemplateCode("test/argo-rollout/index.json", "test/code/typedef-with-template/argo-rollout.ts", false, {
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");
// generateTypedefCodeOnly("test/api.test.domain/index.yml", "test/code/typedef-only/api.test.domain.ts", true);
// generateTypedefCodeOnly("test/infer.domain/index.yml", "test/code/typedef-only/infer.domain.ts", false);

// generateTemplateCodeOnly("test/api.test.domain/index.yml", "test/code/template-only/api.test.domain.ts", true, { sync: false });
// generateTemplateCodeOnly("test/api.test.domain/index.yml", "test/code/template-only/sync-api.test.domain.ts", true, { sync: true });
// generateTemplateCodeOnly("test/infer.domain/index.yml", "test/code/template-only/infer.domain.ts", false, { sync: true });

// generateTypedefWithTemplateCode("test/api.v2.domain/index.yml", "test/code/typedef-with-template/api.v2.domain.ts", false, { sync: false });
// generateTypedefWithTemplateCode("test/api.test.domain/index.yml", "test/code/typedef-with-template/api.test.domain.ts", true, {
// sync: false,
// });
// generateTypedefWithTemplateCode("test/api.test.domain/index.yml", "test/code/typedef-with-template/sync-api.test.domain.ts", true, {
// sync: true,
// });
// generateTypedefWithTemplateCode("test/infer.domain/index.yml", "test/code/typedef-with-template/infer.domain.ts", false, { sync: false });

// generateTypedefWithTemplateCode("test/argo-rollout/index.json", "test/code/typedef-with-template/argo-rollout.ts", false, {
// sync: false,
// });

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

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

// 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");


};
Expand Down
23 changes: 22 additions & 1 deletion src/internal/OpenApiTools/TypeNodeContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@ const generatePath = (entryPoint: string, currentPoint: string, referencePath: s
};
};

const calculateReferencePath = (store: Walker.Store, base: string, pathArray: string[], converterContext: ConverterContext.Types,): ToTypeNode.ResolveReferencePath => {
const calculateReferencePath = (
store: Walker.Store,
base: string,
pathArray: string[],
converterContext: ConverterContext.Types,
): ToTypeNode.ResolveReferencePath => {
let names: string[] = [];
let unresolvedPaths: string[] = [];
pathArray.reduce((previous, lastPath, index) => {
Expand Down Expand Up @@ -148,6 +153,22 @@ export const create = (
name: converterContext.escapeTypeReferenceNodeName(maybeResolvedName),
}),
});
// ここで確認が必要そう?
// if (reference.path.match(/components\/schemas\/io\.k8s\.apimachinery\.pkg\.util\.intstr\.IntOrString$/)) {
// const hasInterface = store.hasStatement(reference.path, ["interface"]);
// const hasTypeAlias = store.hasStatement(reference.path, ["typeAlias"]);
// console.log(`local value: reference.name = ${reference.name}, hasInterface=${hasInterface}, hasTypeAlias=${hasTypeAlias}`);
// }
// if ("RequestBodies.io$k8s$api$core$v1$Namespace" === maybeResolvedName) {
// console.log({
// reference,
// name1: converterContext.escapeDeclarationText(reference.name),
// name2: converterContext.escapeTypeReferenceNodeName(maybeResolvedName),
// hasStatement1: store.hasStatement(reference.path, ["typeAlias"]),
// hasStatement2: store.hasStatement(reference.path, ["interface"]),
// hasStatement3: store.hasStatement(reference.path, ["namespace"]),
// })
// }
store.addStatement(reference.path, {
name: reference.name,
kind: "typeAlias",
Expand Down
11 changes: 6 additions & 5 deletions src/internal/OpenApiTools/Walker/Store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as Path from "path";
import { Tree } from "@himenon/path-oriented-data-structure";
import Dot from "dot-prop";
import ts from "typescript";
import * as fs from "fs";

import type { OpenApi } from "../../../types";
import { UnSupportError } from "../../Exception";
Expand Down Expand Up @@ -51,7 +52,7 @@ class Store {
}
public getRootStatements(): ts.Statement[] {
// Debug Point: 抽象的なデータ構造全体を把握するために出力すると良い
// fs.writeFileSync("debug/tree.json", JSON.stringify(this.operator.getHierarchy(), null, 2), { encoding: "utf-8" });
fs.writeFileSync("debug/tree.json", JSON.stringify(this.operator.getHierarchy(), null, 2), { encoding: "utf-8" });
const statements = Def.componentNames.reduce<ts.Statement[]>((statements, componentName) => {
const treeOfNamespace = this.getChildByPaths(componentName, "namespace");
if (treeOfNamespace) {
Expand All @@ -74,17 +75,17 @@ class Store {
/**
* @params path: "components/headers/hoge"
*/
public addStatement(path: string, statement: Structure.ComponentParams): void {
public addStatement(path: string, statement: Structure.ComponentParams, options?: { override?: boolean }): void {
if (!path.startsWith("components")) {
throw new UnSupportError(`componentsから始まっていません。path=${path}`);
}
const targetPath = Path.posix.relative("components", path);
// すでにinterfaceとして登録がある場合はスキップ
if (this.hasStatement(targetPath, ["interface"])) {
// すでにinterfaceまたはNAMESPACEとして登録がある場合はスキップ
if (this.hasStatement(targetPath, ["interface", "namespace"])) {
return;
}
// もしTypeAliasが同じスコープに登録されているかつ、interfaceが新しく追加しようとしている場合、既存のstatementを削除する
if (this.hasStatement(targetPath, ["typeAlias"]) && statement.kind === "interface") {
if (!!options?.override || (this.hasStatement(targetPath, ["typeAlias"]) && statement.kind === "interface")) {
this.operator.remove(targetPath, "typeAlias");
}
this.operator.set(targetPath, Structure.createInstance(statement));
Expand Down
2 changes: 1 addition & 1 deletion src/internal/OpenApiTools/components/Operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ export const generateStatements = (
export: true,
name: converterContext.generateRequestBodyName(operationId),
type: factory.TypeReferenceNode.create({
name: context.resolveReferencePath(currentPoint, `${reference.path}`) + "." + Name.ComponentChild.Content, // TODO Contextから作成?
name: context.resolveReferencePath(currentPoint, `${reference.path}`).name + "." + Name.ComponentChild.Content, // TODO Contextから作成?
Copy link
Owner Author

Choose a reason for hiding this comment

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

Bug FIX

}),
}),
);
Expand Down
3 changes: 2 additions & 1 deletion src/internal/OpenApiTools/components/RequestBody.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,11 @@ export const generateNamespace = (
context: ToTypeNode.Context,
converterContext: ConverterContext.Types,
): void => {
const escapeName = converterContext.escapeDeclarationText(name);
const basePath = `${parentName}/${name}`;
store.addStatement(basePath, {
kind: "namespace",
name,
Copy link
Owner Author

Choose a reason for hiding this comment

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

Bug Fix

name: converterContext.escapeDeclarationText(escapeName),
comment: requestBody.description,
});
store.addStatement(`${basePath}/Content`, {
Expand Down
4 changes: 2 additions & 2 deletions src/internal/OpenApiTools/components/Schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Factory } from "../../TsGenerator";
import * as ConvertContext from "../ConverterContext";
import * as Guard from "../Guard";
import * as ToTypeNode from "../toTypeNode";
import type { ArraySchema, ObjectSchema, PrimitiveSchema } from "../types";
import type { ArraySchema, ObjectSchema, PrimitiveSchema, AnySchema } from "../types";
import type * as Walker from "../Walker";
import * as ExternalDocumentation from "./ExternalDocumentation";

Expand Down Expand Up @@ -96,7 +96,7 @@ export const generateTypeAlias = (
currentPoint: string,
factory: Factory.Type,
name: string,
schema: PrimitiveSchema,
schema: PrimitiveSchema | AnySchema,
convertContext: ConvertContext.Types,
): ts.TypeAliasDeclaration => {
let type: ts.TypeNode;
Expand Down
47 changes: 29 additions & 18 deletions src/internal/OpenApiTools/components/Schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,6 @@ export const generateNamespace = (
const reference = Reference.generate<OpenApi.Schema>(entryPoint, currentPoint, schema);
if (reference.type === "local") {
const { maybeResolvedName, depth, pathArray } = context.resolveReferencePath(currentPoint, reference.path);
// console.log({
// depth,
// pathArray,
// });
const createTypeNode = () => {
if (depth === 2) {
return factory.TypeReferenceNode.create({
Expand All @@ -61,7 +57,7 @@ export const generateNamespace = (
const schema = DotProp.get(context.rootSchema, pathArray.join(".")) as any;
return ToTypeNode.convert(entryPoint, currentPoint, factory, schema, context, convertContext, { parent: schema });
};
store.addStatement(`${basePath}/${name}`, {
return store.addStatement(`${basePath}/${name}`, {
kind: "typeAlias",
name: convertContext.escapeDeclarationText(name),
value: factory.TypeAliasDeclaration.create({
Expand All @@ -70,7 +66,6 @@ export const generateNamespace = (
type: createTypeNode(),
}),
});
return;
}
Schema.addSchema(
entryPoint,
Expand Down Expand Up @@ -100,62 +95,78 @@ export const generateNamespace = (
});
}
const schema = InferredType.getInferredType(targetSchema);
const path = `${basePath}/${name}`;
if (!schema) {
// ここは修正対象
// return store.addStatement(path, {
// kind: "typeAlias",
// name: convertContext.escapeDeclarationText(name),
// value: Schema.generateTypeAlias(entryPoint, currentPoint, factory, name, { type: "null" }, convertContext),
// });
const typeNode = createNullableTypeNode(factory, targetSchema);
if (!typeNode) {
throw new UnSupportError("schema.type not specified \n" + JSON.stringify(targetSchema));
}
return typeNode;
// if (!typeNode) {
// console.error(`Error[${name}]: ${JSON.stringify(targetSchema)}`);
// return factory.TypeNode.create({
// type: "any",
// });
// throw new UnSupportError("schema.type not specified \n" + JSON.stringify(targetSchema));
// }
// TODO warnは出す
// console.warn(`Warning[${name}]: ${JSON.stringify(targetSchema)}`);
return store.addStatement(path, {
kind: "typeAlias",
name: convertContext.escapeDeclarationText(name),
value: Schema.generateTypeAlias(entryPoint, currentPoint, factory, name, { type: "any" }, convertContext),
}, { override: true });
}
const path = `${basePath}/${name}`;
if (Guard.isAllOfSchema(schema)) {
return store.addStatement(path, {
kind: "typeAlias",
name: convertContext.escapeDeclarationText(name),
value: Schema.generateMultiTypeAlias(entryPoint, currentPoint, factory, name, schema.allOf, context, "allOf", convertContext),
});
}, { override: true });
}
if (Guard.isOneOfSchema(schema)) {
return store.addStatement(path, {
kind: "typeAlias",
name: convertContext.escapeDeclarationText(name),
value: Schema.generateMultiTypeAlias(entryPoint, currentPoint, factory, name, schema.oneOf, context, "oneOf", convertContext),
});
}, { override: true });
}
if (Guard.isAnyOfSchema(schema)) {
return store.addStatement(path, {
kind: "typeAlias",
name: convertContext.escapeDeclarationText(name),
value: Schema.generateMultiTypeAlias(entryPoint, currentPoint, factory, name, schema.anyOf, context, "anyOf", convertContext),
});
}, { override: true });
}
if (Guard.isArraySchema(schema)) {
return store.addStatement(path, {
kind: "typeAlias",
name: convertContext.escapeDeclarationText(name),
value: Schema.generateArrayTypeAlias(entryPoint, currentPoint, factory, name, schema, context, convertContext),
});
}, { override: true });
}
if (Guard.isObjectSchema(schema)) {
return store.addStatement(path, {
kind: "interface",
name: convertContext.escapeDeclarationText(name),
value: Schema.generateInterface(entryPoint, currentPoint, factory, name, schema, context, convertContext),
});
}, { override: true });
}
if (Guard.isObjectSchema(schema)) {
return store.addStatement(path, {
kind: "interface",
name: convertContext.escapeDeclarationText(name),
value: Schema.generateInterface(entryPoint, currentPoint, factory, name, schema, context, convertContext),
});
}, { override: true });
}
if (Guard.isPrimitiveSchema(schema)) {
return store.addStatement(path, {
kind: "typeAlias",
name,
value: Schema.generateTypeAlias(entryPoint, currentPoint, factory, name, schema, convertContext),
});
}, { override: true });
}
throw new UnSupportError("schema.type = Array[] not supported. " + JSON.stringify(schema));
});
Expand Down
4 changes: 4 additions & 0 deletions src/internal/OpenApiTools/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,7 @@ export interface ArraySchema extends Omit<OpenApi.Schema, "type"> {
export interface PrimitiveSchema extends Omit<OpenApi.Schema, "type"> {
type: "string" | "number" | "integer" | "boolean" | "null";
}

export interface AnySchema extends Omit<OpenApi.Schema, "type"> {
type: "any";
}
6 changes: 5 additions & 1 deletion src/internal/TsGenerator/factory/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ export const escapeIdentiferText = (text: string): string => {
};

export const generateComment = (comment: string, deprecated?: boolean): Comment => {
const splitComments = deprecated ? ["@deprecated"].concat(comment.split(/\r?\n/)) : comment.split(/\r?\n/);
const excapedComment = comment
.replace(/\*\//, "\\*\\\\/") // */ -> \*\/
.replace(/\/\*/, "/\\\\*") // /* -> \/\*
.replace(/\*\/\*/, "\\*\\/\\*"); // */* -> \*\/\*
const splitComments = deprecated ? ["@deprecated"].concat(excapedComment.split(/\r?\n/)) : excapedComment.split(/\r?\n/);
const comments = splitComments.filter((comment, index) => {
if (index === splitComments.length - 1 && comment === "") {
return false;
Expand Down
Loading