Skip to content

fix: unresolved reference #8

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 1 commit into from
Jan 15, 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
53 changes: 39 additions & 14 deletions src/Converter/v3/Context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as Path from "path";
import ts from "typescript";

import * as TypeScriptCodeGenerator from "../../CodeGenerator";
import { DevelopmentError, NotFoundReference } from "../../Exception";
import { DevelopmentError } from "../../Exception";
import { Store } from "./store";
import * as ToTypeNode from "./toTypeNode";

Expand All @@ -24,54 +24,67 @@ const generatePath = (entryPoint: string, currentPoint: string, referencePath: s
};
};

const generateName = (store: Store.Type, base: string, pathArray: string[]): string => {
const calculateReferencePath = (store: Store.Type, base: string, pathArray: string[]): ToTypeNode.ResolveReferencePath => {
let names: string[] = [];
let unresolvedPaths: string[] = [];
pathArray.reduce((previous, lastPath, index) => {
const current = Path.join(previous, lastPath);
// ディレクトリが深い場合は相対パスが`..`を繰り返す可能性があり、
// その場合はすでに登録されたnamesを削除する
if (lastPath === ".." && names.length > 0) {
names = names.slice(0, names.length - 1);
}
const isLast = index === pathArray.length - 1;
if (isLast) {
const isFinalPath = index === pathArray.length - 1;
if (isFinalPath) {
const statement = store.getStatement(current, "interface");
const statement2 = store.getStatement(current, "typeAlias");
const statement3 = store.getStatement(current, "namespace");
if (statement) {
names.push(statement.name);
return current;
} else if (statement2) {
names.push(statement2.name);
return current;
} else if (statement3) {
names.push(statement3.name);
return current;
} else {
unresolvedPaths.push(lastPath);
}
} else {
const statement = store.getStatement(current, "namespace");
if (statement) {
names.push(statement.value.name.text);
unresolvedPaths = unresolvedPaths.slice(0, unresolvedPaths.length - 1);
names.push(statement.name);
} else {
unresolvedPaths.push(lastPath);
}
}
return current;
}, base);
if (names.length === 0) {
throw new DevelopmentError("Local Reference Error \n" + JSON.stringify({ pathArray, names, base }, null, 2));
}
return names.join(".");
return {
name: names.join("."),
maybeResolvedName: names.concat(unresolvedPaths).join("."),
unresolvedPaths,
};
};

export const create = (entryPoint: string, store: Store.Type, factory: TypeScriptCodeGenerator.Factory.Type): ToTypeNode.Context => {
const getReferenceName: ToTypeNode.Context["getReferenceName"] = (currentPoint, referencePath): string => {
const resolveReferencePath: ToTypeNode.Context["resolveReferencePath"] = (currentPoint, referencePath) => {
const { pathArray, base } = generatePath(entryPoint, currentPoint, referencePath);
return generateName(store, base, pathArray);
return calculateReferencePath(store, base, pathArray);
};
const setReferenceHandler: ToTypeNode.Context["setReferenceHandler"] = reference => {
const setReferenceHandler: ToTypeNode.Context["setReferenceHandler"] = (currentPoint, reference) => {
if (store.hasStatement(reference.path, ["interface", "typeAlias"])) {
return;
}
if (reference.type === "remote") {
const typeNode = ToTypeNode.convert(entryPoint, reference.referencePoint, factory, reference.data, {
setReferenceHandler,
getReferenceName,
resolveReferencePath,
});
if (ts.isTypeLiteralNode(typeNode)) {
store.addStatement(reference.path, {
Expand All @@ -89,7 +102,7 @@ export const create = (entryPoint: string, store: Store.Type, factory: TypeScrip
name: reference.name,
type: ToTypeNode.convert(entryPoint, reference.referencePoint, factory, reference.data, {
setReferenceHandler,
getReferenceName,
resolveReferencePath,
}),
});
store.addStatement(reference.path, {
Expand All @@ -99,10 +112,22 @@ export const create = (entryPoint: string, store: Store.Type, factory: TypeScrip
});
}
} else if (reference.type === "local") {
if (!store.hasStatement(reference.path, ["namespace", "interface", "typeAlias"])) {
throw new NotFoundReference(`The schema ${reference.name} is undefined in "${reference.path}".`);
if (!store.isAfterDefined(reference.path)) {
const { maybeResolvedName } = resolveReferencePath(currentPoint, reference.path);
const value = factory.TypeAliasDeclaration.create({
export: true,
name: reference.name,
type: factory.TypeReferenceNode.create({
name: maybeResolvedName,
}),
});
store.addStatement(reference.path, {
name: reference.name,
type: "typeAlias",
value,
});
}
}
};
return { setReferenceHandler: setReferenceHandler, getReferenceName };
return { setReferenceHandler: setReferenceHandler, resolveReferencePath: resolveReferencePath };
};
6 changes: 3 additions & 3 deletions src/Converter/v3/components/Header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,20 @@ export const generatePropertySignature = (
if (Guard.isReference(header)) {
const reference = Reference.generate<OpenApi.Header>(entryPoint, currentPoint, header);
if (reference.type === "local") {
context.setReferenceHandler(reference);
context.setReferenceHandler(currentPoint, reference);
return factory.PropertySignature.create({
name,
optional: false,
type: factory.TypeReferenceNode.create({
name: context.getReferenceName(currentPoint, reference.path),
name: context.resolveReferencePath(currentPoint, reference.path).name,
}),
});
}
return factory.PropertySignature.create({
name,
optional: false,
type: factory.TypeReferenceNode.create({
name: context.getReferenceName(currentPoint, reference.path),
name: context.resolveReferencePath(currentPoint, reference.path).name,
}),
});
}
Expand Down
12 changes: 6 additions & 6 deletions src/Converter/v3/components/Operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ export const generateNamespace = (
if (Guard.isReference(operation.requestBody)) {
const reference = Reference.generate<OpenApi.RequestBody>(entryPoint, currentPoint, operation.requestBody);
if (reference.type === "local") {
context.setReferenceHandler(reference);
context.setReferenceHandler(currentPoint, reference);
// TODO (not-use) 追加する必要がある(このメソッドを使わない可能性あり)
factory.TypeReferenceNode.create({ name: context.getReferenceName(currentPoint, reference.path) });
factory.TypeReferenceNode.create({ name: context.resolveReferencePath(currentPoint, reference.path).name });
} else if (reference.type === "remote" && reference.componentName) {
const contentPath = path.join(reference.path, "Content"); // requestBodyはNamespaceを形成するため
const name = "Content";
Expand All @@ -83,7 +83,7 @@ export const generateNamespace = (
name: name,
value: RequestBody.generateInterface(entryPoint, reference.referencePoint, factory, name, reference.data, context),
});
const typeAliasName = context.getReferenceName(currentPoint, contentPath);
const typeAliasName = context.resolveReferencePath(currentPoint, contentPath).name;
store.addStatement(`${basePath}/RequestBody`, {
type: "typeAlias",
name: typeAliasName,
Expand Down Expand Up @@ -129,13 +129,13 @@ export const generateStatements = (
if (Guard.isReference(operation.requestBody)) {
const reference = Reference.generate<OpenApi.RequestBody>(entryPoint, currentPoint, operation.requestBody);
if (reference.type === "local") {
context.setReferenceHandler(reference);
context.setReferenceHandler(currentPoint, reference);
statements.push(
factory.TypeAliasDeclaration.create({
export: true,
name: Name.requestBodyName(operationId),
type: factory.TypeReferenceNode.create({
name: context.getReferenceName(currentPoint, `${reference.path}`) + "." + Name.ComponentChild.Content, // TODO Contextから作成?
name: context.resolveReferencePath(currentPoint, `${reference.path}`) + "." + Name.ComponentChild.Content, // TODO Contextから作成?
}),
}),
);
Expand All @@ -151,7 +151,7 @@ export const generateStatements = (
factory.TypeAliasDeclaration.create({
export: true,
name: requestBodyName,
type: factory.TypeReferenceNode.create({ name: context.getReferenceName(currentPoint, contentPath) }),
type: factory.TypeReferenceNode.create({ name: context.resolveReferencePath(currentPoint, contentPath).name }),
}),
);

Expand Down
4 changes: 2 additions & 2 deletions src/Converter/v3/components/Parameter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ export const generatePropertySignature = (
if (Guard.isReference(parameter)) {
const reference = Reference.generate<OpenApi.Parameter>(entryPoint, currentPoint, parameter);
if (reference.type === "local") {
context.setReferenceHandler(reference);
context.setReferenceHandler(currentPoint, reference);
return factory.PropertySignature.create({
name: reference.name,
optional: false,
type: factory.TypeReferenceNode.create({
name: context.getReferenceName(currentPoint, reference.path),
name: context.resolveReferencePath(currentPoint, reference.path).name,
}),
});
}
Expand Down
2 changes: 1 addition & 1 deletion src/Converter/v3/components/Parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export const generateNamespace = (
export: true,
name: name,
type: factory.TypeReferenceNode.create({
name: context.getReferenceName(currentPoint, reference.path),
name: context.resolveReferencePath(currentPoint, reference.path).name,
}),
}),
});
Expand Down
2 changes: 1 addition & 1 deletion src/Converter/v3/components/Response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const generateReferenceNamespace = (
context: ToTypeNode.Context,
): void => {
const basePath = `${parentPath}/${nameWithStatusCode}`;
const referenceNamespaceName = context.getReferenceName(currentPoint, responseReference.path);
const referenceNamespaceName = context.resolveReferencePath(currentPoint, responseReference.path).name;
store.addStatement(basePath, {
type: "namespace",
name: nameWithStatusCode,
Expand Down
7 changes: 4 additions & 3 deletions src/Converter/v3/components/Responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export const generateNamespaceWithStatusCode = (
if (Guard.isReference(response)) {
const reference = Reference.generate<OpenApi.Response>(entryPoint, currentPoint, response);
if (reference.type === "local") {
context.setReferenceHandler(reference);
context.setReferenceHandler(currentPoint, reference);
Response.generateReferenceNamespace(entryPoint, currentPoint, store, factory, basePath, nameWithStatusCode, reference, context);
} else if (reference.componentName) {
// reference先に定義を作成
Expand Down Expand Up @@ -125,13 +125,14 @@ export const generateInterfacesWithStatusCode = (
if (Guard.isReference(response)) {
const reference = Reference.generate<OpenApi.Response>(entryPoint, currentPoint, response);
if (reference.type === "local") {
context.setReferenceHandler(reference);
context.setReferenceHandler(currentPoint, reference);
const name = context.resolveReferencePath(currentPoint, `${reference.path}/Content`).maybeResolvedName;
statements.push(
factory.TypeAliasDeclaration.create({
export: true,
name: Name.responseName(operationId, statusCode),
type: factory.TypeReferenceNode.create({
name: context.getReferenceName(currentPoint, `${reference.path}/Content`),
name: name,
}),
}),
);
Expand Down
6 changes: 3 additions & 3 deletions src/Converter/v3/components/Schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ export const generateNamespace = (
if (Guard.isReference(schema)) {
const reference = Reference.generate<OpenApi.Schema>(entryPoint, currentPoint, schema);
if (reference.type === "local") {
const referenceName = context.getReferenceName(currentPoint, reference.path);
const { maybeResolvedName } = context.resolveReferencePath(currentPoint, reference.path);
store.addStatement(`${basePath}/${name}`, {
type: "typeAlias",
name: name,
value: factory.TypeAliasDeclaration.create({
export: true,
name: name,
type: factory.TypeReferenceNode.create({
name: referenceName,
name: maybeResolvedName,
}),
}),
});
Expand All @@ -58,7 +58,7 @@ export const generateNamespace = (
name: name,
comment: reference.data.description,
type: factory.TypeReferenceNode.create({
name: context.getReferenceName(currentPoint, reference.path),
name: context.resolveReferencePath(currentPoint, reference.path).name,
}),
}),
});
Expand Down
10 changes: 8 additions & 2 deletions src/Converter/v3/store/Store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export interface Type {
dumpOperationState: (filename: string) => void;
getNoReferenceOperationState: () => Operation.State;
getPathItem: (localPath: string) => OpenApi.PathItem;
isAfterDefined: (referencePath: string) => boolean;
}

export const create = (factory: Factory.Type, rootDocument: OpenApi.Document): Type => {
Expand Down Expand Up @@ -79,8 +80,12 @@ export const create = (factory: Factory.Type, rootDocument: OpenApi.Document): T
};

const hasStatement = (path: string, types: Def.Statement<A, B, C>["type"][]): boolean => {
const targetPath = relative("components", path);
return types.some(type => !!PropAccess.get(state.components, type, targetPath));
const alreadyRegistered = types.some(type => !!getStatement(path, type));
return alreadyRegistered;
};

const isAfterDefined = (referencePath: string) => {
return !!Dot.get(state.document, referencePath.replace(/\//g, "."));
};

const addStatement = (path: string, statement: Def.Statement<A, B, C>): void => {
Expand Down Expand Up @@ -191,5 +196,6 @@ export const create = (factory: Factory.Type, rootDocument: OpenApi.Document): T
addAdditionalStatement,
dumpOperationState,
getPathItem,
isAfterDefined,
};
};
18 changes: 12 additions & 6 deletions src/Converter/v3/toTypeNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,15 @@ import * as Guard from "./Guard";
import { OpenApi } from "./types";
import { ObjectSchemaWithAdditionalProperties } from "./types";

export interface ResolveReferencePath {
name: string;
maybeResolvedName: string;
unresolvedPaths: string[];
}

export interface Context {
setReferenceHandler: (reference: Reference.Type<OpenApi.Schema | OpenApi.JSONSchemaDefinition>) => void;
getReferenceName: (currentPoint: string, referencePath: string) => string;
setReferenceHandler: (currentPoint: string, reference: Reference.Type<OpenApi.Schema | OpenApi.JSONSchemaDefinition>) => void;
resolveReferencePath: (currentPoint: string, referencePath: string) => ResolveReferencePath;
}

export type Convert = (
Expand Down Expand Up @@ -84,15 +90,15 @@ export const convert: Convert = (
const reference = Reference.generate<OpenApi.Schema | OpenApi.JSONSchemaDefinition>(entryPoint, currentPoint, schema);
if (reference.type === "local") {
// Type Aliasを作成 (or すでにある場合は作成しない)
context.setReferenceHandler(reference);
return factory.TypeReferenceNode.create({ name: context.getReferenceName(currentPoint, reference.path) });
context.setReferenceHandler(currentPoint, reference);
return factory.TypeReferenceNode.create({ name: context.resolveReferencePath(currentPoint, reference.path).maybeResolvedName });
}
// サポートしているディレクトリに対して存在する場合
if (reference.componentName) {
// Type AliasもしくはInterfaceを作成
context.setReferenceHandler(reference);
context.setReferenceHandler(currentPoint, reference);
// Aliasを貼る
return factory.TypeReferenceNode.create({ name: context.getReferenceName(currentPoint, reference.path) });
return factory.TypeReferenceNode.create({ name: context.resolveReferencePath(currentPoint, reference.path).name });
}
// サポートしていないディレクトリに存在する場合、直接Interface、もしくはTypeAliasを作成
return convert(entryPoint, reference.referencePoint, factory, reference.data, context, { parent: schema });
Expand Down
12 changes: 12 additions & 0 deletions test/__tests__/__snapshots__/snapshot-test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,18 @@ export namespace Schemas {
}
export type LocalRefOneOfType = Schemas.StringType | Schemas.NumberType | Schemas.ObjectHasPropertiesType | Schemas.LocalRefObjectProperties;
export type LocalRefAllOfType = Schemas.StringType & Schemas.NumberType & Schemas.ObjectHasPropertiesType & Schemas.LocalRefObjectProperties;
export type LocalReferenceBeforeResolvedSchema1 = Schemas.UnresolvedTarget1;
export type UnresolvedTarget1 = boolean;
export type LocalReferenceBeforeResolvedSchema2 = Schemas.UnresolvedTarget2;
export type UnresolvedTarget2 = Schemas.UnresolvedTarget3;
export type UnresolvedTarget3 = number;
export interface LocalReferenceBeforeResolvedSchema3 {
unresolvedTarget4?: Schemas.UnresolvedTarget4;
}
export interface UnresolvedTarget4 {
unresolvedTarget5?: Schemas.UnresolvedTarget5;
}
export type UnresolvedTarget5 = string;
export type RemoteString = string;
export type RemoteRefString = Schemas.RemoteString;
export namespace Level1 {
Expand Down
22 changes: 22 additions & 0 deletions test/api.test.domain/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,28 @@ components:
- $ref: "#/components/schemas/NumberType"
- $ref: "#/components/schemas/ObjectHasPropertiesType"
- $ref: "#/components/schemas/LocalRefObjectProperties"
LocalReferenceBeforeResolvedSchema1:
$ref: "#/components/schemas/UnresolvedTarget1"
UnresolvedTarget1:
type: boolean
LocalReferenceBeforeResolvedSchema2:
$ref: "#/components/schemas/UnresolvedTarget2"
UnresolvedTarget2:
$ref: "#/components/schemas/UnresolvedTarget3"
UnresolvedTarget3:
type: number
LocalReferenceBeforeResolvedSchema3:
type: object
properties:
unresolvedTarget4:
$ref: "#/components/schemas/UnresolvedTarget4"
UnresolvedTarget4:
type: object
properties:
unresolvedTarget5:
$ref: "#/components/schemas/UnresolvedTarget5"
UnresolvedTarget5:
type: string
RemoteRefString:
$ref: "./components/schemas/RemoteString.yml"
RemoteRefBoolean:
Expand Down