UNPKG

hono-openapi

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