Skip to content

Commit 151ab06

Browse files
Add registerRouteHandlersByTag function and Tag type generation
Co-authored-by: jasonblanchard <1238532+jasonblanchard@users.noreply.github.com>
1 parent eb8050c commit 151ab06

File tree

6 files changed

+403
-1
lines changed

6 files changed

+403
-1
lines changed

examples/docs/gen/server.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,30 @@ export function registerRouteHandlers<Req, Res>(server: Server<Req, Res>): Route
7272
},
7373
]
7474
}
75+
76+
export type Tag = null;
77+
78+
export function registerRouteHandlersByTag<Req, Res>(tag: Tag, server: Partial<Server<Req, Res>>): Route[] {
79+
const routes: Route[] = [];
80+
81+
switch (tag) {
82+
case null:
83+
if (server.makePetSpeak) {
84+
routes.push({
85+
method: "post",
86+
path: "/speak/{petId}",
87+
handler: server.makePetSpeak as Route["handler"],
88+
});
89+
}
90+
if (server.uhoh) {
91+
routes.push({
92+
method: "get",
93+
path: "/uhoh",
94+
handler: server.uhoh as Route["handler"],
95+
});
96+
}
97+
break;
98+
}
99+
100+
return routes;
101+
}

examples/kitchensink/gen/server.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,3 +217,58 @@ export function registerRouteHandlers<Req, Res>(server: Server<Req, Res>): Route
217217
},
218218
]
219219
}
220+
221+
export type Tag = null;
222+
223+
export function registerRouteHandlersByTag<Req, Res>(tag: Tag, server: Partial<Server<Req, Res>>): Route[] {
224+
const routes: Route[] = [];
225+
226+
switch (tag) {
227+
case null:
228+
if (server.listPets) {
229+
routes.push({
230+
method: "get",
231+
path: "/pets",
232+
handler: server.listPets as Route["handler"],
233+
});
234+
}
235+
if (server.getPetById) {
236+
routes.push({
237+
method: "get",
238+
path: "/pet/{petId}",
239+
handler: server.getPetById as Route["handler"],
240+
});
241+
}
242+
if (server.updatePetWithForm) {
243+
routes.push({
244+
method: "post",
245+
path: "/pet/{petId}",
246+
handler: server.updatePetWithForm as Route["handler"],
247+
});
248+
}
249+
if (server.mixedContentTypes) {
250+
routes.push({
251+
method: "post",
252+
path: "/pet/{petId}/mixed-content-types",
253+
handler: server.mixedContentTypes as Route["handler"],
254+
});
255+
}
256+
if (server.getPetImage) {
257+
routes.push({
258+
method: "get",
259+
path: "/pet/{petId}/image",
260+
handler: server.getPetImage as Route["handler"],
261+
});
262+
}
263+
if (server.getPetWebpage) {
264+
routes.push({
265+
method: "get",
266+
path: "/pet/{petId}/webpage",
267+
handler: server.getPetWebpage as Route["handler"],
268+
});
269+
}
270+
break;
271+
}
272+
273+
return routes;
274+
}

