From eea9489efc1625d3acf27fc8b2a39d8e82fecc81 Mon Sep 17 00:00:00 2001 From: OpenClaw Subagent Date: Fri, 20 Mar 2026 11:09:20 +0100 Subject: [PATCH] docs: add X-Render-Time header to OpenAPI spec --- public/openapi.json | 15 +++++++++++++++ src/__tests__/openapi-spec.test.ts | 26 ++++++++++++++++++++++++++ src/routes/convert.ts | 6 ++++++ src/routes/demo.ts | 4 ++++ src/swagger.ts | 7 +++++++ 5 files changed, 58 insertions(+) diff --git a/public/openapi.json b/public/openapi.json index 218a85e..f224875 100644 --- a/public/openapi.json +++ b/public/openapi.json @@ -310,6 +310,9 @@ }, "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" + }, + "X-Render-Time": { + "$ref": "#/components/headers/X-Render-Time" } }, "content": { @@ -405,6 +408,9 @@ }, "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" + }, + "X-Render-Time": { + "$ref": "#/components/headers/X-Render-Time" } }, "content": { @@ -508,6 +514,9 @@ }, "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" + }, + "X-Render-Time": { + "$ref": "#/components/headers/X-Render-Time" } }, "content": { @@ -595,6 +604,9 @@ }, "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" + }, + "X-Render-Time": { + "$ref": "#/components/headers/X-Render-Time" } }, "content": { @@ -693,6 +705,9 @@ }, "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" + }, + "X-Render-Time": { + "$ref": "#/components/headers/X-Render-Time" } }, "content": { diff --git a/src/__tests__/openapi-spec.test.ts b/src/__tests__/openapi-spec.test.ts index ab26289..638c8c6 100644 --- a/src/__tests__/openapi-spec.test.ts +++ b/src/__tests__/openapi-spec.test.ts @@ -96,6 +96,32 @@ describe("OpenAPI spec accuracy", () => { }); }); + const allConversionEndpoints = [ + "/v1/convert/html", + "/v1/convert/markdown", + "/v1/convert/url", + "/v1/demo/html", + "/v1/demo/markdown", + ]; + + describe("X-Render-Time header", () => { + it("should define X-Render-Time header component", () => { + expect(spec.components.headers["X-Render-Time"]).toBeDefined(); + expect(spec.components.headers["X-Render-Time"].schema.type).toBe("integer"); + expect(spec.components.headers["X-Render-Time"].description).toContain("render time"); + }); + + allConversionEndpoints.forEach((endpoint) => { + it(`${endpoint} 200 response should include X-Render-Time header`, () => { + const response200 = spec.paths[endpoint]?.post?.responses["200"]; + expect(response200.headers["X-Render-Time"]).toBeDefined(); + expect(response200.headers["X-Render-Time"].$ref).toBe( + "#/components/headers/X-Render-Time" + ); + }); + }); + }); + it("should mention rate limit headers in API description", () => { const description = spec.info.description; expect(description).toContain("X-RateLimit-Limit"); diff --git a/src/routes/convert.ts b/src/routes/convert.ts index b9d9efe..2d84e45 100644 --- a/src/routes/convert.ts +++ b/src/routes/convert.ts @@ -47,6 +47,8 @@ export const convertRouter = Router(); * $ref: '#/components/headers/X-RateLimit-Remaining' * X-RateLimit-Reset: * $ref: '#/components/headers/X-RateLimit-Reset' + * X-Render-Time: + * $ref: '#/components/headers/X-Render-Time' * content: * application/pdf: * schema: @@ -120,6 +122,8 @@ convertRouter.post("/html", async (req: Request, res: Response) => { * $ref: '#/components/headers/X-RateLimit-Remaining' * X-RateLimit-Reset: * $ref: '#/components/headers/X-RateLimit-Reset' + * X-Render-Time: + * $ref: '#/components/headers/X-Render-Time' * content: * application/pdf: * schema: @@ -196,6 +200,8 @@ convertRouter.post("/markdown", async (req: Request, res: Response) => { * $ref: '#/components/headers/X-RateLimit-Remaining' * X-RateLimit-Reset: * $ref: '#/components/headers/X-RateLimit-Reset' + * X-Render-Time: + * $ref: '#/components/headers/X-Render-Time' * content: * application/pdf: * schema: diff --git a/src/routes/demo.ts b/src/routes/demo.ts index c6675d1..b6f842b 100644 --- a/src/routes/demo.ts +++ b/src/routes/demo.ts @@ -108,6 +108,8 @@ interface DemoBody { * $ref: '#/components/headers/X-RateLimit-Remaining' * X-RateLimit-Reset: * $ref: '#/components/headers/X-RateLimit-Reset' + * X-Render-Time: + * $ref: '#/components/headers/X-Render-Time' * content: * application/pdf: * schema: @@ -204,6 +206,8 @@ router.post("/html", async (req: Request, res: Response) => { * $ref: '#/components/headers/X-RateLimit-Remaining' * X-RateLimit-Reset: * $ref: '#/components/headers/X-RateLimit-Reset' + * X-Render-Time: + * $ref: '#/components/headers/X-Render-Time' * content: * application/pdf: * schema: diff --git a/src/swagger.ts b/src/swagger.ts index dc94bba..6108580 100644 --- a/src/swagger.ts +++ b/src/swagger.ts @@ -63,6 +63,13 @@ const options: swaggerJsdoc.Options = { example: 1679875200, }, }, + "X-Render-Time": { + description: "PDF render time in milliseconds", + schema: { + type: "integer", + example: 120, + }, + }, "Retry-After": { description: "Number of seconds to wait before retrying the request (returned on 429 responses)", schema: {