mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-08 14:41:28 -07:00
update api docs
This commit is contained in:
@@ -11,7 +11,7 @@ import {
|
|||||||
searchAccounts,
|
searchAccounts,
|
||||||
updateAccount,
|
updateAccount,
|
||||||
} from '@repo/domain'
|
} from '@repo/domain'
|
||||||
import { logger, withNotFound, withOnError } from '@repo/hono-helpers'
|
import { logger, withCleanSpec, withNotFound, withOnError } from '@repo/hono-helpers'
|
||||||
import { validateAndGetAccountId } from '@repo/jwt'
|
import { validateAndGetAccountId } from '@repo/jwt'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -638,6 +638,7 @@ const app = new Hono<App>()
|
|||||||
app.get(
|
app.get(
|
||||||
'/openapi.json',
|
'/openapi.json',
|
||||||
describeRoute({ hide: true }),
|
describeRoute({ hide: true }),
|
||||||
|
withCleanSpec(
|
||||||
openAPIRouteHandler(app, {
|
openAPIRouteHandler(app, {
|
||||||
documentation: {
|
documentation: {
|
||||||
info: {
|
info: {
|
||||||
@@ -668,6 +669,7 @@ app.get(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
export default app
|
export default app
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Hono } from 'hono'
|
|||||||
import { describeRoute, openAPIRouteHandler } from 'hono-openapi'
|
import { describeRoute, openAPIRouteHandler } from 'hono-openapi'
|
||||||
import { useWorkersLogger } from 'workers-tagged-logger'
|
import { useWorkersLogger } from 'workers-tagged-logger'
|
||||||
|
|
||||||
import { withNotFound, withOnError } from '@repo/hono-helpers'
|
import { withCleanSpec, withNotFound, withOnError } from '@repo/hono-helpers'
|
||||||
|
|
||||||
import { avatarRoutes } from './routes/avatar'
|
import { avatarRoutes } from './routes/avatar'
|
||||||
import { configRoutes } from './routes/config'
|
import { configRoutes } from './routes/config'
|
||||||
@@ -58,6 +58,7 @@ const app = new Hono<App>({ strict: false })
|
|||||||
app.get(
|
app.get(
|
||||||
'/openapi.json',
|
'/openapi.json',
|
||||||
describeRoute({ hide: true }),
|
describeRoute({ hide: true }),
|
||||||
|
withCleanSpec(
|
||||||
openAPIRouteHandler(app, {
|
openAPIRouteHandler(app, {
|
||||||
documentation: {
|
documentation: {
|
||||||
info: {
|
info: {
|
||||||
@@ -95,6 +96,7 @@ app.get(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
export default app
|
export default app
|
||||||
|
|||||||
@@ -1990,4 +1990,16 @@ describe('openapi', () => {
|
|||||||
const raw = await res.text()
|
const raw = await res.text()
|
||||||
expect(raw.match(/\$ref/g)).toBeNull()
|
expect(raw.match(/\$ref/g)).toBeNull()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// `z.int()` carries the safe-integer range as its bounds, which Scalar would
|
||||||
|
// otherwise show as the example value for every integer field (-9007199254740991).
|
||||||
|
// withCleanSpec() supplies a placeholder instead; this guards the wrapper staying
|
||||||
|
// wired up.
|
||||||
|
test('integer fields carry a placeholder example', async () => {
|
||||||
|
const res = await exports.default.fetch(`${ORIGIN}/openapi.json`)
|
||||||
|
const raw = await res.text()
|
||||||
|
const integers = raw.match(/"type":"integer"/g) ?? []
|
||||||
|
expect(integers.length).toBeGreaterThan(0)
|
||||||
|
expect(raw.match(/"example":12345/g)?.length).toBe(integers.length)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
setPresence,
|
setPresence,
|
||||||
verifyPassword,
|
verifyPassword,
|
||||||
} from '@repo/domain'
|
} from '@repo/domain'
|
||||||
import { intVar, logger, withNotFound, withOnError } from '@repo/hono-helpers'
|
import { intVar, logger, withCleanSpec, withNotFound, withOnError } from '@repo/hono-helpers'
|
||||||
import { generateToken, TOKEN_TTL_SECONDS, validateAndGetAccountId } from '@repo/jwt'
|
import { generateToken, TOKEN_TTL_SECONDS, validateAndGetAccountId } from '@repo/jwt'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -720,6 +720,7 @@ const app = new Hono<App>()
|
|||||||
app.get(
|
app.get(
|
||||||
'/openapi.json',
|
'/openapi.json',
|
||||||
describeRoute({ hide: true }),
|
describeRoute({ hide: true }),
|
||||||
|
withCleanSpec(
|
||||||
openAPIRouteHandler(app, {
|
openAPIRouteHandler(app, {
|
||||||
documentation: {
|
documentation: {
|
||||||
info: {
|
info: {
|
||||||
@@ -750,6 +751,7 @@ app.get(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
export default app
|
export default app
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { describeRoute, openAPIRouteHandler } from 'hono-openapi'
|
|||||||
import { useWorkersLogger } from 'workers-tagged-logger'
|
import { useWorkersLogger } from 'workers-tagged-logger'
|
||||||
|
|
||||||
import { consumeGift, createGift, getGift, getPendingGifts } from '@repo/domain'
|
import { consumeGift, createGift, getGift, getPendingGifts } from '@repo/domain'
|
||||||
import { intVar, logger, withNotFound, withOnError } from '@repo/hono-helpers'
|
import { intVar, logger, withCleanSpec, withNotFound, withOnError } from '@repo/hono-helpers'
|
||||||
import { validateAndGetAccountId } from '@repo/jwt'
|
import { validateAndGetAccountId } from '@repo/jwt'
|
||||||
|
|
||||||
// The notification-type ids the hub carries (owned by the `notify` worker). Imported
|
// The notification-type ids the hub carries (owned by the `notify` worker). Imported
|
||||||
@@ -1200,6 +1200,7 @@ const app = new Hono<App>({ strict: false })
|
|||||||
app.get(
|
app.get(
|
||||||
'/openapi.json',
|
'/openapi.json',
|
||||||
describeRoute({ hide: true }),
|
describeRoute({ hide: true }),
|
||||||
|
withCleanSpec(
|
||||||
openAPIRouteHandler(app, {
|
openAPIRouteHandler(app, {
|
||||||
documentation: {
|
documentation: {
|
||||||
info: {
|
info: {
|
||||||
@@ -1231,6 +1232,7 @@ app.get(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
export default app
|
export default app
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import {
|
|||||||
setPresence,
|
setPresence,
|
||||||
setRoomInstanceInProgress,
|
setRoomInstanceInProgress,
|
||||||
} from '@repo/domain'
|
} from '@repo/domain'
|
||||||
import { withNotFound, withOnError } from '@repo/hono-helpers'
|
import { withCleanSpec, withNotFound, withOnError } from '@repo/hono-helpers'
|
||||||
import { validateAndGetAccountId } from '@repo/jwt'
|
import { validateAndGetAccountId } from '@repo/jwt'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -1084,6 +1084,7 @@ async function sweepExpiredPresence(env: Env): Promise<void> {
|
|||||||
app.get(
|
app.get(
|
||||||
'/openapi.json',
|
'/openapi.json',
|
||||||
describeRoute({ hide: true }),
|
describeRoute({ hide: true }),
|
||||||
|
withCleanSpec(
|
||||||
openAPIRouteHandler(app, {
|
openAPIRouteHandler(app, {
|
||||||
documentation: {
|
documentation: {
|
||||||
info: {
|
info: {
|
||||||
@@ -1116,6 +1117,7 @@ app.get(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// The HTTP surface is a standard Hono app, exported by name so it can be mounted
|
// The HTTP surface is a standard Hono app, exported by name so it can be mounted
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import type { Handler, MiddlewareHandler } from 'hono'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zod 4 encodes `z.int()` as `{ type: 'integer', minimum: -9007199254740991, maximum:
|
||||||
|
* 9007199254740991 }` — the safe-integer range. That is accurate, but Scalar (and most
|
||||||
|
* spec viewers) derive the displayed example from `minimum` when a schema carries no
|
||||||
|
* `example` of its own, so every integer field in the docs rendered as
|
||||||
|
* `-9007199254740991`.
|
||||||
|
*
|
||||||
|
* The bounds are left alone; we just supply a neutral placeholder so the viewer has
|
||||||
|
* something better to show.
|
||||||
|
*/
|
||||||
|
const PLACEHOLDER_INTEGER = 12345
|
||||||
|
|
||||||
|
/** Recursively add a placeholder example to integer schemas that lack one. */
|
||||||
|
function addIntegerExamples(node: unknown): void {
|
||||||
|
if (Array.isArray(node)) {
|
||||||
|
for (const item of node) addIntegerExamples(item)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (node === null || typeof node !== 'object') return
|
||||||
|
|
||||||
|
const obj = node as Record<string, unknown>
|
||||||
|
if (obj.type === 'integer' && obj.example === undefined && obj.examples === undefined) {
|
||||||
|
// Don't contradict a schema that really is narrow (`z.int().max(10)`, an enum-ish
|
||||||
|
// range) — the placeholder only goes in where it's a legal value.
|
||||||
|
const min = obj.minimum
|
||||||
|
const max = obj.maximum
|
||||||
|
const tooLow = typeof min === 'number' && PLACEHOLDER_INTEGER < min
|
||||||
|
const tooHigh = typeof max === 'number' && PLACEHOLDER_INTEGER > max
|
||||||
|
if (!tooLow && !tooHigh) obj.example = PLACEHOLDER_INTEGER
|
||||||
|
}
|
||||||
|
for (const value of Object.values(obj)) addIntegerExamples(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap `openAPIRouteHandler(...)` so the generated document gets example values for its
|
||||||
|
* integer fields. Purely cosmetic — nothing about the documented shapes changes.
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* app.get('/openapi.json', describeRoute({ hide: true }), withCleanSpec(openAPIRouteHandler(app, { ... })))
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function withCleanSpec(handler: Handler | MiddlewareHandler): Handler {
|
||||||
|
return async (c, next) => {
|
||||||
|
const res = await (handler as Handler)(c, next)
|
||||||
|
if (!(res instanceof Response)) return res as never
|
||||||
|
const spec: unknown = await res.json()
|
||||||
|
addIntegerExamples(spec)
|
||||||
|
return c.json(spec as Record<string, unknown>)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ export * from './helpers/env'
|
|||||||
export { logger } from './helpers/logger'
|
export { logger } from './helpers/logger'
|
||||||
export { getRequestLogData, type LogDataRequest } from './helpers/request'
|
export { getRequestLogData, type LogDataRequest } from './helpers/request'
|
||||||
export * from './helpers/errors'
|
export * from './helpers/errors'
|
||||||
|
export * from './helpers/openapi'
|
||||||
export * from './helpers/url'
|
export * from './helpers/url'
|
||||||
export * from './middleware/withCache'
|
export * from './middleware/withCache'
|
||||||
export * from './middleware/withDefaultCors'
|
export * from './middleware/withDefaultCors'
|
||||||
|
|||||||
Reference in New Issue
Block a user