exports = module.exports = internals.paths = function(settings) { this.settings = settings; this.definitions = new Definitions(settings); this.properties = new Properties(settings, {}, {}); this.responses = new Responses(settings, {}, {}); this.defaults = { responses: {} }; this.schema = Joi.object({ tags: Joi.array().items(Joi.string()), summary: Joi.string(), description: Joi.string(), externalDocs: Joi.object({ description: Joi.string(), url: Joi.string().uri() }), operationId: Joi.string(), consumes: Joi.array().items(Joi.string()), produces: Joi.array().items(Joi.string()), parameters: Joi.array().items(Joi.object()), responses: Joi.object().required(), schemes: Joi.array().items(Joi.string().valid(['http', 'https', 'ws', 'wss'])), deprecated: Joi.boolean(), security: Joi.array().items(Joi.object()) }); };
lab.test('reuseDefinitions = false', async () => { // forces two models even though the model hash is the same const tempRoutes = [ { method: 'POST', path: '/store1/', options: { handler: Helper.defaultHandler, tags: ['api'], validate: { payload: Joi.object({ a: Joi.number(), b: Joi.number(), operator: Joi.string(), equals: Joi.number() }).label('A') } } }, { method: 'POST', path: '/store2/', options: { handler: Helper.defaultHandler, tags: ['api'], validate: { payload: Joi.object({ a: Joi.number(), b: Joi.number(), operator: Joi.string(), equals: Joi.number() }).label('B') } } } ]; const server = await Helper.createServer({ reuseDefinitions: false }, tempRoutes); const response = await server.inject({ method: 'GET', url: '/swagger.json' }); //console.log(JSON.stringify(response.result)); expect(response.statusCode).to.equal(200); expect(response.result.definitions.A).to.exist(); expect(response.result.definitions.B).to.exist(); const isValid = await Validate.test(response.result); expect(isValid).to.be.true(); });
lab.test('test that name changing for required', async () => { const FormDependencyDefinition = Joi.object({ id: Joi.number().required() }).label('FormDependencyDefinition'); const ActionDefinition = Joi.object({ id: Joi.number() .required() .allow(null), reminder: FormDependencyDefinition.required() }).label('ActionDefinition'); let testRoutes = [ { method: 'POST', path: '/server/', options: { handler: Helper.defaultHandler, tags: ['api'], validate: { payload: ActionDefinition } } } ]; const server = await Helper.createServer({}, testRoutes); const response = await server.inject({ method: 'GET', url: '/swagger.json' }); expect(response.statusCode).to.equal(200); expect(response.result.definitions.ActionDefinition).to.equal({ type: 'object', properties: { id: { type: 'number' }, reminder: { $ref: '#/definitions/FormDependencyDefinition' } }, required: ['id', 'reminder'] }); });
['queryParams', 'pathParams', 'headerParams', 'payloadParams'].forEach(function(property) { // swap out any custom validation function for Joi object/string if (Utilities.isFunction(routeData[property])) { if (property !== 'pathParams') { self.settings.log( ['validation', 'warning'], 'Using a Joi.function for a query, header or payload is not supported.' ); if (property === 'payloadParams') { routeData[property] = Joi.object().label('Hidden Model'); } else { routeData[property] = Joi.object({ 'Hidden Model': Joi.string() }); } } else { self.settings.log( ['validation', 'error'], 'Using a Joi.function for a params is not supported and has been removed.' ); routeData[property] = null; } } });
lab.test('test that optional array is not in swagger output', async () => { let testRoutes = [ { method: 'POST', path: '/server/1/', options: { handler: Helper.defaultHandler, tags: ['api'], validate: { payload: Joi.object({ a: Joi.number().required(), b: Joi.string().optional() }).label('test') } } } ]; const server = await Helper.createServer({}, testRoutes); const response = await server.inject({ method: 'GET', url: '/swagger.json' }); expect(response.statusCode).to.equal(200); expect(response.result.definitions.test).to.equal({ type: 'object', properties: { a: { type: 'number' }, b: { type: 'string' } }, required: ['a'] }); });
'use strict'; const Boom = require('@hapi/boom'); const Hoek = require('@hapi/hoek'); const Joi = require('@hapi/joi'); const internals = {}; internals.schema = Joi.object({ sampleInterval: Joi.number().min(0), maxHeapUsedBytes: Joi.number().min(0), maxEventLoopDelay: Joi.number().min(0), maxRssBytes: Joi.number().min(0) }) .unknown(); internals.defaults = { sampleInterval: 0, // Frequency of load sampling in milliseconds (zero is no sampling) maxHeapUsedBytes: 0, // Reject requests when V8 heap is over size in bytes (zero is no max) maxRssBytes: 0, // Reject requests when process RSS is over size in bytes (zero is no max) maxEventLoopDelay: 0 // Milliseconds of delay after which requests are rejected (zero is no max) }; exports = module.exports = internals.Heavy = function (options) { options = options || {};
routes.forEach(route => { let routeOptions = Hoek.reach(route, 'settings.plugins.hapi-swagger') || {}; let routeData = { path: route.path, method: route.method.toUpperCase(), description: route.settings.description, notes: route.settings.notes, tags: Hoek.reach(route, 'settings.tags'), queryParams: Hoek.reach(route, 'settings.validate.query'), pathParams: Hoek.reach(route, 'settings.validate.params'), payloadParams: Hoek.reach(route, 'settings.validate.payload'), responseSchema: Hoek.reach(route, 'settings.response.schema'), responseStatus: Hoek.reach(route, 'settings.response.status'), headerParams: Hoek.reach(route, 'settings.validate.headers'), consumes: Hoek.reach(routeOptions, 'consumes') || null, produces: Hoek.reach(routeOptions, 'produces') || null, responses: Hoek.reach(routeOptions, 'responses') || null, payloadType: Hoek.reach(routeOptions, 'payloadType') || null, security: Hoek.reach(routeOptions, 'security') || null, order: Hoek.reach(routeOptions, 'order') || null, deprecated: Hoek.reach(routeOptions, 'deprecated') || null, id: Hoek.reach(routeOptions, 'id') || null, groups: route.group }; Utilities.assignVendorExtensions(routeData, routeOptions); routeData.path = Utilities.replaceInPath(routeData.path, ['endpoints'], this.settings.pathReplacements); // user configured interface through route plugin options if (Hoek.reach(routeOptions, 'validate.query')) { routeData.queryParams = Utilities.toJoiObject(Hoek.reach(routeOptions, 'validate.query')); } if (Hoek.reach(routeOptions, 'validate.params')) { routeData.pathParams = Utilities.toJoiObject(Hoek.reach(routeOptions, 'validate.params')); } if (Hoek.reach(routeOptions, 'validate.headers')) { routeData.headerParams = Utilities.toJoiObject(Hoek.reach(routeOptions, 'validate.headers')); } if (Hoek.reach(routeOptions, 'validate.payload')) { // has different structure, just pass straight through routeData.payloadParams = Hoek.reach(routeOptions, 'validate.payload'); // if its a native javascript object convert it to JOI if (!routeData.payloadParams.isJoi) { routeData.payloadParams = Joi.object(routeData.payloadParams); } } // swap out any custom validation function for Joi object/string ['queryParams', 'pathParams', 'headerParams', 'payloadParams'].forEach(function(property) { // swap out any custom validation function for Joi object/string if (Utilities.isFunction(routeData[property])) { if (property !== 'pathParams') { self.settings.log( ['validation', 'warning'], 'Using a Joi.function for a query, header or payload is not supported.' ); if (property === 'payloadParams') { routeData[property] = Joi.object().label('Hidden Model'); } else { routeData[property] = Joi.object({ 'Hidden Model': Joi.string() }); } } else { self.settings.log( ['validation', 'error'], 'Using a Joi.function for a params is not supported and has been removed.' ); routeData[property] = null; } } }); // hapi wildcard method support if (routeData.method === '*') { // OPTIONS not supported by Swagger and HEAD not support by HAPI ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].forEach(method => { var newRoute = Hoek.clone(routeData); newRoute.method = method.toUpperCase(); routesData.push(newRoute); }); } else { routesData.push(routeData); } });
const js2xmlparser = require('js2xmlparser'); const sumModel = Joi.object({ id: Joi.string() .required() .example('x78P9c'), a: Joi.number() .required() .example(5), b: Joi.number() .required() .example(5), operator: Joi.string() .required() .description('either +, -, /, or *') .example('+'), equals: Joi.number() .required() .example(10), created: Joi.string() .required() .isoDate() .description('ISO date string') .example('2015-12-01'), modified: Joi.string() .isoDate() .description('ISO date string') .example('2015-12-01') }) .label('Sum') .description('json body for sum');
'use strict'; const Stream = require('stream'); const Boom = require('@hapi/boom'); const Cryptiles = require('@hapi/cryptiles'); const Hoek = require('@hapi/hoek'); const Joi = require('@hapi/joi'); const internals = { restfulValidatedMethods: ['post', 'put', 'patch', 'delete'] }; internals.schema = Joi.object().keys({ key: Joi.string().optional(), size: Joi.number().optional(), autoGenerate: Joi.boolean().optional(), addToViewContext: Joi.boolean().optional(), cookieOptions: Joi.object().keys(null), headerName: Joi.string().optional(), restful: Joi.boolean().optional(), skip: Joi.func().optional(), enforce: Joi.boolean().optional(), logUnauthorized: Joi.boolean().optional() }); internals.defaults = { key: 'crumb',
internals.schema = Joi.object({ provider: Joi.object({ name: Joi.string().optional().default('custom'), protocol: Joi.string().valid('oauth', 'oauth2'), temporary: Joi.string().when('protocol', { is: 'oauth', then: Joi.required(), otherwise: Joi.forbidden() }), signatureMethod: Joi.string().valid('HMAC-SHA1', 'RSA-SHA1').when('protocol', { is: 'oauth', then: Joi.default('HMAC-SHA1'), otherwise: Joi.forbidden() }), auth: Joi.string().required(), useParamsAuth: internals.flexBoolean.default(false).when('protocol', { is: 'oauth2', then: Joi.optional(), otherwise: Joi.forbidden() }), token: Joi.string().required(), headers: Joi.object(), profile: Joi.func(), profileMethod: Joi.string().valid('get', 'post').default('get'), scope: Joi.alternatives().try( Joi.array().items(Joi.string()), Joi.func() ).when('protocol', { is: 'oauth2', otherwise: Joi.forbidden() }), scopeSeparator: Joi.string().when('protocol', { is: 'oauth2', otherwise: Joi.forbidden() }) }).required(), password: Joi.string().required(), clientId: Joi.string().required(), clientSecret: Joi.alternatives().when('protocol', { is: 'oauth', then: Joi.string().required().allow(''), otherwise: Joi.alternatives().try(Joi.string().allow(''), Joi.object()) }).required(), cookie: Joi.string(), isSameSite: Joi.valid('Strict', 'Lax').allow(false).default('Strict'), isSecure: internals.flexBoolean, isHttpOnly: internals.flexBoolean, ttl: Joi.number(), domain: Joi.string().allow(null), providerParams: Joi.alternatives().try(Joi.object(), Joi.func()), allowRuntimeProviderParams: internals.flexBoolean.default(false), scope: Joi.alternatives().try( Joi.array().items(Joi.string()), Joi.func() ).when('provider.protocol', { is: 'oauth2', otherwise: Joi.forbidden() }), name: Joi.string().required(), config: Joi.object(), profileParams: Joi.object(), skipProfile: internals.flexBoolean.optional().default(false), forceHttps: internals.flexBoolean.optional().default(false), location: Joi.alternatives().try( Joi.func().maxArity(1), Joi.string() ).default(false), runtimeStateCallback: Joi.func().optional() });
module.exports = Joi.object({ baseUri: Joi.array().items(Joi.string()).single().default(['self']), childSrc: Joi.array().items(Joi.string()).single(), connectSrc: Joi.array().items(Joi.string()).single().default(['self']), defaultSrc: Joi.array().items(Joi.string()).single().default(['none']), fontSrc: Joi.array().items(Joi.string()).single(), formAction: Joi.array().items(Joi.string()).single(), frameAncestors: Joi.array().items(Joi.string()).single(), frameSrc: Joi.array().items(Joi.string()).single(), imgSrc: Joi.array().items(Joi.string()).single().default(['self']), manifestSrc: Joi.array().items(Joi.string()).single(), mediaSrc: Joi.array().items(Joi.string()).single(), objectSrc: Joi.array().items(Joi.string()).single(), oldSafari: Joi.boolean(), pluginTypes: Joi.array().items(Joi.string()).single(), reflectedXss: Joi.string().valid('allow', 'block', 'filter'), reportOnly: Joi.boolean(), reportUri: Joi.string(), requireSriFor: Joi.array().items(Joi.string()).single(), sandbox: [ Joi.array().items(Joi.string().valid('allow-forms', 'allow-same-origin', 'allow-scripts', 'allow-top-navigation')).single(), Joi.boolean() ], scriptSrc: Joi.array().items(Joi.string()).single().default(['self']) .when('generateNonces', { is: [false, 'style'], then: Joi.array().items(Joi.string().valid('strict-dynamic').forbidden()) }), styleSrc: Joi.array().items(Joi.string()).single().default(['self']) .when('generateNonces', { is: [false, 'script'], then: Joi.array().items(Joi.string().valid('strict-dynamic').forbidden()) }), workerSrc: Joi.array().items(Joi.string()).single().default(['self']), generateNonces: Joi.alternatives().try([Joi.boolean(), Joi.string().valid('script', 'style')]).default(true) }).with('reportOnly', 'reportUri');
'use strict'; const Joi = require('@hapi/joi'); const internals = { schema: Joi.object({ uri: Joi.string().uri().required() }).required() }; exports = module.exports = function (options) { const settings = Joi.attempt(options, internals.schema); return { protocol: 'oauth2', useParamsAuth: true, auth: settings.uri + '/oauthserver/auth/', token: settings.uri + '/oauthserver/token/', scope: ['whoami'], scopeSeparator: ',', profile: async function (credentials, params, get) { const query = { access_token: credentials.token }; const profile = await get(settings.uri + '/api/user.whoami', query);
const baseSchema = require('./base-schema'); const defaults = require('./defaults'); const regExpObject = require('./reg-exp-object'); // Add some constraints that apply to both generateSW and generateSWString. module.exports = baseSchema.keys({ cacheId: joi.string(), cleanupOutdatedCaches: joi.boolean().default(defaults.cleanupOutdatedCaches), clientsClaim: joi.boolean().default(defaults.clientsClaim), directoryIndex: joi.string(), ignoreURLParametersMatching: joi.array().items(regExpObject), navigateFallback: joi.string().default(defaults.navigateFallback), navigateFallbackBlacklist: joi.array().items(regExpObject), navigateFallbackWhitelist: joi.array().items(regExpObject), navigationPreload: joi.boolean().default(defaults.navigationPreload), offlineGoogleAnalytics: joi.alternatives().try(joi.boolean(), joi.object()) .default(defaults.offlineGoogleAnalytics), runtimeCaching: joi.array().items(joi.object().keys({ method: joi.string().valid( 'DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT' ), urlPattern: [regExpObject, joi.string(), joi.func()], handler: [ joi.func(), joi.string().valid( 'CacheFirst',
'use strict'; const Joi = require('@hapi/joi'); const internals = { schema: Joi.object({ express: Joi.boolean().optional() }).optional() }; exports = module.exports = (options = {}) => { const settings = Joi.attempt(options, internals.schema); const domain = 'connect.stripe.com'; const authPrefix = settings.express ? '/express' : ''; return { protocol: 'oauth2', useParamsAuth: true, auth: `https://${domain}${authPrefix}/oauth/authorize`, token: `https://${domain}/oauth/token`, scope: [], headers: { 'User-Agent': 'hapi-bell-stripe' }, profile: async function (credentials, params, get) { const user = `https://${credentials.token}@${domain}/v1/account`; const profile = await get(user); credentials.profile = {
lab.test('definitionPrefix = useLabel', async () => { // use the label as a prefix for dynamic model names const tempRoutes = [ { method: 'POST', path: '/store1/', options: { handler: Helper.defaultHandler, tags: ['api'], validate: { payload: Joi.object({ a: Joi.number(), b: Joi.number(), operator: Joi.string(), equals: Joi.number() }).label('A') } } }, { method: 'POST', path: '/store2/', options: { handler: Helper.defaultHandler, tags: ['api'], validate: { payload: Joi.object({ c: Joi.number(), v: Joi.number(), operator: Joi.string(), equals: Joi.number() }).label('A A') } } }, { method: 'POST', path: '/store3/', options: { handler: Helper.defaultHandler, tags: ['api'], validate: { payload: Joi.object({ c: Joi.number(), f: Joi.number(), operator: Joi.string(), equals: Joi.number() }).label('A') } } } ]; const server = await Helper.createServer({ definitionPrefix: 'useLabel' }, tempRoutes); const response = await server.inject({ method: 'GET', url: '/swagger.json' }); expect(response.statusCode).to.equal(200); expect(response.result.definitions.A).to.exist(); expect(response.result.definitions['A A']).to.exist(); expect(response.result.definitions['A 1']).to.exist(); const isValid = await Validate.test(response.result); expect(isValid).to.be.true(); });
const internals = {}; internals.schema = Joi.object({ strictHeader: Joi.boolean(), ignoreErrors: Joi.boolean(), isSecure: Joi.boolean(), isHttpOnly: Joi.boolean(), isSameSite: Joi.valid('Strict', 'Lax').allow(false), path: Joi.string().allow(null), domain: Joi.string().allow(null), ttl: Joi.number().allow(null), encoding: Joi.string().valid('base64json', 'base64', 'form', 'iron', 'none'), sign: Joi.object({ password: [Joi.string(), Joi.binary(), Joi.object()], integrity: Joi.object() }), iron: Joi.object(), password: [Joi.string(), Joi.binary(), Joi.object()], // Used by hapi clearInvalid: Joi.boolean(), autoValue: Joi.any(), passThrough: Joi.boolean() }); internals.defaults = { strictHeader: true, // Require an RFC 6265 compliant header format
/** * @type {Object} * @private * * Store internal objects */ const internals = { regexp: { braces: /[{}']+/g, params: /\{(\w+\*?|\w+\?|\w+\*[1-9][0-9]*)\}/g, wildcard: /\*$/g }, scheme: { params: { query: joi.object(), params: joi.object() }, options: { secure: joi.boolean(), rel: joi.boolean().default(false), host: joi.string() } } } /** * @function * @private * * Check if condition is true and throw error if not
}; internals.schema = Joi.object({ httpClient: Joi.object({ request: Joi.func(), parseCacheControl: Joi.func() }), host: Joi.string(), port: Joi.number().integer(), protocol: Joi.string().valid('http', 'https', 'http:', 'https:'), uri: Joi.string(), passThrough: Joi.boolean(), localStatePassThrough: Joi.boolean(), acceptEncoding: Joi.boolean().when('passThrough', { is: true, otherwise: Joi.forbidden() }), rejectUnauthorized: Joi.boolean(), xforward: Joi.boolean(), redirects: Joi.number().min(0).integer().allow(false), timeout: Joi.number().integer(), mapUri: Joi.func(), onResponse: Joi.func(), onRequest: Joi.func(), agent: Joi.object(), ttl: Joi.string().valid('upstream').allow(null), maxSockets: Joi.number().positive().allow(false), secureProtocol: Joi.string(), ciphers: Joi.string(), downstreamResponseTime: Joi.boolean() }) .xor('host', 'mapUri', 'uri') .without('mapUri', 'port') .without('mapUri', 'protocol')
'use strict' const Joi = require('@hapi/joi') const pull = require('pull-stream') const ndjson = require('pull-ndjson') const { PassThrough } = require('readable-stream') module.exports = { validate: { query: Joi.object().keys({ n: Joi.alternatives() .when('count', { is: Joi.any().exist(), then: Joi.any().forbidden(), otherwise: Joi.number().integer().greater(0) }), count: Joi.number().integer().greater(0), arg: Joi.string().required() }).unknown() }, async handler (request, h) { const { ipfs } = request.server.app const peerId = request.query.arg // Default count to 10 const count = request.query.n || request.query.count || 10 const responseStream = await new Promise((resolve, reject) => { const stream = new PassThrough() pull(
const DEFAULT = { width: 1300, height: 900, timeout: 30000, maxImageFileSize: 10240, minify: true, inline: false, strict: false, extract: false, inlineImages: false, concurrency: Infinity, include: [], }; const schema = Joi.object() .keys({ html: Joi.string(), src: [Joi.string(), Joi.object()], css: [Joi.string(), Joi.array()], base: Joi.string(), strict: Joi.boolean().default(DEFAULT.strict), extract: Joi.boolean().default(DEFAULT.extract), inlineImages: Joi.boolean().default(DEFAULT.inlineImages), postcss: Joi.array(), ignore: [Joi.array(), Joi.object().unknown(true)], width: Joi.number().default(DEFAULT.width), height: Joi.number().default(DEFAULT.height), minify: Joi.boolean().default(DEFAULT.minify), dimensions: Joi.array().items({width: Joi.number(), height: Joi.number()}), inline: [Joi.boolean().default(DEFAULT.inline), Joi.object().unknown(true)],
'use strict'; const Hoek = require('@hapi/hoek'); const Joi = require('@hapi/joi'); const internals = {}; internals.schema = Joi.object({ uri: Joi.string().uri().optional(), extendedProfile: Joi.boolean().optional(), identityServiceProfile: Joi.boolean().optional().when('extendedProfile', { is: false, then: Joi.invalid(true) }) }); internals.defaults = { uri: 'https://login.salesforce.com', extendedProfile: true, identityServiceProfile: false }; exports = module.exports = function (options) { const combinedSettings = Hoek.applyToDefaults(internals.defaults, options || {}); const settings = Joi.attempt(combinedSettings, internals.schema); return { protocol: 'oauth2', auth: settings.uri + '/services/oauth2/authorize',
lab.experiment('lout examples', () => { // these are example are taken from https://github.com/hapijs/lout/blob/master/test/routes/default.js /* Copyright (c) 2012-2014, Walmart and other contributors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source '@hapi/code' must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The names of any contributors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ const routes = [ { method: 'GET', path: '/test', config: { handler: Helper.defaultHandler, validate: { query: { param1: Joi.string() .insensitive() .required() } }, tags: ['api'], description: 'Test GET', notes: 'test note' } }, { method: 'GET', path: '/another/test', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: { param1: Joi.string().required() } } } }, { method: 'GET', path: '/zanother/test', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: { param1: Joi.string().required() } } } }, { method: 'POST', path: '/test', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: { param2: Joi.string().valid('first', 'last'), param3: 'third', param4: 42 } } } }, { method: 'DELETE', path: '/test', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: { param2: Joi.string().valid('first', 'last') } } } }, { method: 'PUT', path: '/test', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: { param2: Joi.string().valid('first', 'last') } } } }, { method: 'PATCH', path: '/test', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: { param2: Joi.string().valid('first', 'last'), param3: Joi.number().valid(42) } } } }, { method: 'GET', path: '/notincluded', config: { tags: ['api'], handler: Helper.defaultHandler, plugins: { lout: false } } }, { method: 'GET', path: '/nested', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: { param1: Joi.object({ nestedparam1: Joi.string().required(), array: Joi.array() }) } } } }, { method: 'GET', path: '/rootobject', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: Joi.object({ param1: Joi.string().required() }) } } }, { method: 'GET', path: '/rootarray', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: Joi.array() .items(Joi.string().required(), Joi.object({ param1: Joi.number() }), Joi.number().forbidden()) .min(2) .max(5) .length(3) } } }, { method: 'GET', path: '/complexarray', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: Joi.array() .ordered('foo', 'bar') .items( Joi.string().required(), Joi.string() .valid('four') .forbidden(), Joi.object({ param1: Joi.number() }), Joi.number().forbidden() ) .min(2) .max(5) .length(3) .ordered('bar', 'bar') .items(Joi.number().required()) } } }, { method: 'GET', path: '/path/{pparam}/test', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { params: { pparam: Joi.string().required() } } } }, { method: 'GET', path: '/emptyobject', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: { param1: Joi.object() } } } }, { method: 'GET', path: '/alternatives', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: { param1: Joi.alternatives().try(Joi.number().required(), Joi.string().valid('first', 'last')) } } } }, { method: 'GET', path: '/withnestedalternatives', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: { param1: Joi.object({ param2: Joi.alternatives().try( { param3: Joi.object({ param4: Joi.number().example(5) }).description('this is cool too') }, Joi.number().min(42) ) }).description('something really cool'), param2: Joi.array() .items( Joi.object({ param2: Joi.alternatives().try( { param3: Joi.object({ param4: Joi.number().example(5) }).description('this is cool too') }, Joi.array().items('foo', 'bar'), Joi.number() .min(42) .required(), Joi.number() .max(42) .required() ) }).description('all the way down') ) .description('something really cool') } } } }, { method: 'GET', path: '/novalidation', config: { tags: ['api'], handler: Helper.defaultHandler } }, { method: 'GET', path: '/withresponse', config: { tags: ['api'], handler: Helper.defaultHandler, response: { schema: { param1: Joi.string() } } } }, { method: 'GET', path: '/withstatus', config: { tags: ['api'], handler: Helper.defaultHandler, response: { schema: { param1: Joi.string() }, status: { 204: { param2: Joi.string() }, 404: { error: 'Failure' } } } } }, { method: 'GET', path: '/withpojoinarray', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: { param1: Joi.array().items({ param2: Joi.string() }) } } } }, { method: 'POST', path: '/withnestedrulesarray', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { payload: { param1: Joi.array().items( Joi.object({ param2: Joi.array() .items( Joi.object({ param3: Joi.string() }) ) .optional() }) ) } } } }, { method: 'GET', path: '/withhtmlnote', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: { param1: Joi.string().notes('<span class="htmltypenote">HTML type note</span>') } }, notes: '<span class="htmlroutenote">HTML route note</span>' } }, { method: 'GET', path: '/withnotesarray', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: { param1: Joi.string().notes([ '<span class="htmltypenote">HTML type note</span>', '<span class="htmltypenote">HTML type note</span>' ]) } } } }, { method: 'GET', path: '/withexample', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: { param1: Joi.string() .regex(/^\w{1,5}$/) .example('abcde') } } } }, { method: 'POST', path: '/denybody', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { payload: false } } }, { method: 'POST', path: '/rootemptyobject', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { payload: Joi.object() } } }, { method: 'GET', path: '/withnestedexamples', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: { param1: Joi.object({ param2: Joi.object({ param3: Joi.number().example(5) }).example({ param3: 5 }) }).example({ param2: { param3: 5 } }) } } } }, { method: 'GET', path: '/withmeta', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: { param1: Joi.string().meta({ index: true, unique: true }) } } } }, { method: 'GET', path: '/withunit', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: { param1: Joi.number().unit('ms') } } } }, { method: 'GET', path: '/withdefaultvalue', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: { param1: Joi.number().default(42) } } } }, { method: 'GET', path: '/withbinaryencoding', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: { param1: Joi.binary() .min(42) .max(128) .length(64) .encoding('base64') } } } }, { method: 'GET', path: '/withdate', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: { param1: Joi.date() .min('1-1-1974') .max('12-31-2020') } } } }, { method: 'GET', path: '/withpeersconditions', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: { param1: Joi.object() .and('a', 'b', 'c') .or('a', 'b', 'c') .xor('a', 'b', 'c') .with('a', ['b', 'c']) .without('a', ['b', 'c']) } } } }, { method: 'GET', path: '/withpattern', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: { param1: Joi.object({ a: Joi.string() }).pattern(/\w\d/, Joi.boolean()) } } } }, { method: 'GET', path: '/withallowunknown', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: { param1: Joi.object().unknown(), param2: Joi.object().unknown(false) } } } }, { method: 'GET', path: '/withstringspecifics', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: { param1: Joi.string() .alphanum() .regex(/\d{3}.*/) .token() .email() .guid() .isoDate() .hostname() .lowercase() .uppercase() .trim(), param2: Joi.string().email() } } } }, { method: 'GET', path: '/withconditionalalternatives', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: { param1: Joi.alternatives() .when('b', { is: 5, then: Joi.string(), otherwise: Joi.number() .required() .description('Things and stuff') }) .when('a', { is: true, then: Joi.date(), otherwise: Joi.any() }), param2: Joi.alternatives() .when('b', { is: 5, then: Joi.string() }) .when('a', { is: true, otherwise: Joi.any() }) } } } }, { method: 'GET', path: '/withreferences', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: { param1: Joi.ref('a.b'), param2: Joi.ref('$x') } } } }, { method: 'GET', path: '/withassert', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: { param1: Joi.object().assert('d.e', Joi.ref('a.c'), 'equal to a.c'), param2: Joi.object().assert('$x', Joi.ref('b.e'), 'equal to b.e') } } } }, { method: 'GET', path: '/withproperties', vhost: 'john.doe', config: { tags: ['api'], handler: Helper.defaultHandler, cors: { maxAge: 12345 }, jsonp: 'callback' } }, { method: 'OPTIONS', path: '/optionstest', handler: Helper.defaultHandler }, { method: 'GET', path: '/withrulereference', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: { param1: Joi.date().min(Joi.ref('param2')), param2: Joi.date() } } } }, { method: 'GET', path: '/withcorstrue', vhost: 'john.doe', config: { tags: ['api'], handler: Helper.defaultHandler, cors: true } }, { method: 'GET', path: '/withstrip', config: { tags: ['api'], handler: Helper.defaultHandler, validate: { query: { param1: Joi.any().strip(), param2: Joi.any() } } } }, { method: 'GET', path: '/internal', config: { tags: ['api'], isInternal: true, handler: Helper.defaultHandler } } ]; lab.test('all routes parsed', async () => { const server = await Helper.createServer({}, routes); const response = await server.inject({ method: 'GET', url: '/swagger.json' }); expect(response.statusCode).to.equal(200); // the 40 to 45 difference is in one route having a number of methods expect(response.result.paths).to.have.length(40); }); });
/* Copyright 2018 Google LLC Use of this source code is governed by an MIT-style license that can be found in the LICENSE file or at https://opensource.org/licenses/MIT. */ const joi = require('@hapi/joi'); const defaults = require('./defaults'); const regExpObject = require('./reg-exp-object'); // Define some common constraints used by all methods. module.exports = joi.object().keys({ dontCacheBustURLsMatching: regExpObject, globFollow: joi.boolean().default(defaults.globFollow), globIgnores: joi.array().items(joi.string()).default(defaults.globIgnores), globPatterns: joi.array().items(joi.string()).default(defaults.globPatterns), globStrict: joi.boolean().default(defaults.globStrict), manifestTransforms: joi.array().items(joi.func().arity(1)), maximumFileSizeToCacheInBytes: joi.number().min(1) .default(defaults.maximumFileSizeToCacheInBytes), modifyURLPrefix: joi.object(), // templatedURLs is an object where any property name is valid, and the values // can be either a string or an array of strings. templatedURLs: joi.object().pattern(/./, [joi.string(), joi.array().items(joi.string())]), });
// Declare Internals const internals = {}; internals.defaults = { accessTokenName: 'access_token', allowQueryToken: false, allowCookieToken: false, allowMultipleHeaders: false, allowChaining: false, tokenType: 'Bearer', unauthorized: Boom.unauthorized }; internals.schema = Joi.object().keys({ validate: Joi.func().required(), accessTokenName: Joi.string().required(), allowQueryToken: Joi.boolean(), allowCookieToken: Joi.boolean(), allowMultipleHeaders: Joi.boolean(), allowChaining: Joi.boolean(), tokenType: Joi.string().required(), unauthorized: Joi.func() }); internals.implementation = (server, options) => { Hoek.assert(options, 'Missing bearer auth strategy options'); const settings = Hoek.applyToDefaults(internals.defaults, options);
lab.experiment('definitions', () => { const routes = [ { method: 'POST', path: '/test/', options: { handler: Helper.defaultHandler, tags: ['api'], validate: { payload: { a: Joi.number() .required() .description('the first number'), b: Joi.number() .required() .description('the second number'), operator: Joi.string() .required() .default('+') .description('the opertator i.e. + - / or *'), equals: Joi.number() .required() .description('the result of the sum') } } } }, { method: 'POST', path: '/test/2', options: { handler: Helper.defaultHandler, tags: ['api'], validate: { payload: Joi.object({ a: Joi.string(), b: Joi.object({ c: Joi.string() }) }).label('Model') } } }, { method: 'POST', path: '/test/3', options: { handler: Helper.defaultHandler, tags: ['api'], validate: { payload: Joi.object({ a: Joi.string(), b: Joi.object({ c: Joi.string() }) }).label('Model 1') } } } ]; lab.test('payload with inline definition', async () => { const server = await Helper.createServer({}, routes); const defination = { properties: { a: { description: 'the first number', type: 'number' }, b: { description: 'the second number', type: 'number' }, operator: { description: 'the opertator i.e. + - / or *', default: '+', type: 'string' }, equals: { description: 'the result of the sum', type: 'number' } }, required: ['a', 'b', 'operator', 'equals'], type: 'object' }; const response = await server.inject({ method: 'GET', url: '/swagger.json' }); expect(response.statusCode).to.equal(200); expect(response.result.paths['/test/'].post.parameters[0].schema).to.equal({ $ref: '#/definitions/Model 1' }); expect(response.result.definitions['Model 1']).to.equal(defination); const isValid = await Validate.test(response.result); expect(isValid).to.be.true(); }); lab.test('override definition named Model', async () => { const server = await Helper.createServer({}, routes); const response = await server.inject({ method: 'GET', url: '/swagger.json' }); //console.log(JSON.stringify(response.result.definitions)); expect(response.result.definitions.b).to.exists(); expect(response.result.definitions.Model).to.exists(); expect(response.result.definitions['Model 1']).to.exists(); const isValid = await Validate.test(response.result); expect(isValid).to.be.true(); }); lab.test('reuseDefinitions = false', async () => { // forces two models even though the model hash is the same const tempRoutes = [ { method: 'POST', path: '/store1/', options: { handler: Helper.defaultHandler, tags: ['api'], validate: { payload: Joi.object({ a: Joi.number(), b: Joi.number(), operator: Joi.string(), equals: Joi.number() }).label('A') } } }, { method: 'POST', path: '/store2/', options: { handler: Helper.defaultHandler, tags: ['api'], validate: { payload: Joi.object({ a: Joi.number(), b: Joi.number(), operator: Joi.string(), equals: Joi.number() }).label('B') } } } ]; const server = await Helper.createServer({ reuseDefinitions: false }, tempRoutes); const response = await server.inject({ method: 'GET', url: '/swagger.json' }); //console.log(JSON.stringify(response.result)); expect(response.statusCode).to.equal(200); expect(response.result.definitions.A).to.exist(); expect(response.result.definitions.B).to.exist(); const isValid = await Validate.test(response.result); expect(isValid).to.be.true(); }); lab.test('definitionPrefix = useLabel', async () => { // use the label as a prefix for dynamic model names const tempRoutes = [ { method: 'POST', path: '/store1/', options: { handler: Helper.defaultHandler, tags: ['api'], validate: { payload: Joi.object({ a: Joi.number(), b: Joi.number(), operator: Joi.string(), equals: Joi.number() }).label('A') } } }, { method: 'POST', path: '/store2/', options: { handler: Helper.defaultHandler, tags: ['api'], validate: { payload: Joi.object({ c: Joi.number(), v: Joi.number(), operator: Joi.string(), equals: Joi.number() }).label('A A') } } }, { method: 'POST', path: '/store3/', options: { handler: Helper.defaultHandler, tags: ['api'], validate: { payload: Joi.object({ c: Joi.number(), f: Joi.number(), operator: Joi.string(), equals: Joi.number() }).label('A') } } } ]; const server = await Helper.createServer({ definitionPrefix: 'useLabel' }, tempRoutes); const response = await server.inject({ method: 'GET', url: '/swagger.json' }); expect(response.statusCode).to.equal(200); expect(response.result.definitions.A).to.exist(); expect(response.result.definitions['A A']).to.exist(); expect(response.result.definitions['A 1']).to.exist(); const isValid = await Validate.test(response.result); expect(isValid).to.be.true(); }); lab.test('test that optional array is not in swagger output', async () => { let testRoutes = [ { method: 'POST', path: '/server/1/', options: { handler: Helper.defaultHandler, tags: ['api'], validate: { payload: Joi.object({ a: Joi.number().required(), b: Joi.string().optional() }).label('test') } } } ]; const server = await Helper.createServer({}, testRoutes); const response = await server.inject({ method: 'GET', url: '/swagger.json' }); expect(response.statusCode).to.equal(200); expect(response.result.definitions.test).to.equal({ type: 'object', properties: { a: { type: 'number' }, b: { type: 'string' } }, required: ['a'] }); }); lab.test('test that name changing for required', async () => { const FormDependencyDefinition = Joi.object({ id: Joi.number().required() }).label('FormDependencyDefinition'); const ActionDefinition = Joi.object({ id: Joi.number() .required() .allow(null), reminder: FormDependencyDefinition.required() }).label('ActionDefinition'); let testRoutes = [ { method: 'POST', path: '/server/', options: { handler: Helper.defaultHandler, tags: ['api'], validate: { payload: ActionDefinition } } } ]; const server = await Helper.createServer({}, testRoutes); const response = await server.inject({ method: 'GET', url: '/swagger.json' }); expect(response.statusCode).to.equal(200); expect(response.result.definitions.ActionDefinition).to.equal({ type: 'object', properties: { id: { type: 'number' }, reminder: { $ref: '#/definitions/FormDependencyDefinition' } }, required: ['id', 'reminder'] }); }); });
'use strict'; const Hoek = require('@hapi/hoek'); const Joi = require('@hapi/joi'); const Request = require('./request'); const Response = require('./response'); const Symbols = require('./symbols'); const internals = {}; internals.options = Joi.object().keys({ url: Joi.alternatives([ Joi.string(), Joi.object().keys({ protocol: Joi.string(), hostname: Joi.string(), port: Joi.any(), pathname: Joi.string().required(), query: Joi.any() }) ]) .required(), headers: Joi.object(), payload: Joi.any(), simulate: { end: Joi.boolean(), split: Joi.boolean(), error: Joi.boolean(),