Skip to content

Commit 17d0bb9

Browse files
start example
1 parent 24df11f commit 17d0bb9

File tree

10 files changed

+1244
-0
lines changed

10 files changed

+1244
-0
lines changed

.github/workflows/version.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ jobs:
6666
npm install --save-exact --workspace docs-example openapi-typescript-server@${{ env.PACKAGE_VERSION }} openapi-typescript-server-express@${{ env.PACKAGE_VERSION }}
6767
npm install --save-exact --workspace docs-example openapi-typescript-server@${{ env.PACKAGE_VERSION }} openapi-typescript-server-express@${{ env.PACKAGE_VERSION }}
6868
69+
npm install --save-exact --workspace tags-example openapi-typescript-server@${{ env.PACKAGE_VERSION }} openapi-typescript-server-express@${{ env.PACKAGE_VERSION }}
70+
npm install --save-exact --workspace tags-example openapi-typescript-server@${{ env.PACKAGE_VERSION }} openapi-typescript-server-express@${{ env.PACKAGE_VERSION }}
71+
6972
- name: Build the packages
7073
run: npm run build:packages
7174

examples/tags/api.ts

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import type * as ServerTypes from "./gen/server.ts";
2+
import * as server from "./gen/server.ts";
3+
import type { Request, Response } from "express";
4+
import { promises as fs } from "fs";
5+
import { join } from "path";
6+
import { fileURLToPath } from "url";
7+
8+
const __filename = fileURLToPath(import.meta.url);
9+
const __dirname = join(__filename, "..");
10+
11+
const API: ServerTypes.Server<Request, Response> = {
12+
getPetById: async ({ parameters, req }): ServerTypes.GetPetByIdResult => {
13+
if (parameters.path.petId === 42) {
14+
return {
15+
content: {
16+
default: {
17+
"application/json": {
18+
message: "Cannot get that pet",
19+
},
20+
},
21+
},
22+
status: 503,
23+
};
24+
}
25+
26+
if (parameters.path.petId === 500) {
27+
throw new Error("Cannot get that pet");
28+
}
29+
30+
return {
31+
content: {
32+
200: {
33+
"application/json": {
34+
pet: { id: parameters.path.petId, name: "dog" },
35+
},
36+
},
37+
},
38+
};
39+
},
40+
41+
updatePetWithForm: async ({
42+
parameters,
43+
requestBody,
44+
}): ServerTypes.UpdatePetWithFormResult => {
45+
const { petId } = parameters.path;
46+
const { name } = parameters.query ?? {};
47+
const { status } = requestBody.content;
48+
49+
return {
50+
content: {
51+
200: {
52+
"application/json": {
53+
pet: { id: petId, name: name || "dog", status },
54+
},
55+
},
56+
},
57+
};
58+
},
59+
60+
mixedContentTypes: async ({
61+
parameters,
62+
requestBody,
63+
contentType,
64+
}): ServerTypes.MixedContentTypesResult => {
65+
const { petId } = parameters.path;
66+
let status: "available" | "pending" | "sold" | undefined;
67+
68+
// Since each content type has different structures,
69+
// use the request content type and requestBody discriminator to narrow the type in each case.
70+
71+
if (
72+
contentType === "application/json" &&
73+
requestBody.mediaType === "application/json"
74+
) {
75+
status = requestBody.content.jsonstatus;
76+
}
77+
78+
if (
79+
contentType == "application/xml" &&
80+
requestBody.mediaType === "application/xml"
81+
) {
82+
status = requestBody.content.xmlstatus;
83+
}
84+
85+
return {
86+
content: {
87+
200: {
88+
"application/json": {
89+
pet: { id: petId, name: "dog", status },
90+
},
91+
},
92+
},
93+
};
94+
},
95+
96+
listPets: server.listPetsUnimplemented,
97+
98+
getPetImage: async (): ServerTypes.GetPetImageResult => {
99+
const image = await fs.readFile(join(__dirname, `./cat.jpeg`), {
100+
encoding: "base64",
101+
});
102+
103+
return {
104+
content: {
105+
200: {
106+
"image/jpeg": image,
107+
},
108+
},
109+
};
110+
},
111+
112+
getPetWebpage: async ({ parameters }): ServerTypes.GetPetWebpageResult => {
113+
const { petId } = parameters.path;
114+
return {
115+
content: {
116+
200: {
117+
"text/html": `<html><body><h1>Hello, pet ${petId}!</h1></body></html>`,
118+
},
119+
},
120+
};
121+
},
122+
};
123+
124+
export default API;

