import { Logger } from '@dunx/core'; import type { HttpApp } from '@dunx/http'; import { danglingRefs, OpenApiExplorer, type OpenApiDocument, } from '@dunx/openapi'; const documentAt = async ( url: string, path = 'api/openapi.json', ): Promise<[Response, OpenApiDocument]> => { const response = await fetch(new URL(path, url)); return [response, (await response.json()) as OpenApiDocument]; }; export class DocsDemo { constructor(private readonly logger: Logger) {} /** Served by a controller in the same graph, so it goes through the same * middleware and describes the paths the app really mounted. */ async demonstrate(app: HttpApp, url: string): Promise { const { logger } = this; const [response, document] = await documentAt(url); logger.info( `GET /api/openapi.json -> ${response.status} openapi ${document.openapi}, ` + `${Object.keys(document.paths).length} paths`, ); logger.info(`paths: ${JSON.stringify(Object.keys(document.paths))}`); logger.info( `components/schemas: ${JSON.stringify(Object.keys(document.components.schemas))}`, ); const create = document.paths['/api/users']?.post; logger.info( `POST /api/users requestBody -> ` + JSON.stringify( create?.requestBody?.content['application/json']?.schema, ), ); logger.info( `POST /api/users 400 -> ` + JSON.stringify(create?.responses['400']?.content?.['application/json']), ); // A named response schema becomes a component the operation $refs. It is // documentation only: nothing validates a response. const one = document.paths['/api/users/{id}']?.get; logger.info( `GET /api/users/{id} responses -> ` + JSON.stringify( Object.fromEntries( Object.entries(one?.responses ?? {}).map(([status, response]) => [ status, response.content?.['application/json']?.schema, ]), ), ), ); const list = document.paths['/api/users']?.get; logger.info(`GET /api/users query -> ${JSON.stringify(list?.parameters)}`); // A $ref resolving to nothing renders as an empty box and reports no error. logger.info( `unresolved $refs: ${danglingRefs(document).length}, warnings: ` + JSON.stringify(app.get(OpenApiExplorer).warnings), ); const page = await fetch(new URL('api/docs', url)); const html = await page.text(); logger.info( `GET /api/docs -> ${page.status} ${page.headers.get('content-type')}, ` + `${html.length} bytes of Swagger UI shell`, ); /** * The page fetches two `swagger-ui-dist` assets, and the guarantee is that * they are same-origin: no CDN. Script bodies are stripped first, since a * `src=` inside a `