UNPKG

egg-joi-swagger

Version:

joi + swagger + validate plugin for eggjs

192 lines (171 loc) 5.64 kB
'use strict'; const Joi = require('joi'); const shins = require('shins'); const assert = require('assert'); const converter = require('widdershins'); const { dejoi } = require('joi2json'); const { isPlainObject } = require('lodash'); let html = ''; module.exports = () => { return async function doc(ctx, next) { const { app } = ctx; const joiSwaggerConfig = app.config.joiSwagger; if (ctx.path === joiSwaggerConfig.routePath) { if (html) { ctx.body = html; return; } const { schema } = app; const { components } = schema; const { pkg } = app.config; const paths = {}; for (const schemaKey in schema) { if (schemaKey === 'components') continue; const entity = schema[schemaKey]; for (const key in entity) { const item = Object.assign({}, entity[key]); const parameters = []; if (item.parameters) { let { query, path } = item.parameters; if (query) { if (isPlainObject(query)) query = Joi.object().keys(query); query = dejoi(query); for (const name in query.properties) { parameters.push({ in: 'query', name, // eslint-disable-next-line no-bitwise required: query.required && !!~query.required.indexOf(name), schema: query.properties[name], description: query.properties[name].description, }); } } if (path) { assert(isPlainObject || path.isJoi); if (isPlainObject(path)) { path = Joi.object().keys(path); } path = dejoi(path); for (const name in path.properties) { parameters.push({ in: 'path', name, required: true, schema: path.properties[name], description: path.properties[name].description, }); } } } let responses = {}; if (item.responses) { for (const status in item.responses) { let resSchema = item.responses[status].schema; if (isPlainObject(resSchema)) { resSchema = Joi.object(resSchema); } responses[status] = { content: { 'application/json': { schema: dejoi(resSchema), }, }, }; } } else { responses = { 200: { content: { 'application/json': { schema: {}, }, }, }, }; } item.tags = [ schemaKey ]; item.operationId = key; item.parameters = parameters; item.responses = responses; if ([ 'put', 'post', 'patch' ].includes(item.method)) { let { body, required } = item.requestBody; assert(body); if (isPlainObject(body)) { body = Joi.object().keys(body); } body = dejoi(body); if (required) body.required = required; item.requestBody = { required: true, content: { 'application/json': { schema: body, }, }, }; } if (!paths[item.path]) paths[item.path] = {}; paths[item.path][item.method] = item; } } const swagger = { openapi: '3.0.0', info: { title: pkg.name, version: pkg.version, description: pkg.description, }, servers: joiSwaggerConfig.servers, paths, security: joiSwaggerConfig.security || [], components: { ...components, securitySchemes: joiSwaggerConfig.securitySchemes }, }; const options = {}; options.codeSamples = true; options.httpsnippet = false; options.theme = joiSwaggerConfig.sampleTheme ? joiSwaggerConfig.sampleTheme.toLowerCase() : 'darkula'; options.search = true; options.discovery = false; options.shallowSchemas = false; options.tocSummary = true; options.headings = 2; options.verbose = false; options.omitBody = true; options.language_tabs = []; options.sample = true; Object.getOwnPropertyNames(joiSwaggerConfig.sampleLanguages).forEach(lang => { const obj = {}; obj[lang] = joiSwaggerConfig.sampleLanguages[lang]; options.language_tabs.push(obj); }); // Shin options const shinOptions = {}; shinOptions.minify = true; shinOptions.inline = true; shinOptions.unsafe = false; shinOptions['no-link'] = true; shinOptions.customCss = true; // Check for custom logo option if (joiSwaggerConfig.customLogo) { shinOptions.logo = joiSwaggerConfig.customLogo; } html = await new Promise((resolve, reject) => { converter.convert(swagger, options, (err, markdownString) => { if (err) { return reject(err); } shins.render(markdownString, shinOptions, (err, html) => { if (err) { return reject(err); } resolve(html); }); }); }); ctx.body = html; } else { await next(); } }; };