examples/tags/app.test.ts

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import { describe, it } from "node:test";
2+
import assert from "node:assert";
3+
import makeApp from "./app.ts";
4+
import request from "supertest";
5+
import { json2xml } from "xml-js";
6+
7+
const app = makeApp();
8+
9+
describe("getPetById", async () => {
10+
it("returns 200", async () => {
11+
const response = await request(app)
12+
.get("/api/v3/pet/123")
13+
.set("Accept", "application/json");
14+
15+
assert.equal(response.status, 200);
16+
assert.deepEqual(response.body, {
17+
pet: {
18+
id: 123,
19+
name: "dog",
20+
},
21+
});
22+
});
23+
24+
it("returns 503 via default response and custom status code", async () => {
25+
const response = await request(app)
26+
.get("/api/v3/pet/42")
27+
.set("Accept", "application/json");
28+
29+
assert.equal(response.status, 503);
30+
assert.deepEqual(response.body, {
31+
message: "Cannot get that pet",
32+
});
33+
});
34+
});
35+
36+
describe("updatePetWithForm", async () => {
37+
it("returns 200", async () => {
38+
const response = await request(app)
39+
.post("/api/v3/pet/123?name=cat")
40+
.set("Accept", "application/json")
41+
.send({ status: "sold" });
42+
43+
assert.equal(response.status, 200);
44+
assert.deepEqual(response.body, {
45+
pet: {
46+
id: 123,
47+
name: "cat",
48+
status: "sold",
49+
},
50+
});
51+
});
52+
53+
it("accepts xml input", async () => {
54+
const xmlData = json2xml(JSON.stringify({ status: "sold" }), {
55+
compact: true,
56+
ignoreComment: true,
57+
spaces: 4,
58+
});
59+
60+
const response = await request(app)
61+
.post("/api/v3/pet/123?name=cat")
62+
.set("Content-Type", "application/xml")
63+
.send(xmlData);
64+
65+
assert.equal(response.status, 200);
66+
assert.deepEqual(response.body, {
67+
pet: {
68+
id: 123,
69+
name: "cat",
70+
status: "sold",
71+
},
72+
});
73+
});
74+
75+
it("accepts form-urlencoded input", async () => {
76+
const response = await request(app)
77+
.post("/api/v3/pet/123?name=cat")
78+
.set("Content-Type", "application/x-www-form-urlencoded")
79+
.send("status=sold");
80+
81+
assert.equal(response.status, 200);
82+
assert.deepEqual(response.body, {
83+
pet: {
84+
id: 123,
85+
name: "cat",
86+
status: "sold",
87+
},
88+
});
89+
});
90+
});
91+
92+
describe("mixed content types with different structures", async () => {
93+
it("handles json by default", async () => {
94+
const response = await request(app)
95+
.post("/api/v3/pet/123/mixed-content-types")
96+
.send({ jsonstatus: "sold" });
97+
98+
assert.equal(response.status, 200);
99+
assert.deepEqual(response.body, {
100+
pet: {
101+
id: 123,
102+
name: "dog",
103+
status: "sold",
104+
},
105+
});
106+
});
107+
108+
it("handles xml", async () => {
109+
const xmlData = json2xml(JSON.stringify({ xmlstatus: "sold" }), {
110+
compact: true,
111+
ignoreComment: true,
112+
spaces: 4,
113+
});
114+
115+
const response = await request(app)
116+
.post("/api/v3/pet/123/mixed-content-types")
117+
.set("Content-Type", "application/xml")
118+
.send(xmlData);
119+
120+
assert.equal(response.status, 200);
121+
assert.deepEqual(response.body, {
122+
pet: {
123+
id: 123,
124+
name: "dog",
125+
status: "sold",
126+
},
127+
});
128+
});
129+
});
130+
131+
describe("listPets", async () => {
132+
it("propagates unimplemented error", async () => {
133+
const response = await request(app)
134+
.get("/api/v3/pets")
135+
.set("Accept", "application/json");
136+
137+
assert.equal(response.status, 501);
138+
assert.equal(response.body.message, "Not Implemented");
139+
});
140+
141+
describe("getPetImage", async () => {
142+
it("returns 200", async () => {
143+
const response = await request(app)
144+
.get("/api/v3/pet/123/image")
145+
.set("Accept", "image/jpeg");
146+
147+
assert.equal(response.status, 200);
148+
assert(response.body);
149+
assert.equal(response.headers["content-type"], "image/jpeg");
150+
});
151+
});
152+
153+
describe("getPetWebpage", async () => {
154+
it("returns 200", async () => {
155+
const response = await request(app)
156+
.get("/api/v3/pet/123/webpage")
157+
.set("Accept", "text/html");
158+
159+
assert.equal(response.status, 200);
160+
assert.equal(
161+
response.text,
162+
"<html><body><h1>Hello, pet 123!</h1></body></html>",
163+
);
164+
});
165+
});
166+
});

