hono-openapi
Version:
OpenAPI schema generator for Hono
2 lines (1 loc) • 4.66 kB
JavaScript
import{u as e}from"./utils.js";export{g as generateValidatorDocs}from"./utils.js";import{HTTPException as t}from"hono/http-exception";const s=["GET","PUT","POST","DELETE","OPTIONS","HEAD","PATCH","TRACE"],n=e=>e.charAt(0).toUpperCase()+e.slice(1),o=new Map,a=(e,t)=>{const s=`${e}:${t}`;if(o.has(s))return o.get(s);let a=e;if("/"===t)return`${a}Index`;for(const e of t.split("/"))123===e.charCodeAt(0)?a+=`By${n(e.slice(1,-1))}`:a+=n(e);return o.set(s,a),a},r=new Map;function c(e,t,s){return e&&t in e?e[t]??s:s}function i(...e){return e.reduce(((e,t)=>{if(!t)return e;let s;return("tags"in e&&e.tags||"tags"in t&&t.tags)&&(s=[...c(e,"tags",[]),...c(t,"tags",[])]),{...e,...t,tags:s,responses:{...c(e,"responses",{}),...c(t,"responses",{})},parameters:m(e.parameters,t.parameters)}}),{})}function p({path:e,method:t,data:s,schema:n}){e=(e=>e.split("/").map((e=>{let t=e;return t.startsWith(":")&&(t=t.slice(1,t.length),t.endsWith("?")&&(t=t.slice(0,-1)),t=`{${t}}`),t})).join("/"))(e);const o=t.toLowerCase();if("all"===o)if(r.has(e)){const t=r.get(e)??{};r.set(e,{...t,...s,parameters:m(t.parameters,s.parameters)})}else r.set(e,s);else{const t=function(e){const t=Array.from(r.keys());let s={};for(const n of t)e.match(n)&&(s=i(s,r.get(n)??{}));return s}(e);n[e]={...n[e]?n[e]:{},[o]:{responses:{},operationId:a(o,e),...i(t,n[e]?.[o],s)}}}}const l=e=>"$ref"in e?e.$ref:`${e.in} ${e.name}`;function m(...e){const t=e.flatMap((e=>e??[])).reduce(((e,t)=>(e.set(l(t),t),e)),new Map);return Array.from(t.values())}function u(e,{excludeStaticFile:t=!0,exclude:s=[]}){const n={},o=Array.isArray(s)?s:[s];for(const[s,a]of Object.entries(e))if(!(o.some((e=>"string"==typeof e?s===e:e.test(s)))||s.includes("*")||t&&s.includes("."))){for(const e of Object.keys(a)){const t=a[e];if(s.includes("{")){t.parameters||(t.parameters=[]);const e=s.split("/").filter((e=>e.startsWith("{")&&!t.parameters.find((t=>"path"===t.in&&t.name===e.slice(1,e.length-1)))));for(const s of e){const e=s.slice(1,s.length-1),n=t.parameters.findIndex((t=>"param"===t.in&&t.name===e));-1!==n?t.parameters[n].in="path":t.parameters.push({schema:{type:"string"},in:"path",name:e,required:!0})}}t.responses||(t.responses={200:{}})}n[s]=a}return n}function f(e,t){const s={version:"3.1.0",components:{}};let n=null;return async o=>(n||(n=await d(e,t,s,o)),o.json(n))}async function d(t,{documentation:n={},excludeStaticFile:o=!0,exclude:a=[],excludeMethods:r=["OPTIONS"],excludeTags:c=[],defaultOptions:i}={documentation:{},excludeStaticFile:!0,exclude:[],excludeMethods:["OPTIONS"],excludeTags:[]},{version:l="3.1.0",components:m={}}={version:"3.1.0",components:{}},f){const d={version:l,components:m},h={};for(const n of t.routes){if(!(e in n.handler))continue;if(r.includes(n.method))continue;if(!1===s.includes(n.method)&&"ALL"!==n.method)continue;const{resolver:t,metadata:o={}}=n.handler[e],a=i?.[n.method],{docs:c,components:l}=await t({...d,...o},a);d.components={...d.components,...l??{}},p({method:n.method,path:n.path,data:c,schema:h})}for(const e in h)for(const t in h[e]){const s=h[e][t]?.hide;s&&("boolean"==typeof s?s:f&&s(f))&&delete h[e][t]}return{openapi:d.version,...{...n,tags:n.tags?.filter((e=>!c?.includes(e?.name))),info:{title:"Hono Documentation",description:"Development documentation",version:"0.0.0",...n.info},paths:{...u(h,{excludeStaticFile:o,exclude:a}),...n.paths},components:{...n.components,schemas:{...d.components,...n.components?.schemas}}}}}function h(s){const{validateResponse:n,...o}=s;return Object.assign((async(e,o)=>{if(await o(),n&&s.responses){const o=e.res.status,a=e.res.headers.get("content-type");if(o&&a){const r=s.responses[o];if(r&&"content"in r&&r.content){const s=a.split(";")[0],o=r.content[s];if(o?.schema&&"validator"in o.schema)try{let t;const n=e.res.clone();if("application/json"===s?t=await n.json():"text/plain"===s&&(t=await n.text()),!t)throw new Error("No data to validate!");await o.schema.validator(t)}catch(e){let s={status:500,message:"Response validation failed!"};throw"object"==typeof n&&(s={...s,...n}),new t(s.status,{message:s.message,cause:e})}}}}}),{[e]:{resolver:(e,t)=>x(e,o,t)}})}async function x(e,t,s={}){let n={};const o={...s,...t,responses:{...s?.responses,...t.responses}};if(o.responses)for(const t of Object.keys(o.responses)){const s=o.responses[t];if(s&&"content"in s)for(const t of Object.keys(s.content??{})){const o=s.content?.[t];if(o&&(o.schema&&"builder"in o.schema)){const t=await o.schema.builder(e);o.schema=t.schema,t.components&&(n={...n,...t.components})}}}return{docs:o,components:n}}export{h as describeRoute,x as generateRouteSpecs,d as generateSpecs,f as openAPISpecs,e as uniqueSymbol};