examples/petstore/gen/server.ts

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,3 +743,153 @@ export function registerRouteHandlers<Req, Res>(server: Server<Req, Res>): Route
743743
},
744744
]
745745
}
746+
747+
export type Tag = "pet" | "store" | "user";
748+
749+
export function registerRouteHandlersByTag<Req, Res>(tag: Tag, server: Partial<Server<Req, Res>>): Route[] {
750+
const routes: Route[] = [];
751+
752+
switch (tag) {
753+
case "pet":
754+
if (server.updatePet) {
755+
routes.push({
756+
method: "put",
757+
path: "/pet",
758+
handler: server.updatePet as Route["handler"],
759+
});
760+
}
761+
if (server.addPet) {
762+
routes.push({
763+
method: "post",
764+
path: "/pet",
765+
handler: server.addPet as Route["handler"],
766+
});
767+
}
768+
if (server.findPetsByStatus) {
769+
routes.push({
770+
method: "get",
771+
path: "/pet/findByStatus",
772+
handler: server.findPetsByStatus as Route["handler"],
773+
});
774+
}
775+
if (server.findPetsByTags) {
776+
routes.push({
777+
method: "get",
778+
path: "/pet/findByTags",
779+
handler: server.findPetsByTags as Route["handler"],
780+
});
781+
}
782+
if (server.getPetById) {
783+
routes.push({
784+
method: "get",
785+
path: "/pet/{petId}",
786+
handler: server.getPetById as Route["handler"],
787+
});
788+
}
789+
if (server.updatePetWithForm) {
790+
routes.push({
791+
method: "post",
792+
path: "/pet/{petId}",
793+
handler: server.updatePetWithForm as Route["handler"],
794+
});
795+
}
796+
if (server.deletePet) {
797+
routes.push({
798+
method: "delete",
799+
path: "/pet/{petId}",
800+
handler: server.deletePet as Route["handler"],
801+
});
802+
}
803+
if (server.uploadFile) {
804+
routes.push({
805+
method: "post",
806+
path: "/pet/{petId}/uploadImage",
807+
handler: server.uploadFile as Route["handler"],
808+
});
809+
}
810+
break;
811+
case "store":
812+
if (server.getInventory) {
813+
routes.push({
814+
method: "get",
815+
path: "/store/inventory",
816+
handler: server.getInventory as Route["handler"],
817+
});
818+
}
819+
if (server.placeOrder) {
820+
routes.push({
821+
method: "post",
822+
path: "/store/order",
823+
handler: server.placeOrder as Route["handler"],
824+
});
825+
}
826+
if (server.getOrderById) {
827+
routes.push({
828+
method: "get",
829+
path: "/store/order/{orderId}",
830+
handler: server.getOrderById as Route["handler"],
831+
});
832+
}
833+
if (server.deleteOrder) {
834+
routes.push({
835+
method: "delete",
836+
path: "/store/order/{orderId}",
837+
handler: server.deleteOrder as Route["handler"],
838+
});
839+
}
840+
break;
841+
case "user":
842+
if (server.createUser) {
843+
routes.push({
844+
method: "post",
845+
path: "/user",
846+
handler: server.createUser as Route["handler"],
847+
});
848+
}
849+
if (server.createUsersWithListInput) {
850+
routes.push({
851+
method: "post",
852+
path: "/user/createWithList",
853+
handler: server.createUsersWithListInput as Route["handler"],
854+
});
855+
}
856+
if (server.loginUser) {
857+
routes.push({
858+
method: "get",
859+
path: "/user/login",
860+
handler: server.loginUser as Route["handler"],
861+
});
862+
}
863+
if (server.logoutUser) {
864+
routes.push({
865+
method: "get",
866+
path: "/user/logout",
867+
handler: server.logoutUser as Route["handler"],
868+
});
869+
}
870+
if (server.getUserByName) {
871+
routes.push({
872+
method: "get",
873+
path: "/user/{username}",
874+
handler: server.getUserByName as Route["handler"],
875+
});
876+
}
877+
if (server.updateUser) {
878+
routes.push({
879+
method: "put",
880+
path: "/user/{username}",
881+
handler: server.updateUser as Route["handler"],
882+
});
883+
}
884+
if (server.deleteUser) {
885+
routes.push({
886+
method: "delete",
887+
path: "/user/{username}",
888+
handler: server.deleteUser as Route["handler"],
889+
});
890+
}
891+
break;
892+
}
893+
894+
return routes;
895+
}

