build(swagger): normalize and sort generated OpenAPI path keys #101
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,8 @@
|
||||
// Regenerate with: npm run swagger (from the server/ directory)
|
||||
// The generated JSON is committed so the docs work without a build step.
|
||||
|
||||
const fs = require('fs')
|
||||
|
||||
const swaggerAutogen = require('swagger-autogen')({ openapi: '3.0.0' })
|
||||
const pkg = require('../package.json')
|
||||
const brand = require('../src/config/brand')
|
||||
@@ -916,7 +918,46 @@ const doc = {
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize `/a/b/` → `/a/b` in the generated path keys.
|
||||
*
|
||||
* swagger-autogen builds a path by string-concatenating the mount prefix with the
|
||||
* route argument, so a capability router mounted at `/users` that declares its
|
||||
* collection route as `router.get('/')` documents as `/api/v1/admin/users/`.
|
||||
* Express itself does not care (non-strict routing treats the two as one route,
|
||||
* and server/routes.manifest.json records the canonical slash-less form), but the
|
||||
* *spec* would advertise a URL no client uses and stop documenting the one they
|
||||
* all call. The domain split (docs/website/API_V2_PLAN.md § Phase 2) creates one
|
||||
* of these per capability router, so it is fixed here once rather than by
|
||||
* contorting the route declarations in every router file.
|
||||
*
|
||||
* The path keys are also **sorted**. swagger-autogen emits them in router-traversal
|
||||
* order, so moving a route between files rewrites most of this 5k-line committed
|
||||
* artifact even when the API is provably unchanged — burying the one line a
|
||||
* reviewer needs to see. OpenAPI attaches no meaning to path order, and
|
||||
* scripts/routeManifest.js already sorts for the same reason.
|
||||
*/
|
||||
function normalizePaths(spec) {
|
||||
const paths = {}
|
||||
for (const [p, item] of Object.entries(spec.paths).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))) {
|
||||
const key = p.length > 1 ? p.replace(/\/+$/, '') : p
|
||||
if (paths[key]) {
|
||||
// Two different declarations collapsed onto one path — merging would hide
|
||||
// whichever lost. Nothing in the tree does this today; fail loudly if it starts.
|
||||
throw new Error(
|
||||
`swagger: "${p}" and "${key}" collide after trailing-slash normalization. ` +
|
||||
'Two routes are documenting the same URL — reconcile them in the router.',
|
||||
)
|
||||
}
|
||||
paths[key] = item
|
||||
}
|
||||
spec.paths = paths
|
||||
return spec
|
||||
}
|
||||
|
||||
swaggerAutogen(outputFile, routes, doc).then(() => {
|
||||
const written = JSON.parse(fs.readFileSync(outputFile, 'utf8'))
|
||||
fs.writeFileSync(outputFile, `${JSON.stringify(normalizePaths(written), null, 2)}\n`)
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('swagger-output.json generated.')
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user