mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-08 06:31:27 -07:00
update api docs
This commit is contained in:
@@ -11,7 +11,7 @@ import {
|
||||
searchAccounts,
|
||||
updateAccount,
|
||||
} 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 {
|
||||
@@ -638,6 +638,7 @@ const app = new Hono<App>()
|
||||
app.get(
|
||||
'/openapi.json',
|
||||
describeRoute({ hide: true }),
|
||||
withCleanSpec(
|
||||
openAPIRouteHandler(app, {
|
||||
documentation: {
|
||||
info: {
|
||||
@@ -668,6 +669,7 @@ app.get(
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
export default app
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Hono } from 'hono'
|
||||
import { describeRoute, openAPIRouteHandler } from 'hono-openapi'
|
||||
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 { configRoutes } from './routes/config'
|
||||
@@ -58,6 +58,7 @@ const app = new Hono<App>({ strict: false })
|
||||
app.get(
|
||||
'/openapi.json',
|
||||
describeRoute({ hide: true }),
|
||||
withCleanSpec(
|
||||
openAPIRouteHandler(app, {
|
||||
documentation: {
|
||||
info: {
|
||||
@@ -95,6 +96,7 @@ app.get(
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
export default app
|
||||
|
||||
@@ -1990,4 +1990,16 @@ describe('openapi', () => {
|
||||
const raw = await res.text()
|
||||
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,
|
||||
verifyPassword,
|
||||
} 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 {
|
||||
@@ -720,6 +720,7 @@ const app = new Hono<App>()
|
||||
app.get(
|
||||
'/openapi.json',
|
||||
describeRoute({ hide: true }),
|
||||
withCleanSpec(
|
||||
openAPIRouteHandler(app, {
|
||||
documentation: {
|
||||
info: {
|
||||
@@ -750,6 +751,7 @@ app.get(
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
export default app
|
||||
|
||||
@@ -3,7 +3,7 @@ import { describeRoute, openAPIRouteHandler } from 'hono-openapi'
|
||||
import { useWorkersLogger } from 'workers-tagged-logger'
|
||||
|
||||
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'
|
||||
|
||||
// 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(
|
||||
'/openapi.json',
|
||||
describeRoute({ hide: true }),
|
||||
withCleanSpec(
|
||||
openAPIRouteHandler(app, {
|
||||
documentation: {
|
||||
info: {
|
||||
@@ -1231,6 +1232,7 @@ app.get(
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
export default app
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
setPresence,
|
||||
setRoomInstanceInProgress,
|
||||
} from '@repo/domain'
|
||||
import { withNotFound, withOnError } from '@repo/hono-helpers'
|
||||
import { withCleanSpec, withNotFound, withOnError } from '@repo/hono-helpers'
|
||||
import { validateAndGetAccountId } from '@repo/jwt'
|
||||
|
||||
import {
|
||||
@@ -1084,6 +1084,7 @@ async function sweepExpiredPresence(env: Env): Promise<void> {
|
||||
app.get(
|
||||
'/openapi.json',
|
||||
describeRoute({ hide: true }),
|
||||
withCleanSpec(
|
||||
openAPIRouteHandler(app, {
|
||||
documentation: {
|
||||
info: {
|
||||
@@ -1116,6 +1117,7 @@ app.get(
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
// 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 { getRequestLogData, type LogDataRequest } from './helpers/request'
|
||||
export * from './helpers/errors'
|
||||
export * from './helpers/openapi'
|
||||
export * from './helpers/url'
|
||||
export * from './middleware/withCache'
|
||||
export * from './middleware/withDefaultCors'
|
||||
|
||||
Reference in New Issue
Block a user