examples/tags/app.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import express from "express";
2+
import type { Request, Response, NextFunction } from "express";
3+
import { registerRouteHandlers } from "./gen/server.ts";
4+
import registerRoutes from "openapi-typescript-server-express";
5+
import API from "./api.ts";
6+
import OpenApiValidator from "express-openapi-validator";
7+
import { NotImplementedError } from "openapi-typescript-server-runtime";
8+
import xmlparser from "express-xml-bodyparser";
9+
10+
export default function makeApp() {
11+
const app = express();
12+
13+
app.get("/", (_req, res) => {
14+
res.send("Hello World!");
15+
});
16+
17+
app.use(express.json());
18+
app.use(xmlparser());
19+
app.use(express.urlencoded({ extended: true }));
20+
21+
const apiRouter = express();
22+
23+
apiRouter.use(
24+
OpenApiValidator.middleware({
25+
apiSpec: "./openapi.yaml",
26+
validateResponses: false,
27+
}),
28+
);
29+
registerRoutes(registerRouteHandlers(API), apiRouter, {
30+
serializers: {
31+
"image/jpeg": (content) => {
32+
return Buffer.from(content, "base64");
33+
},
34+
},
35+
});
36+
37+
app.use("/api/v3", apiRouter);
38+
39+
interface ValidationError extends Error {
40+
status?: number;
41+
errors?: Record<string, unknown>[];
42+
}
43+
44+
app.use((err: unknown, req: Request, res: Response, next: NextFunction) => {
45+
console.error("+++", err);
46+
47+
const validationError = err as ValidationError;
48+
if (validationError.status && validationError.errors) {
49+
res.status(validationError.status || 500).json({
50+
message: validationError.message,
51+
errors: validationError.errors,
52+
});
53+
return;
54+
}
55+
56+
if (err instanceof NotImplementedError) {
57+
res.status(501).json({
58+
message: "Not Implemented",
59+
});
60+
return;
61+
}
62+
63+
res.status(500).json({
64+
message: "Internal Server Error",
65+
});
66+
return;
67+
});
68+
69+
return app;
70+
}

examples/tags/cat.jpeg

44.3 KB
Loading

examples/tags/cmd/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import makeApp from "../app.ts";
2+
3+
makeApp().listen(8080, () => {
4+
console.log("Server running on port 8080");
5+
});

0 commit comments

Comments
 (0)