You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					205 lines
				
				5.7 KiB
			
		
		
			
		
	
	
					205 lines
				
				5.7 KiB
			| 
											3 years ago
										 | 'use strict' | ||
|  | 
 | ||
|  | const fs = require('fs') | ||
|  | const path = require('path') | ||
|  | const Ref = require('json-schema-resolver') | ||
|  | const cloner = require('rfdc')({ proto: true, circles: false }) | ||
|  | const { rawRequired } = require('../symbols') | ||
|  | const { xConsume } = require('../constants') | ||
|  | 
 | ||
|  | function addHook (fastify, pluginOptions) { | ||
|  |   const routes = [] | ||
|  |   const sharedSchemasMap = new Map() | ||
|  | 
 | ||
|  |   fastify.addHook('onRoute', (routeOptions) => { | ||
|  |     routes.push(routeOptions) | ||
|  |   }) | ||
|  | 
 | ||
|  |   fastify.addHook('onRegister', async (instance) => { | ||
|  |     // we need to wait the ready event to get all the .getSchemas()
 | ||
|  |     // otherwise it will be empty
 | ||
|  |     // TODO: better handle for schemaId
 | ||
|  |     // when schemaId is the same in difference instance
 | ||
|  |     // the latter will lost
 | ||
|  |     instance.addHook('onReady', (done) => { | ||
|  |       const allSchemas = instance.getSchemas() | ||
|  |       for (const schemaId of Object.keys(allSchemas)) { | ||
|  |         if (!sharedSchemasMap.has(schemaId)) { | ||
|  |           sharedSchemasMap.set(schemaId, allSchemas[schemaId]) | ||
|  |         } | ||
|  |       } | ||
|  |       done() | ||
|  |     }) | ||
|  |   }) | ||
|  | 
 | ||
|  |   return { | ||
|  |     routes, | ||
|  |     Ref () { | ||
|  |       const externalSchemas = cloner(Array.from(sharedSchemasMap.values())) | ||
|  |       return Ref(Object.assign( | ||
|  |         { applicationUri: 'todo.com' }, | ||
|  |         pluginOptions.refResolver, | ||
|  |         { clone: true, externalSchemas }) | ||
|  |       ) | ||
|  |     } | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | function shouldRouteHide (schema, opts) { | ||
|  |   const { hiddenTag, hideUntagged } = opts | ||
|  | 
 | ||
|  |   if (schema && schema.hide) { | ||
|  |     return true | ||
|  |   } | ||
|  | 
 | ||
|  |   const tags = (schema && schema.tags) || [] | ||
|  | 
 | ||
|  |   if (tags.length === 0 && hideUntagged) { | ||
|  |     return true | ||
|  |   } | ||
|  | 
 | ||
|  |   if (tags.includes(hiddenTag)) { | ||
|  |     return schema.tags.includes(hiddenTag) | ||
|  |   } | ||
|  | 
 | ||
|  |   return false | ||
|  | } | ||
|  | 
 | ||
|  | // The swagger standard does not accept the url param with ':'
 | ||
|  | // so '/user/:id' is not valid.
 | ||
|  | // This function converts the url in a swagger compliant url string
 | ||
|  | // => '/user/{id}'
 | ||
|  | // custom verbs at the end of a url are okay => /user::watch but should be rendered as /user:watch in swagger
 | ||
|  | const COLON = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_' | ||
|  | function formatParamUrl (str) { | ||
|  |   let i, char | ||
|  |   let state = 'skip' | ||
|  |   let path = '' | ||
|  |   let param = '' | ||
|  |   let level = 0 | ||
|  |   // count for regex if no param exist
 | ||
|  |   let regexp = 0 | ||
|  |   for (i = 0; i < str.length; i++) { | ||
|  |     char = str[i] | ||
|  |     switch (state) { | ||
|  |       case 'colon': { | ||
|  |         // we only accept a-zA-Z0-9_ in param
 | ||
|  |         if (COLON.indexOf(char) !== -1) { | ||
|  |           param += char | ||
|  |         } else if (char === '(') { | ||
|  |           state = 'regexp' | ||
|  |           level++ | ||
|  |         } else { | ||
|  |           // end
 | ||
|  |           state = 'skip' | ||
|  |           path += '{' + param + '}' | ||
|  |           path += char | ||
|  |           param = '' | ||
|  |         } | ||
|  |         break | ||
|  |       } | ||
|  |       case 'regexp': { | ||
|  |         if (char === '(') { | ||
|  |           level++ | ||
|  |         } else if (char === ')') { | ||
|  |           level-- | ||
|  |         } | ||
|  |         // we end if the level reach zero
 | ||
|  |         if (level === 0) { | ||
|  |           state = 'skip' | ||
|  |           if (param === '') { | ||
|  |             regexp++ | ||
|  |             param = 'regexp' + String(regexp) | ||
|  |           } | ||
|  |           path += '{' + param + '}' | ||
|  |           param = '' | ||
|  |         } | ||
|  |         break | ||
|  |       } | ||
|  |       default: { | ||
|  |         // we check if we need to change state
 | ||
|  |         if (char === ':' && str[i + 1] === ':') { | ||
|  |           // double colon -> single colon
 | ||
|  |           path += char | ||
|  |           // skip one more
 | ||
|  |           i++ | ||
|  |         } else if (char === ':') { | ||
|  |           // single colon -> state colon
 | ||
|  |           state = 'colon' | ||
|  |         } else if (char === '(') { | ||
|  |           state = 'regexp' | ||
|  |           level++ | ||
|  |         } else if (char === '*') { | ||
|  |           // * -> wildcard
 | ||
|  |           // should be exist once only
 | ||
|  |           path += '{wildcard}' | ||
|  |         } else { | ||
|  |           path += char | ||
|  |         } | ||
|  |       } | ||
|  |     } | ||
|  |   } | ||
|  |   // clean up
 | ||
|  |   if (state === 'colon' && param !== '') { | ||
|  |     path += '{' + param + '}' | ||
|  |   } | ||
|  |   return path | ||
|  | } | ||
|  | 
 | ||
|  | function resolveLocalRef (jsonSchema, externalSchemas) { | ||
|  |   if (typeof jsonSchema.type !== 'undefined' && typeof jsonSchema.properties !== 'undefined') { | ||
|  |     // for the shorthand querystring/params/headers declaration
 | ||
|  |     const propertiesMap = Object.keys(jsonSchema.properties).reduce((acc, headers) => { | ||
|  |       const rewriteProps = {} | ||
|  |       rewriteProps.required = (Array.isArray(jsonSchema.required) && jsonSchema.required.indexOf(headers) >= 0) || false | ||
|  |       // save raw required for next restore in the content/<media-type>
 | ||
|  |       if (jsonSchema.properties[headers][xConsume]) { | ||
|  |         rewriteProps[rawRequired] = jsonSchema.properties[headers].required | ||
|  |       } | ||
|  |       const newProps = Object.assign({}, jsonSchema.properties[headers], rewriteProps) | ||
|  | 
 | ||
|  |       return Object.assign({}, acc, { [headers]: newProps }) | ||
|  |     }, {}) | ||
|  | 
 | ||
|  |     return propertiesMap | ||
|  |   } | ||
|  | 
 | ||
|  |   // for oneOf, anyOf, allOf support in querystring/params/headers
 | ||
|  |   if (jsonSchema.oneOf || jsonSchema.anyOf || jsonSchema.allOf) { | ||
|  |     const schemas = jsonSchema.oneOf || jsonSchema.anyOf || jsonSchema.allOf | ||
|  |     return schemas.reduce(function (acc, schema) { | ||
|  |       const json = resolveLocalRef(schema, externalSchemas) | ||
|  |       return { ...acc, ...json } | ||
|  |     }, {}) | ||
|  |   } | ||
|  | 
 | ||
|  |   // $ref is in the format: #/definitions/<resolved definition>/<optional fragment>
 | ||
|  |   const localRef = jsonSchema.$ref.split('/')[2] | ||
|  |   return resolveLocalRef(externalSchemas[localRef], externalSchemas) | ||
|  | } | ||
|  | 
 | ||
|  | function readPackageJson () { | ||
|  |   try { | ||
|  |     return JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'package.json'))) | ||
|  |   } catch (err) { | ||
|  |     return {} | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | function resolveSwaggerFunction (opts, cache, routes, Ref, done) { | ||
|  |   if (typeof opts.openapi === 'undefined' || opts.openapi === null) { | ||
|  |     return require('../spec/swagger')(opts, cache, routes, Ref, done) | ||
|  |   } else { | ||
|  |     return require('../spec/openapi')(opts, cache, routes, Ref, done) | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | module.exports = { | ||
|  |   addHook, | ||
|  |   shouldRouteHide, | ||
|  |   readPackageJson, | ||
|  |   formatParamUrl, | ||
|  |   resolveLocalRef, | ||
|  |   resolveSwaggerFunction | ||
|  | } |