Skip to content

Commit

Permalink
Reusing OnEndpoint type for extracting routing hooks (#2237)
Browse files Browse the repository at this point in the history
  • Loading branch information
RobinTail authored Dec 4, 2024
1 parent 95dba0a commit 63ea6e9
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 88 deletions.
103 changes: 50 additions & 53 deletions src/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import { Method, methods } from "./method";
import { contentTypes } from "./content-type";
import { loadPeer } from "./peer-helpers";
import { Routing } from "./routing";
import { walkRouting } from "./routing-walker";
import { OnEndpoint, walkRouting } from "./routing-walker";
import { HandlingRules } from "./schema-walker";
import { zodToTs } from "./zts";
import { ZTSContext, printNode, addJsDocComment } from "./zts-helpers";
Expand Down Expand Up @@ -174,58 +174,55 @@ export class Integration {
const commons = { makeAlias: this.makeAlias.bind(this), optionalPropStyle };
const ctxIn = { brandHandling, ctx: { ...commons, isResponse: false } };
const ctxOut = { brandHandling, ctx: { ...commons, isResponse: true } };
walkRouting({
routing,
onEndpoint: (endpoint, path, method) => {
const entitle = makeCleanId.bind(null, method, path); // clean id with method+path prefix
const input = makeType(
entitle("input"),
zodToTs(endpoint.getSchema("input"), ctxIn),
);
const positiveSchema = endpoint
.getResponses("positive")
.map(({ schema, mimeTypes }) => (mimeTypes ? schema : noContent))
.reduce((agg, schema) => agg.or(schema));
const positiveResponse = makeType(
entitle("positive.response"),
zodToTs(positiveSchema, ctxOut),
);
const negativeSchema = endpoint
.getResponses("negative")
.map(({ schema, mimeTypes }) => (mimeTypes ? schema : noContent))
.reduce((agg, schema) => agg.or(schema));
const negativeResponse = makeType(
entitle("negative.response"),
zodToTs(negativeSchema, ctxOut),
);
const genericResponse = makeType(
entitle("response"),
f.createUnionTypeNode([
f.createTypeReferenceNode(positiveResponse.name.text),
f.createTypeReferenceNode(negativeResponse.name.text),
]),
);
this.program.push(
input,
positiveResponse,
negativeResponse,
genericResponse,
);
this.paths.push(path);
const isJson = endpoint
.getResponses("positive")
.some(({ mimeTypes }) => mimeTypes?.includes(contentTypes.json));
this.registry.set(quoteProp(method, path), {
input: input.name.text,
positive: positiveResponse.name.text,
negative: negativeResponse.name.text,
response: genericResponse.name.text,
tags: endpoint.getTags(),
isJson,
});
},
});

const onEndpoint: OnEndpoint = (endpoint, path, method) => {
const entitle = makeCleanId.bind(null, method, path); // clean id with method+path prefix
const input = makeType(
entitle("input"),
zodToTs(endpoint.getSchema("input"), ctxIn),
);
const positiveSchema = endpoint
.getResponses("positive")
.map(({ schema, mimeTypes }) => (mimeTypes ? schema : noContent))
.reduce((agg, schema) => agg.or(schema));
const positiveResponse = makeType(
entitle("positive.response"),
zodToTs(positiveSchema, ctxOut),
);
const negativeSchema = endpoint
.getResponses("negative")
.map(({ schema, mimeTypes }) => (mimeTypes ? schema : noContent))
.reduce((agg, schema) => agg.or(schema));
const negativeResponse = makeType(
entitle("negative.response"),
zodToTs(negativeSchema, ctxOut),
);
const genericResponse = makeType(
entitle("response"),
f.createUnionTypeNode([
f.createTypeReferenceNode(positiveResponse.name.text),
f.createTypeReferenceNode(negativeResponse.name.text),
]),
);
this.program.push(
input,
positiveResponse,
negativeResponse,
genericResponse,
);
this.paths.push(path);
const isJson = endpoint
.getResponses("positive")
.some(({ mimeTypes }) => mimeTypes?.includes(contentTypes.json));
this.registry.set(quoteProp(method, path), {
input: input.name.text,
positive: positiveResponse.name.text,
negative: negativeResponse.name.text,
response: genericResponse.name.text,
tags: endpoint.getTags(),
isJson,
});
};
walkRouting({ routing, onEndpoint });
this.program.unshift(...this.aliases.values());

// export type Path = "/v1/user/retrieve" | ___;
Expand Down
67 changes: 32 additions & 35 deletions src/routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { DependsOnMethod } from "./depends-on-method";
import { Diagnostics } from "./diagnostics";
import { AbstractEndpoint } from "./endpoint";
import { AuxMethod, Method } from "./method";
import { walkRouting } from "./routing-walker";
import { OnEndpoint, walkRouting } from "./routing-walker";
import { ServeStatic } from "./serve-static";
import { GetLogger } from "./server-helpers";

Expand All @@ -31,39 +31,36 @@ export const initRouting = ({
}) => {
const doc = new Diagnostics(getLogger());
const corsedPaths = new Set<string>();
walkRouting({
routing,
onStatic: (path, handler) => void app.use(path, handler),
onEndpoint: (endpoint, path, method, siblingMethods) => {
if (!isProduction()) doc.check(endpoint, { path, method });
const matchingParsers = parsers?.[endpoint.getRequestType()] || [];
const handler: RequestHandler = async (request, response) => {
const logger = getLogger(request);
if (config.cors) {
const accessMethods: Array<Method | AuxMethod> = [
method,
...(siblingMethods || []),
"options",
];
const methodsLine = accessMethods.join(", ").toUpperCase();
const defaultHeaders: Record<string, string> = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": methodsLine,
"Access-Control-Allow-Headers": "content-type",
};
const headers =
typeof config.cors === "function"
? await config.cors({ request, endpoint, logger, defaultHeaders })
: defaultHeaders;
for (const key in headers) response.set(key, headers[key]);
}
return endpoint.execute({ request, response, logger, config });
};
if (config.cors && !corsedPaths.has(path)) {
app.options(path, ...matchingParsers, handler);
corsedPaths.add(path);
const onEndpoint: OnEndpoint = (endpoint, path, method, siblingMethods) => {
if (!isProduction()) doc.check(endpoint, { path, method });
const matchingParsers = parsers?.[endpoint.getRequestType()] || [];
const handler: RequestHandler = async (request, response) => {
const logger = getLogger(request);
if (config.cors) {
const accessMethods: Array<Method | AuxMethod> = [
method,
...(siblingMethods || []),
"options",
];
const methodsLine = accessMethods.join(", ").toUpperCase();
const defaultHeaders: Record<string, string> = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": methodsLine,
"Access-Control-Allow-Headers": "content-type",
};
const headers =
typeof config.cors === "function"
? await config.cors({ request, endpoint, logger, defaultHeaders })
: defaultHeaders;
for (const key in headers) response.set(key, headers[key]);
}
app[method](path, ...matchingParsers, handler);
},
});
return endpoint.execute({ request, response, logger, config });
};
if (config.cors && !corsedPaths.has(path)) {
app.options(path, ...matchingParsers, handler);
corsedPaths.add(path);
}
app[method](path, ...matchingParsers, handler);
};
walkRouting({ routing, onEndpoint, onStatic: app.use.bind(app) });
};

0 comments on commit 63ea6e9

Please sign in to comment.