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

Prefer Map dictionaries #1731

Merged
merged 11 commits into from
May 5, 2024
40 changes: 18 additions & 22 deletions src/documentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,8 @@ interface DocumentationParams {
}

export class Documentation extends OpenApiBuilder {
protected lastSecuritySchemaIds: Partial<Record<SecuritySchemeType, number>> =
{};
protected lastOperationIdSuffixes: Record<string, number> = {};
protected lastSecuritySchemaIds = new Map<SecuritySchemeType, number>();
protected lastOperationIdSuffixes = new Map<string, number>();

protected makeRef(
name: string,
Expand All @@ -88,28 +87,27 @@ export class Documentation extends OpenApiBuilder {
protected ensureUniqOperationId(
path: string,
method: Method,
userDefinedOperationId?: string,
userDefined?: string,
) {
if (userDefinedOperationId) {
assert(
!(userDefinedOperationId in this.lastOperationIdSuffixes),
const operationId = userDefined || makeCleanId(method, path);
let lastSuffix = this.lastOperationIdSuffixes.get(operationId);
if (lastSuffix === undefined) {
this.lastOperationIdSuffixes.set(operationId, 1);
return operationId;
}
if (userDefined) {
assert.fail(
new DocumentationError({
message: `Duplicated operationId: "${userDefinedOperationId}"`,
message: `Duplicated operationId: "${userDefined}"`,
method,
isResponse: false,
path,
}),
);
this.lastOperationIdSuffixes[userDefinedOperationId] = 1;
return userDefinedOperationId;
}
const operationId = makeCleanId(method, path);
if (operationId in this.lastOperationIdSuffixes) {
this.lastOperationIdSuffixes[operationId]++;
return `${operationId}${this.lastOperationIdSuffixes[operationId]}`;
}
this.lastOperationIdSuffixes[operationId] = 1;
return operationId;
lastSuffix++;
this.lastOperationIdSuffixes.set(operationId, lastSuffix);
return `${operationId}${lastSuffix}`;
}

protected ensureUniqSecuritySchemaName(subject: SecuritySchemeObject) {
Expand All @@ -122,11 +120,9 @@ export class Documentation extends OpenApiBuilder {
return name;
}
}
this.lastSecuritySchemaIds[subject.type] =
(this.lastSecuritySchemaIds?.[subject.type] || 0) + 1;
return `${subject.type.toUpperCase()}_${
this.lastSecuritySchemaIds[subject.type]
}`;
const nextId = (this.lastSecuritySchemaIds.get(subject.type) || 0) + 1;
this.lastSecuritySchemaIds.set(subject.type, nextId);
return `${subject.type.toUpperCase()}_${nextId}`;
}

public constructor({
Expand Down
7 changes: 5 additions & 2 deletions src/integration-helpers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import ts from "typescript";
import { chain, toPairs } from "ramda";
import { Method } from "./method";

export const f = ts.factory;

Expand Down Expand Up @@ -74,10 +75,10 @@ export const makeEmptyInitializingConstructor = (
params: ts.ParameterDeclaration[],
) => f.createConstructorDeclaration(undefined, params, f.createBlock([]));

export const makeQuotedProp = (name: string, ref: string) =>
export const makeInterfaceProp = (name: string, ref: string) =>
f.createPropertySignature(
undefined,
`"${name}"`,
name,
undefined,
f.createTypeReferenceNode(ref),
);
Expand Down Expand Up @@ -205,3 +206,5 @@ export const makeObjectKeysReducer = (
initial,
],
);

export const quoteProp = (...parts: [Method, string]) => `"${parts.join(" ")}"`;
74 changes: 40 additions & 34 deletions src/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
makeConst,
makeEmptyInitializingConstructor,
makeIndexedPromise,
makeInterfaceProp,
makeObjectKeysReducer,
makeParam,
makeParams,
Expand All @@ -18,12 +19,12 @@ import {
makePublicLiteralType,
makePublicReadonlyProp,
makePublicType,
makeQuotedProp,
makeRecord,
makeTemplateType,
makeTypeParams,
parametricIndexNode,
protectedReadonlyModifier,
quoteProp,
spacingMiddle,
} from "./integration-helpers";
import { defaultSerializer, makeCleanId } from "./common-helpers";
Expand All @@ -38,13 +39,6 @@ import type Prettier from "prettier";

type IOKind = "input" | "response" | "positive" | "negative";

interface Registry {
[METHOD_PATH: string]: Partial<Record<IOKind, string>> & {
isJson: boolean;
tags: string[];
};
}

interface IntegrationParams {
routing: Routing;
/**
Expand Down Expand Up @@ -95,9 +89,15 @@ interface FormattedPrintingOptions {
export class Integration {
protected program: ts.Node[] = [];
protected usage: Array<ts.Node | string> = [];
protected registry: Registry = {};
protected registry = new Map<
{ method: Method; path: string },
Partial<Record<IOKind, string>> & {
RobinTail marked this conversation as resolved.
Show resolved Hide resolved
isJson: boolean;
tags: string[];
}
>();
protected paths: string[] = [];
protected aliases: Record<string, ts.TypeAliasDeclaration> = {};
protected aliases = new Map<string, ts.TypeAliasDeclaration>();
protected ids = {
pathType: f.createIdentifier("Path"),
methodType: f.createIdentifier("Method"),
Expand Down Expand Up @@ -130,11 +130,11 @@ export class Integration {
protected interfaces: { id: ts.Identifier; kind: IOKind }[] = [];

protected getAlias(name: string): ts.TypeReferenceNode | undefined {
return name in this.aliases ? f.createTypeReferenceNode(name) : undefined;
return this.aliases.has(name) ? f.createTypeReferenceNode(name) : undefined;
}

protected makeAlias(name: string, type: ts.TypeNode): ts.TypeReferenceNode {
this.aliases[name] = createTypeAlias(type, name);
this.aliases.set(name, createTypeAlias(type, name));
return this.getAlias(name)!;
}

Expand Down Expand Up @@ -208,19 +208,22 @@ export class Integration {
this.program.push(createTypeAlias(genericResponse, genericResponseId));
if (method !== "options") {
this.paths.push(path);
this.registry[`${method} ${path}`] = {
input: inputId,
positive: positiveResponseId,
negative: negativeResponseId,
response: genericResponseId,
isJson: endpoint.getMimeTypes("positive").includes(mimeJson),
tags: endpoint.getTags(),
};
this.registry.set(
{ method, path },
{
input: inputId,
positive: positiveResponseId,
negative: negativeResponseId,
response: genericResponseId,
isJson: endpoint.getMimeTypes("positive").includes(mimeJson),
tags: endpoint.getTags(),
},
);
}
},
});

this.program.unshift(...Object.values<ts.Node>(this.aliases));
this.program.unshift(...this.aliases.values());

// export type Path = "/v1/user/retrieve" | ___;
this.program.push(makePublicLiteralType(this.ids.pathType, this.paths));
Expand Down Expand Up @@ -255,17 +258,19 @@ export class Integration {
}
this.interfaces.push({ id: this.ids.responseInterface, kind: "response" });

const registryEntries = Array.from(this.registry.entries());

// export interface Input ___ { "get /v1/user/retrieve": GetV1UserRetrieveInput; }
for (const { id, kind } of this.interfaces) {
this.program.push(
makePublicExtendedInterface(
id,
extenderClause,
Object.keys(this.registry)
.map((methodPath) => {
const reference = this.registry[methodPath][kind];
registryEntries
.map(([{ method, path }, entry]) => {
const reference = entry[kind];
return reference
? makeQuotedProp(methodPath, reference)
? makeInterfaceProp(quoteProp(method, path), reference)
: undefined;
})
.filter(
Expand All @@ -285,10 +290,13 @@ export class Integration {
makeConst(
this.ids.jsonEndpointsConst,
f.createObjectLiteralExpression(
Object.keys(this.registry)
.filter((methodPath) => this.registry[methodPath].isJson)
.map((methodPath) =>
f.createPropertyAssignment(`"${methodPath}"`, f.createTrue()),
registryEntries
.filter(([{}, { isJson }]) => isJson)
.map(([{ method, path }]) =>
f.createPropertyAssignment(
quoteProp(method, path),
f.createTrue(),
),
),
),
),
Expand All @@ -300,13 +308,11 @@ export class Integration {
makeConst(
this.ids.endpointTagsConst,
f.createObjectLiteralExpression(
Object.keys(this.registry).map((methodPath) =>
registryEntries.map(([{ method, path }, { tags }]) =>
f.createPropertyAssignment(
`"${methodPath}"`,
quoteProp(method, path),
f.createArrayLiteralExpression(
this.registry[methodPath].tags.map((tag) =>
f.createStringLiteral(tag),
),
tags.map((tag) => f.createStringLiteral(tag)),
),
),
),
Expand Down