packages/openapi-typescript-server-runtime/src/schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export const OpenAPISpec = z.object({
2929
summary: z.string().optional(),
3030
description: z.string().optional(),
3131
operationId: z.string().optional(),
32+
tags: z.array(z.string()).optional(),
3233
parameters: z
3334
.array(
3435
z.object({

packages/openapi-typescript-server/bin/cli/index.cjs

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ function generate(spec, types, outpath, version) {
319319
moduleSpecifier: "openapi-typescript-server-runtime"
320320
});
321321
const operationsById = {};
322+
const allTags = /* @__PURE__ */ new Set();
322323
for (const path in spec.paths) {
323324
const pathSpec = spec.paths[path];
324325
for (const method in pathSpec) {
@@ -408,13 +409,16 @@ function generate(spec, types, outpath, version) {
408409
isExported: true,
409410
type: `Promise<${responseVariantInterfaceNames.join(" | ")}>`
410411
});
412+
const operationTags = operation.tags || [];
413+
operationTags.forEach((tag) => allTags.add(tag));
411414
operationsById[operationId] = {
412415
path,
413416
method,
414417
args: argsInterface.getName(),
415418
result: resultType.getName(),
416419
summary: operation.summary,
417-
description: operation.description
420+
description: operation.description,
421+
tags: operationTags
418422
};
419423
sourceFile.addFunction({
420424
name: `${operationId}Unimplemented`,
@@ -481,6 +485,75 @@ function generate(spec, types, outpath, version) {
481485
writer.writeLine("]");
482486
}
483487
});
488+
const tagValues = Array.from(allTags).map((tag) => `"${tag}"`);
489+
const hasUntaggedOperations = Object.values(operationsById).some(
490+
(op) => !op.tags || op.tags.length === 0
491+
);
492+
if (hasUntaggedOperations) {
493+
tagValues.push("null");
494+
}
495+
if (tagValues.length > 0) {
496+
sourceFile.addTypeAlias({
497+
name: "Tag",
498+
isExported: true,
499+
type: tagValues.join(" | ")
500+
});
501+
}
502+
const tagToOperations = {};
503+
Object.entries(operationsById).forEach(([operationId, { tags }]) => {
504+
if (!tags || tags.length === 0) {
505+
if (!tagToOperations["null"]) {
506+
tagToOperations["null"] = [];
507+
}
508+
tagToOperations["null"].push(operationId);
509+
} else {
510+
tags.forEach((tag) => {
511+
if (!tagToOperations[tag]) {
512+
tagToOperations[tag] = [];
513+
}
514+
tagToOperations[tag].push(operationId);
515+
});
516+
}
517+
});
518+
sourceFile.addFunction({
519+
name: "registerRouteHandlersByTag",
520+
isExported: true,
521+
parameters: [
522+
{ name: "tag", type: "Tag" },
523+
{ name: "server", type: "Partial<Server<Req, Res>>" }
524+
],
525+
typeParameters: [{ name: "Req" }, { name: "Res" }],
526+
returnType: "Route[]",
527+
statements: (writer) => {
528+
writer.writeLine("const routes: Route[] = [];");
529+
writer.writeLine("");
530+
if (Object.keys(tagToOperations).length > 0) {
531+
writer.writeLine("switch (tag) {");
532+
Object.entries(tagToOperations).forEach(([tag, operations]) => {
533+
const caseValue = tag === "null" ? "null" : `"${tag}"`;
534+
writer.writeLine(`case ${caseValue}:`);
535+
operations.forEach((operationId) => {
536+
const op = operationsById[operationId];
537+
if (op) {
538+
writer.writeLine(`if (server.${operationId}) {`);
539+
writer.writeLine("routes.push({");
540+
writer.writeLine(`method: "${op.method}",`);
541+
writer.writeLine(`path: "${op.path}",`);
542+
writer.writeLine(
543+
`handler: server.${operationId} as Route["handler"],`
544+
);
545+
writer.writeLine("});");
546+
writer.writeLine("}");
547+
}
548+
});
549+
writer.writeLine("break;");
550+
});
551+
writer.writeLine("}");
552+
}
553+
writer.writeLine("");
554+
writer.writeLine("return routes;");
555+
}
556+
});
484557
sourceFile.insertText(
485558
0,
486559
`/**

0 commit comments

Comments
 (0)