feat: Add OpenAPI/Swagger documentation#193
Conversation
|
Warning Review limit reached
More reviews will be available in 7 minutes and 5 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more credits in the billing tab to continue. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
📝 WalkthroughWalkthroughIntegrates OpenAPI 3.0.3 documentation into the backend via ChangesOpenAPI/Swagger Integration
Sequence Diagram(s)sequenceDiagram
participant Client
participant SecurityMiddleware
participant ValidatorMiddleware
participant ExpressRoute
participant SwaggerUI
Client->>SecurityMiddleware: POST /api/accounts (JSON body)
SecurityMiddleware->>SecurityMiddleware: Apply Helmet CSP + CORS
SecurityMiddleware->>ValidatorMiddleware: next()
ValidatorMiddleware->>ValidatorMiddleware: normalisePathForSpec()
ValidatorMiddleware->>ValidatorMiddleware: resolve $ref schema from swaggerSpec
ValidatorMiddleware->>ValidatorMiddleware: validateAgainstSchema(req.body)
alt Validation fails
ValidatorMiddleware-->>Client: 422 VALIDATION_ERROR + details
else Validation passes
ValidatorMiddleware->>ExpressRoute: next()
ExpressRoute-->>Client: 201 Account created
end
Client->>SecurityMiddleware: GET /api-docs
SecurityMiddleware->>SecurityMiddleware: Apply relaxed CSP (unsafe-inline)
SecurityMiddleware->>SwaggerUI: next()
SwaggerUI-->>Client: Swagger UI HTML
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds first-class OpenAPI/Swagger support to the backend (interactive docs, raw spec endpoints, shared schemas) and introduces request-body validation middleware based on the generated spec.
Changes:
- Add Swagger/OpenAPI generation (
swagger-jsdoc) and Swagger UI (swagger-ui-express) plus JSON/YAML spec endpoints. - Add
@openapiJSDoc annotations across routes and define canonical component schemas inconfig/swagger.ts(with reference copies undersrc/swagger/). - Add an Express middleware to validate incoming JSON request bodies against the OpenAPI requestBody schema.
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| backend/src/swagger/schemas.yaml | Adds reference component schemas for external tooling. |
| backend/src/swagger/README.md | Documents how OpenAPI files are generated and exported. |
| backend/src/routes/health.ts | Adds OpenAPI docs for health/liveness/readiness endpoints. |
| backend/src/routes/auth.ts | Adds OpenAPI docs for auth endpoints (register/login/refresh). |
| backend/src/routes/accounts.ts | Adds OpenAPI docs for account endpoints, including pagination params. |
| backend/src/middleware/swaggerValidator.ts | Introduces request-body validation middleware using the generated spec. |
| backend/src/middleware/security.ts | Relaxes CSP specifically for Swagger UI routes; reformats imports/strings. |
| backend/src/index.ts | Serves Swagger UI and raw spec endpoints; installs request validation middleware. |
| backend/src/config/swagger.ts | Defines base OpenAPI spec, schemas, and Swagger UI options; adds JSON→YAML conversion. |
| backend/src/tests/swagger.test.ts | Adds unit tests asserting spec structure, paths, tags, and UI options. |
| backend/scripts/generate-types.ts | Adds script to output spec JSON and optionally generate TS types via openapi-typescript. |
| backend/package.json | Adds Swagger dependencies and type packages. |
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Only validate requests with JSON bodies | ||
| if (!req.body || Object.keys(req.body).length === 0) { | ||
| next(); | ||
| return; | ||
| } |
| // Try to find matching path in spec | ||
| const normalisedPath = normalisePathForSpec(req.path); | ||
| const pathSpec = paths[normalisedPath]; |
| // Relaxed CSP for Swagger UI only — allows inline scripts/styles required by swagger-ui-express | ||
| app.use( | ||
| "/api-docs", | ||
| helmet({ | ||
| contentSecurityPolicy: { | ||
| directives: { | ||
| defaultSrc: ["'self'"], | ||
| scriptSrc: ["'self'", "'unsafe-inline'"], | ||
| styleSrc: ["'self'", "'unsafe-inline'"], | ||
| imgSrc: ["'self'", "data:", "https://validator.swagger.io"], | ||
| }, | ||
| }, | ||
| crossOriginEmbedderPolicy: false, | ||
| }), | ||
| ); |
| it("should include custom CSS to hide the top bar", () => { | ||
| expect(swaggerUiOptions.customCss).toContain(".swagger-ui .topbar"); | ||
| expect(swaggerUiOptions.customCss).toContain("display: none"); | ||
| }); |
| components: | ||
| schemas: | ||
| ApiError: |
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
backend/src/routes/health.ts (1)
252-268:⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy liftCritical implementation mismatch in the readiness probe.
The
/api/health/readinessendpoint documentation promises to check critical services (database, stellar), but the implementation cannot access registered health checks. ThehealthChecksobject (line 22) is never populated; health checks are registered in thedependenciesarray (viaregisterHealthCheck(), lines 32-39) instead. As a result, the condition at line 257 (if (healthChecks[name])) will always be false, the loop body never executes, and the endpoint always returns200 { status: 'ready', services: {} }regardless of actual service status.The readiness probe is broken and will not serve its intended purpose (checking critical services before directing traffic). The implementation must be corrected to iterate over the
dependenciesarray, filter for critical services, and check their actual health status.🔧 Proposed fix (example approach)
Option 1: Populate
healthChecksduring registration:export function registerHealthCheck( name: string, check: () => Promise<ServiceHealth>, critical: boolean = false ): void { dependencies.push({ name, check, critical }); + healthChecks[name] = check; log.info(`Health check registered: ${name}`, { critical }); }Option 2: Check dependencies array directly in readiness endpoint:
router.get('/health/readiness', async (req: Request, res: Response) => { - const criticalServices = ['database', 'stellar']; + const criticalDeps = dependencies.filter(d => d.critical); const results: Record<string, ServiceHealth> = {}; - for (const name of criticalServices) { - if (healthChecks[name]) { + for (const dep of criticalDeps) { try { - results[name] = await healthChecks[name](); + results[dep.name] = await dep.check(); } catch (error) { - return res.status(503).json({ status: 'not ready', error: (error as Error).message }); + return res.status(503).json({ status: 'not ready', error: (error as Error).message }); } } } const allUp = Object.values(results).every(s => s.status === 'up'); res.status(allUp ? 200 : 503).json({ status: allUp ? 'ready' : 'not ready', services: results }); });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/src/routes/health.ts` around lines 252 - 268, The readiness endpoint is trying to access healthChecks[name], but the healthChecks object is never populated. Health checks are instead registered in the dependencies array via registerHealthCheck(). Fix the '/health/readiness' route handler to iterate directly over the dependencies array, filter for critical services ('database' and 'stellar'), and call their health check functions from the dependencies array rather than the non-functional healthChecks object. This will ensure the endpoint actually checks service status and returns the correct readiness state.
🧹 Nitpick comments (1)
backend/src/index.ts (1)
205-216: 💤 Low value
apiGatewayis instantiated but never used.The
ApiGatewayinstance is created at lines 210-214, butregisterGatewayRoutesat line 216 is commented out andapiGatewayis never referenced elsewhere. This is dead code that unnecessarily instantiates services.Either uncomment and complete the gateway registration, or remove the unused instantiation to reduce confusion.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/src/index.ts` around lines 205 - 216, The apiGateway instance is created but never used because the registerGatewayRoutes function call is commented out. Either uncomment the registerGatewayRoutes(apiGateway, apiKeyService, usageTrackingService) call and ensure it is properly implemented to register the API gateway routes, or remove the unused instantiation of ApiGateway along with any service instantiations (apiKeyService, usageTrackingService, rateLimiter) that are only needed for the gateway if they are not used elsewhere in the code. Choose the path based on whether the gateway functionality is intended to be active in this application.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/scripts/generate-types.ts`:
- Around line 34-66: The catch block at line 55 catches all errors (import
failures, generation failures, file write failures) but always logs
"openapi-typescript not installed," masking real generation failures. Refactor
the catch block to distinguish between import errors (where the "not installed"
message is appropriate) and other errors (which should be reported with actual
error details). Check the error type or error message to determine if it's a
module not found error versus a genuine generation or file system failure, and
log accordingly with the actual error information for non-import failures.
In `@backend/src/__tests__/swagger.test.ts`:
- Around line 220-223: The test assertion in the "should include custom CSS to
hide the top bar" test expects `swaggerUiOptions.customCss` to contain "display:
none", but the actual implementation in backend/src/config/swagger.ts applies
positioning rules to hide the topbar instead. Update the test assertion from
expecting "display: none" to expecting the actual CSS property or value that is
used in the swagger configuration to hide the .swagger-ui .topbar element,
ensuring the test reflects what the implementation actually does.
In `@backend/src/index.ts`:
- Around line 67-98: The OpenAPI documentation for the `/api/v1/credit-score`
endpoint declares security requirements (apiKeyAuth and bearerAuth) but the
route handler has no authentication middleware, creating a mismatch between
documented and actual behavior. Apply the apiGateway middleware to the route
handler to enforce authentication, making the implementation match the
documentation. This same fix applies to all other endpoints with security
declarations but missing authentication enforcement.
In `@backend/src/middleware/security.ts`:
- Around line 86-103: The global helmetMiddleware() call at line 102 is being
applied to all routes including `/api-docs`, which overwrites the relaxed CSP
configuration that was specifically set up for the `/api-docs` route. To fix
this, modify the helmetMiddleware() call to skip the `/api-docs` path. This can
be done by adding a conditional skip option to helmetMiddleware() that prevents
it from applying to requests starting with `/api-docs`, ensuring the relaxed CSP
for Swagger UI remains in effect and is not overwritten by the stricter global
helmet configuration.
In `@backend/src/middleware/swaggerValidator.ts`:
- Around line 45-59: The validateAgainstSchema function does not reject
non-object values when the schema requires type "object". Currently it only
validates when the value IS an object, but fails to add an error when the schema
specifies type "object" but the value is an array, null, or a primitive. After
resolving the schema reference, check if resolved.type equals "object" and the
value is not a plain object (i.e., it is null, an array, or a non-object type),
and if so, add an error message to the errors array indicating the type mismatch
before proceeding with further validation logic.
- Around line 159-163: The early return in the swaggerValidator middleware is
incorrectly skipping validation for empty JSON objects `{}` by checking if
`Object.keys(req.body).length === 0`. This prevents validation of required
fields that should be present in the request body. Remove the empty object
length check from the early return condition, keeping only the check for falsy
bodies (truly missing bodies). Instead, add logic after `requestBodySpec` is
resolved to only skip validation when the operation does not define or require a
JSON body in its schema. This allows `validateAgainstSchema(req.body,
jsonSchema)` to properly report missing required fields for empty objects.
- Around line 136-137: The normalisePathForSpec function is being called on
concrete request paths like /api/accounts/123, but it only converts Express
route patterns (with colons) to OpenAPI format (with braces). Since concrete
paths contain actual parameter values, not placeholders, the normalization
returns the path unchanged, preventing any match against the OpenAPI spec keys
like /api/accounts/{id}. Instead of normalizing the concrete path, implement
pattern-based matching that compares the concrete request path against available
OpenAPI template paths to find the matching spec entry. This could be done by
iterating through the paths in the spec and checking if the concrete path
matches each template pattern, handling parameter substitution correctly.
In `@backend/src/swagger/README.md`:
- Line 23: The documentation in backend/src/swagger/README.md at line 23
references a script command `generate:api-types` that does not exist in
backend/package.json. Either identify the correct script name in
backend/package.json that actually generates the API types and update the
documentation to reference that existing script, or add the missing
`generate:api-types` script to backend/package.json with the appropriate command
to generate the openapi-spec.json file.
In `@backend/src/swagger/schemas.yaml`:
- Around line 1-3: The schemas.yaml file is drifting from the canonical runtime
spec defined in backend/src/config/swagger.ts. You need to synchronize the
entire schemas.yaml file (lines 1-217) with the actual schema definitions from
backend/src/config/swagger.ts to eliminate the divergence. Specifically, ensure
that all schema definitions present in the swagger.ts configuration are included
in schemas.yaml, including missing schemas like PaginatedTransactions and any
nested properties like HealthCheckResult.checks.*.details. Update the comment in
schemas.yaml to accurately reflect whether it is truly canonical or a reference
copy, and maintain schema parity between this file and the runtime specification
so that external tools using schemas.yaml remain in sync with what
/api-docs.json actually exposes.
---
Outside diff comments:
In `@backend/src/routes/health.ts`:
- Around line 252-268: The readiness endpoint is trying to access
healthChecks[name], but the healthChecks object is never populated. Health
checks are instead registered in the dependencies array via
registerHealthCheck(). Fix the '/health/readiness' route handler to iterate
directly over the dependencies array, filter for critical services ('database'
and 'stellar'), and call their health check functions from the dependencies
array rather than the non-functional healthChecks object. This will ensure the
endpoint actually checks service status and returns the correct readiness state.
---
Nitpick comments:
In `@backend/src/index.ts`:
- Around line 205-216: The apiGateway instance is created but never used because
the registerGatewayRoutes function call is commented out. Either uncomment the
registerGatewayRoutes(apiGateway, apiKeyService, usageTrackingService) call and
ensure it is properly implemented to register the API gateway routes, or remove
the unused instantiation of ApiGateway along with any service instantiations
(apiKeyService, usageTrackingService, rateLimiter) that are only needed for the
gateway if they are not used elsewhere in the code. Choose the path based on
whether the gateway functionality is intended to be active in this application.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: a8021ace-3c6f-4374-b555-e9a26c29ee67
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (12)
backend/package.jsonbackend/scripts/generate-types.tsbackend/src/__tests__/swagger.test.tsbackend/src/config/swagger.tsbackend/src/index.tsbackend/src/middleware/security.tsbackend/src/middleware/swaggerValidator.tsbackend/src/routes/accounts.tsbackend/src/routes/auth.tsbackend/src/routes/health.tsbackend/src/swagger/README.mdbackend/src/swagger/schemas.yaml
| try { | ||
| const openapiTS = await import("openapi-typescript"); | ||
| const generateFn = openapiTS.default || openapiTS; | ||
|
|
||
| if (typeof generateFn === "function") { | ||
| const output = await generateFn(swaggerSpec as any); | ||
| const tsOutputPath = path.resolve(outputDir, "generated-api.ts"); | ||
| const content = typeof output === "string" ? output : String(output); | ||
| fs.writeFileSync(tsOutputPath, content, "utf-8"); | ||
| console.log(`✅ TypeScript types written to ${tsOutputPath}`); | ||
| } else { | ||
| console.log( | ||
| "ℹ️ openapi-typescript loaded but generate function not found.", | ||
| ); | ||
| console.log( | ||
| " The OpenAPI spec JSON has been saved — you can generate types manually with:", | ||
| ); | ||
| console.log( | ||
| ` npx openapi-typescript ${specOutputPath} -o ${path.resolve(outputDir, "generated-api.ts")}`, | ||
| ); | ||
| } | ||
| } catch { | ||
| console.log( | ||
| "ℹ️ openapi-typescript not installed. Skipping TypeScript type generation.", | ||
| ); | ||
| console.log( | ||
| " Install it with: pnpm add -D openapi-typescript --filter @chenaikit/backend", | ||
| ); | ||
| console.log(" Then re-run this script, or generate types manually with:"); | ||
| console.log( | ||
| ` npx openapi-typescript ${specOutputPath} -o ${path.resolve(outputDir, "generated-api.ts")}`, | ||
| ); | ||
| } |
There was a problem hiding this comment.
Catch block is masking real generation failures as “dependency missing.”
Line 55 catches all errors from import, generation, and file write, but always logs “not installed.” If generation fails for a bad spec or runtime error, this script misreports success conditions and skips actionable failure reporting.
Proposed fix
- try {
- const openapiTS = await import("openapi-typescript");
- const generateFn = openapiTS.default || openapiTS;
-
- if (typeof generateFn === "function") {
- const output = await generateFn(swaggerSpec as any);
- const tsOutputPath = path.resolve(outputDir, "generated-api.ts");
- const content = typeof output === "string" ? output : String(output);
- fs.writeFileSync(tsOutputPath, content, "utf-8");
- console.log(`✅ TypeScript types written to ${tsOutputPath}`);
- } else {
- console.log(
- "ℹ️ openapi-typescript loaded but generate function not found.",
- );
- console.log(
- " The OpenAPI spec JSON has been saved — you can generate types manually with:",
- );
- console.log(
- ` npx openapi-typescript ${specOutputPath} -o ${path.resolve(outputDir, "generated-api.ts")}`,
- );
- }
- } catch {
+ let generateFn: unknown;
+ try {
+ const openapiTS = await import("openapi-typescript");
+ generateFn = openapiTS.default || openapiTS;
+ } catch {
console.log(
"ℹ️ openapi-typescript not installed. Skipping TypeScript type generation.",
);
@@
- }
+ return;
+ }
+
+ if (typeof generateFn !== "function") {
+ console.log("ℹ️ openapi-typescript loaded but generate function not found.");
+ return;
+ }
+
+ const output = await generateFn(swaggerSpec as any);
+ const tsOutputPath = path.resolve(outputDir, "generated-api.ts");
+ fs.writeFileSync(tsOutputPath, typeof output === "string" ? output : String(output), "utf-8");
+ console.log(`✅ TypeScript types written to ${tsOutputPath}`);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/scripts/generate-types.ts` around lines 34 - 66, The catch block at
line 55 catches all errors (import failures, generation failures, file write
failures) but always logs "openapi-typescript not installed," masking real
generation failures. Refactor the catch block to distinguish between import
errors (where the "not installed" message is appropriate) and other errors
(which should be reported with actual error details). Check the error type or
error message to determine if it's a module not found error versus a genuine
generation or file system failure, and log accordingly with the actual error
information for non-import failures.
| /** | ||
| * @openapi | ||
| * /api/v1/credit-score: | ||
| * get: | ||
| * summary: Get credit score | ||
| * description: Returns an AI-generated credit score with contributing factors. Gateway-protected endpoint. | ||
| * tags: [Credit Scoring] | ||
| * security: | ||
| * - apiKeyAuth: [] | ||
| * - bearerAuth: [] | ||
| * responses: | ||
| * 200: | ||
| * description: Credit score computed successfully | ||
| * content: | ||
| * application/json: | ||
| * schema: | ||
| * $ref: '#/components/schemas/CreditScoreResponse' | ||
| * 401: | ||
| * description: Authentication required | ||
| * 403: | ||
| * description: Insufficient permissions | ||
| */ | ||
| app.get("/api/v1/credit-score", (_req: Request, res: Response) => { | ||
| res.json({ | ||
| success: true, | ||
| data: { | ||
| score: Math.floor(Math.random() * 850) + 150, | ||
| factors: ['payment_history', 'credit_utilization', 'account_age'], | ||
| factors: ["payment_history", "credit_utilization", "account_age"], | ||
| timestamp: new Date().toISOString(), | ||
| }, | ||
| }); | ||
| }); |
There was a problem hiding this comment.
OpenAPI security annotations don't match actual implementation.
The @openapi annotations declare security: [apiKeyAuth, bearerAuth] at lines 74-76 and 107-109, indicating these endpoints require authentication. However, the route handlers at lines 89-98 and 122-132 have no authentication middleware—they return mock data unconditionally.
This mismatch between documented and actual behavior could mislead API consumers into thinking these endpoints are protected when they're not. Either:
- Apply the
apiGatewaymiddleware to enforce authentication, or - Remove the
securitydeclarations from the annotations if these are intentionally public stubs.
Also applies to: 100-132
🧰 Tools
🪛 ast-grep (0.43.0)
[warning] 88-97: Avoid allowing access to unintended directories or files
Context: app.get("/api/v1/credit-score", (_req: Request, res: Response) => {
res.json({
success: true,
data: {
score: Math.floor(Math.random() * 850) + 150,
factors: ["payment_history", "credit_utilization", "account_age"],
timestamp: new Date().toISOString(),
},
});
})
Note: Security best practice.
(path-traversal-typescript)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/src/index.ts` around lines 67 - 98, The OpenAPI documentation for the
`/api/v1/credit-score` endpoint declares security requirements (apiKeyAuth and
bearerAuth) but the route handler has no authentication middleware, creating a
mismatch between documented and actual behavior. Apply the apiGateway middleware
to the route handler to enforce authentication, making the implementation match
the documentation. This same fix applies to all other endpoints with security
declarations but missing authentication enforcement.
| function validateAgainstSchema(value: any, schema: any): string[] { | ||
| const errors: string[] = []; | ||
|
|
||
| if (!schema || !value) return errors; | ||
|
|
||
| // Resolve $ref if present | ||
| const resolved = schema.$ref ? resolveRef(schema.$ref) : schema; | ||
| if (!resolved) return errors; | ||
|
|
||
| // Type check | ||
| if ( | ||
| resolved.type === "object" && | ||
| typeof value === "object" && | ||
| !Array.isArray(value) | ||
| ) { |
There was a problem hiding this comment.
Reject non-object bodies when the schema requires an object.
For type: "object", arrays/primitives/null currently fall through without adding an error, so malformed bodies can pass validation.
Suggested fix
function validateAgainstSchema(value: any, schema: any): string[] {
const errors: string[] = [];
- if (!schema || !value) return errors;
+ if (!schema) return errors;
// Resolve $ref if present
const resolved = schema.$ref ? resolveRef(schema.$ref) : schema;
if (!resolved) return errors;
// Type check
- if (
- resolved.type === "object" &&
- typeof value === "object" &&
- !Array.isArray(value)
- ) {
+ if (resolved.type === "object") {
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
+ errors.push("Request body must be an object");
+ return errors;
+ }
+
// Check required fields📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function validateAgainstSchema(value: any, schema: any): string[] { | |
| const errors: string[] = []; | |
| if (!schema || !value) return errors; | |
| // Resolve $ref if present | |
| const resolved = schema.$ref ? resolveRef(schema.$ref) : schema; | |
| if (!resolved) return errors; | |
| // Type check | |
| if ( | |
| resolved.type === "object" && | |
| typeof value === "object" && | |
| !Array.isArray(value) | |
| ) { | |
| function validateAgainstSchema(value: any, schema: any): string[] { | |
| const errors: string[] = []; | |
| if (!schema) return errors; | |
| // Resolve $ref if present | |
| const resolved = schema.$ref ? resolveRef(schema.$ref) : schema; | |
| if (!resolved) return errors; | |
| // Type check | |
| if (resolved.type === "object") { | |
| if (value === null || typeof value !== "object" || Array.isArray(value)) { | |
| errors.push("Request body must be an object"); | |
| return errors; | |
| } | |
| // Check required fields |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/src/middleware/swaggerValidator.ts` around lines 45 - 59, The
validateAgainstSchema function does not reject non-object values when the schema
requires type "object". Currently it only validates when the value IS an object,
but fails to add an error when the schema specifies type "object" but the value
is an array, null, or a primitive. After resolving the schema reference, check
if resolved.type equals "object" and the value is not a plain object (i.e., it
is null, an array, or a non-object type), and if so, add an error message to the
errors array indicating the type mismatch before proceeding with further
validation logic.
| function normalisePathForSpec(expressPath: string): string { | ||
| return expressPath.replace(/:([^/]+)/g, "{$1}"); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Show parameterized OpenAPI paths and where the validator is mounted.
rg -nP 'app\.use\(validateRequestAgainstSpec\)|/api/[^"\047`]*\{[^}]+\}' backend/src/index.ts backend/src/routes backend/src/config/swagger.tsRepository: nexoraorg/chenaikit
Length of output: 317
🏁 Script executed:
cat -n backend/src/middleware/swaggerValidator.ts | sed -n '129,180p'Repository: nexoraorg/chenaikit
Length of output: 1901
🏁 Script executed:
cat -n backend/src/middleware/swaggerValidator.ts | sed -n '60,127p'Repository: nexoraorg/chenaikit
Length of output: 2756
🏁 Script executed:
cat -n backend/src/routes/accounts.ts | sed -n '1,20p'Repository: nexoraorg/chenaikit
Length of output: 880
🏁 Script executed:
# Check the actual spec structure - what does getSpecPaths() return?
rg -A5 'function getSpecPaths|const getSpecPaths' backend/src/middleware/swaggerValidator.tsRepository: nexoraorg/chenaikit
Length of output: 225
🏁 Script executed:
# Look for how paths are populated in the spec
cat -n backend/src/config/swagger.ts | head -50Repository: nexoraorg/chenaikit
Length of output: 2261
🏁 Script executed:
# Check what the actual paths object looks like in the spec after building
rg -B2 -A10 'const swaggerSpec|export const swaggerSpec' backend/src/config/swagger.tsRepository: nexoraorg/chenaikit
Length of output: 501
🏁 Script executed:
# Look at how swagger-jsdoc is called and what files it includes
cat -n backend/src/config/swagger.ts | sed -n '60,120p'Repository: nexoraorg/chenaikit
Length of output: 2382
🏁 Script executed:
# Find the swagger-jsdoc options configuration
rg -B5 'const options' backend/src/config/swagger.ts | head -30Repository: nexoraorg/chenaikit
Length of output: 132
🏁 Script executed:
# Let's trace through a concrete example - check one of the documented routes
cat -n backend/src/routes/accounts.ts | sed -n '10,80p'Repository: nexoraorg/chenaikit
Length of output: 2546
Match concrete request paths against OpenAPI path templates.
req.path is a concrete URL path at runtime (e.g., /api/accounts/123), so calling normalisePathForSpec() on it does not work. The function converts :id to {id}, but /api/accounts/123 contains no colons—it returns unchanged. The OpenAPI spec has /api/accounts/{id} as the key, so paths[normalisedPath] will always be undefined for parameterized routes, causing validation to be skipped.
The documented endpoints with path parameters (GET /api/accounts/{id}, GET /api/accounts/{id}/balance, GET /api/accounts/{id}/transactions) never validate request bodies because their concrete request paths never match the templated spec keys.
Matching must use pattern-based lookup to handle path parameter substitution:
Suggested fix
-function normalisePathForSpec(expressPath: string): string {
- return expressPath.replace(/:([^/]+)/g, "{$1}");
+function escapeRegex(value: string): string {
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+}
+
+function findPathSpec(paths: Record<string, any>, requestPath: string): any {
+ for (const [specPath, pathSpec] of Object.entries(paths)) {
+ const pattern = `^${escapeRegex(specPath).replace(/\\\{[^/]+\\\}/g, "[^/]+")}/?$`;
+ if (new RegExp(pattern).test(requestPath)) {
+ return pathSpec;
+ }
+ }
+
+ return undefined;
}- // Try to find matching path in spec
- const normalisedPath = normalisePathForSpec(req.path);
- const pathSpec = paths[normalisedPath];
+ // Try to find matching path in spec
+ const pathSpec = findPathSpec(paths, req.path);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/src/middleware/swaggerValidator.ts` around lines 136 - 137, The
normalisePathForSpec function is being called on concrete request paths like
/api/accounts/123, but it only converts Express route patterns (with colons) to
OpenAPI format (with braces). Since concrete paths contain actual parameter
values, not placeholders, the normalization returns the path unchanged,
preventing any match against the OpenAPI spec keys like /api/accounts/{id}.
Instead of normalizing the concrete path, implement pattern-based matching that
compares the concrete request path against available OpenAPI template paths to
find the matching spec entry. This could be done by iterating through the paths
in the spec and checking if the concrete path matches each template pattern,
handling parameter substitution correctly.
| // Only validate requests with JSON bodies | ||
| if (!req.body || Object.keys(req.body).length === 0) { | ||
| next(); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Do not skip {} before checking the operation schema.
An empty JSON object still needs validation for documented bodies with required fields, such as auth requests requiring email and password. This early return lets {} bypass the validator entirely.
Suggested fix
- // Only validate requests with JSON bodies
- if (!req.body || Object.keys(req.body).length === 0) {
- next();
- return;
- }
-
const method = req.method.toLowerCase();
const paths = getSpecPaths();Then let validateAgainstSchema(req.body, jsonSchema) report missing required fields; if you want to handle absent bodies separately, do it after requestBodySpec is resolved and only skip when the operation does not define/require a JSON body.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/src/middleware/swaggerValidator.ts` around lines 159 - 163, The early
return in the swaggerValidator middleware is incorrectly skipping validation for
empty JSON objects `{}` by checking if `Object.keys(req.body).length === 0`.
This prevents validation of required fields that should be present in the
request body. Remove the empty object length check from the early return
condition, keeping only the check for falsy bodies (truly missing bodies).
Instead, add logic after `requestBodySpec` is resolved to only skip validation
when the operation does not define or require a JSON body in its schema. This
allows `validateAgainstSchema(req.body, jsonSchema)` to properly report missing
required fields for empty objects.
| # Reusable OpenAPI Schemas | ||
| # These are the canonical schemas defined in ../config/swagger.ts | ||
| # This file serves as a reference copy for external tooling (e.g., client SDK generators). |
There was a problem hiding this comment.
Canonical schema reference is already drifting from runtime spec.
Line 2 says this file is canonical, but it diverges from backend/src/config/swagger.ts (e.g., missing PaginatedTransactions, and missing HealthCheckResult.checks.*.details). That creates SDK/spec contract drift between tooling using this file and /api-docs.json.
Also applies to: 5-217
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/src/swagger/schemas.yaml` around lines 1 - 3, The schemas.yaml file
is drifting from the canonical runtime spec defined in
backend/src/config/swagger.ts. You need to synchronize the entire schemas.yaml
file (lines 1-217) with the actual schema definitions from
backend/src/config/swagger.ts to eliminate the divergence. Specifically, ensure
that all schema definitions present in the swagger.ts configuration are included
in schemas.yaml, including missing schemas like PaginatedTransactions and any
nested properties like HealthCheckResult.checks.*.details. Update the comment in
schemas.yaml to accurately reflect whether it is truly canonical or a reference
copy, and maintain schema parity between this file and the runtime specification
so that external tools using schemas.yaml remain in sync with what
/api-docs.json actually exposes.
| // Only validate requests with JSON bodies | ||
| if (!req.body || Object.keys(req.body).length === 0) { | ||
| next(); | ||
| return; | ||
| } |
| function validateAgainstSchema(value: any, schema: any): string[] { | ||
| const errors: string[] = []; | ||
|
|
||
| if (!schema || !value) return errors; |
| const method = req.method.toLowerCase(); | ||
| const paths = getSpecPaths(); | ||
|
|
||
| // Try to find matching path in spec | ||
| const normalisedPath = normalisePathForSpec(req.path); | ||
| const pathSpec = paths[normalisedPath]; |
| const options: swaggerJsdoc.OAS3Options = { | ||
| definition: swaggerDefinition, | ||
| // Scan route files and index.ts for @openapi annotations | ||
| apis: ["./src/routes/*.ts", "./src/index.ts"], | ||
| }; |
| app.use((req, res, next) => { | ||
| if (req.path.startsWith("/api-docs")) return next(); | ||
| return helmetMiddleware()(req, res, next); | ||
| }); |
| export function jsonToYaml(obj: unknown, indent: number = 0): string { | ||
| const pad = " ".repeat(indent); |
| export function validateRequestAgainstSpec( | ||
| req: Request, | ||
| res: Response, | ||
| next: NextFunction, | ||
| ): void { |
|
@kydrahul resolve conflicts |
Closes #172
Changes
/api-docs/api-docs.json, YAML at/api-docs.yamlswaggerValidator.ts)schemas.yamlreference file for SDK generatorsgenerate-types.tsscript for TypeScript type generationScreenshot
Summary by CodeRabbit
Release Notes
New Features
/api-docswith searchable endpoint browser/api-docs.json,/api-docs.yaml) for programmatic integrationDocumentation