UNPKG

@athenna/http

Version:

The Athenna Http server. Built on top of fastify.

499 lines (498 loc) 14.7 kB
/** * @athenna/http * * (c) João Lenon <lenon@athenna.io> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ import { Is, Options, Macroable, Route as RouteHelper } from '@athenna/common'; import { UndefinedMethodException } from '#src/exceptions/UndefinedMethodException'; import { NotFoundValidatorException } from '#src/exceptions/NotFoundValidatorException'; import { NotFoundMiddlewareException } from '#src/exceptions/NotFoundMiddlewareException'; export class Route extends Macroable { constructor(url, methods, handler) { super(); this.route = { url, methods, deleted: false, prefixes: [], middlewares: { middlewares: [], terminators: [], interceptors: [] }, fastify: { schema: {}, config: {} } }; if (Is.String(handler)) { const [controller, method] = handler.split('.'); const dependency = ioc.safeUse(`App/Http/Controllers/${controller}`); if (!dependency[method]) { throw new UndefinedMethodException(method, controller); } this.route.handler = (...args) => { const service = ioc.safeUse(`App/Http/Controllers/${controller}`); if (!service[method]) { throw new UndefinedMethodException(method, controller); } return service[method].bind(dependency)(...args); }; } else { this.route.handler = handler; } RouteHelper.getParamsName(url).forEach(param => this.param(param)); } /** * Set the route name. * * @example * ```ts * Route.name('profile') * ``` */ name(name) { this.route.name = name; // eslint-disable-next-line // @ts-ignore this.route.fastify.config.name = name; return this; } /** * Set fastify vanilla route options. * * @example * ```ts * Route.vanillaOptions({ schema: {} }) * ``` */ vanillaOptions(options) { this.route.fastify = { ...this.route.fastify, ...options }; return this; } /** * Set a prefix for the route. * * @example * ```ts * Route.prefix('/api/v1') * ``` */ prefix(prefix) { this.route.prefixes.push(prefix); return this; } /** * Set a named validator, validator closure or a MiddlewareContract implementation * in the route. */ validator(validator, prepend = false) { const insertionType = prepend ? 'unshift' : 'push'; if (Is.String(validator)) { const namedAlias = `App/Validators/Names/${validator}`; const alias = `App/Validators/${validator}`; if (!ioc.has(namedAlias) && !ioc.has(alias)) { throw new NotFoundValidatorException(alias, namedAlias); } this.route.middlewares.middlewares[insertionType]((...args) => { const mid = ioc.use(namedAlias) || ioc.safeUse(alias); return mid.handle.bind(mid)(...args); }); return this; } if (Is.Function(validator)) { this.route.middlewares.middlewares[insertionType](validator); return this; } this.route.middlewares.middlewares[insertionType](validator.handle.bind(validator)); return this; } /** * Set a named middleware, middleware closure or a MiddlewareContract implementation * in the route. */ middleware(middleware, prepend = false) { const insertionType = prepend ? 'unshift' : 'push'; if (Is.String(middleware)) { const namedAlias = `App/Http/Middlewares/Names/${middleware}`; const alias = `App/Http/Middlewares/${middleware}`; if (!ioc.has(namedAlias) && !ioc.has(alias)) { throw new NotFoundMiddlewareException(alias, namedAlias); } this.route.middlewares.middlewares[insertionType]((...args) => { const mid = ioc.use(namedAlias) || ioc.safeUse(alias); return mid.handle.bind(mid)(...args); }); return this; } if (Is.Function(middleware)) { this.route.middlewares.middlewares[insertionType](middleware); return this; } this.route.middlewares.middlewares[insertionType](middleware.handle.bind(middleware)); return this; } /** * Set a named interceptor, interceptor closure or a InterceptorContract implementation * in the route. */ interceptor(interceptor, prepend = false) { const insertionType = prepend ? 'unshift' : 'push'; if (Is.String(interceptor)) { const namedAlias = `App/Http/Interceptors/Names/${interceptor}`; const alias = `App/Http/Interceptors/${interceptor}`; if (!ioc.has(namedAlias) && !ioc.has(alias)) { throw new NotFoundMiddlewareException(alias, namedAlias); } this.route.middlewares.interceptors[insertionType]((...args) => { const mid = ioc.use(namedAlias) || ioc.safeUse(alias); return mid.intercept.bind(mid)(...args); }); return this; } if (Is.Function(interceptor)) { this.route.middlewares.interceptors[insertionType](interceptor); return this; } this.route.middlewares.interceptors[insertionType](interceptor.intercept.bind(interceptor)); return this; } /** * Set a named terminator, terminator closure or a TerminatorContract implementation * in the route. */ terminator(terminator, prepend = false) { const insertionType = prepend ? 'unshift' : 'push'; if (Is.String(terminator)) { const namedAlias = `App/Http/Terminators/Names/${terminator}`; const alias = `App/Http/Terminators/${terminator}`; if (!ioc.has(namedAlias) && !ioc.has(alias)) { throw new NotFoundMiddlewareException(alias, namedAlias); } this.route.middlewares.terminators[insertionType]((...args) => { const mid = ioc.use(namedAlias) || ioc.safeUse(alias); return mid.terminate.bind(mid)(...args); }); return this; } if (Is.Function(terminator)) { this.route.middlewares.terminators[insertionType](terminator); return this; } this.route.middlewares.terminators[insertionType](terminator.terminate.bind(terminator)); return this; } /** * Set up all helmet options for route. * * @example * ```ts * Route.helmet({ * dnsPrefetchControl: { allow: true } * }) * ``` */ helmet(options) { this.route.fastify.helmet = options; return this; } /** * Set up all schema options for route. * * @example * ```ts * Route.schema({ * body: { * type: 'array', * items: { * oneOf: [{ type: 'string', description: 'User name' }] * } * } * }) * ``` */ schema(options) { this.route.fastify.schema = options; return this; } /** * Set up all rate limit options for route. * * @example * ```ts * Route.rateLimit({ * max: 3, * timeWindow: '1 minute' * }) * ``` */ rateLimit(options) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore this.route.fastify.config.rateLimit = options; return this; } /** * Hide the route in swagger docs. * * @example * ```ts * Route.hide() * ``` */ hide() { this.route.fastify.schema.hide = true; return this; } /** * Set the route as deprecated in swagger docs. * * @example * ```ts * Route.deprecated() * ``` */ deprecated() { this.route.fastify.schema.deprecated = true; return this; } /** * Set a summary for the route swagger docs. * * @example * ```ts * Route.summary('List all users') * ``` */ summary(summary) { this.route.fastify.schema.summary = summary; return this; } /** * Set a description for the route swagger docs. * * @example * ```ts * Route.description('List all users') * ``` */ description(description) { this.route.fastify.schema.description = description; return this; } /** * Set tags for the route swagger docs. * * @example * ```ts * Route.tags(['v1']) * ``` */ tags(tags) { if (!this.route.fastify.schema.tags) { this.route.fastify.schema.tags = []; } tags.forEach(tag => this.route.fastify.schema.tags.push(tag)); return this; } /** * Set body param for the route swagger docs. * * @example * ```ts * Route.body('name', { * type: 'string', * description: 'User name' * }) * ``` */ body(name, body = {}) { if (!this.route.fastify.schema.body) { this.route.fastify.schema.body = { type: 'object', properties: {} }; } // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore this.route.fastify.schema.body.properties[name] = body; return this; } /** * Set param for the route swagger docs. * * @example * ```ts * Route.param('id', { description: 'Set the user id' }) * ``` */ param(name, param = {}) { if (!this.route.fastify.schema.params) { this.route.fastify.schema.params = { type: 'object', properties: {} }; } // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore this.route.fastify.schema.params.properties[name] = param; return this; } /** * Set query string for the route swagger docs. * * @example * ```ts * Route.queryString('page', { description: 'Set the pagination page' }) * ``` */ queryString(name, queryString = {}) { if (!this.route.fastify.schema.querystring) { this.route.fastify.schema.querystring = { type: 'object', properties: {} }; } // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore this.route.fastify.schema.querystring.properties[name] = queryString; return this; } /** * Set response for the route swagger docs. * * @example * For objects * ```ts * Route.response(200, { * description: 'Return one user', * properties: { * name: { type: 'string', description: 'User name' } * } * }) * ``` * * For arrays * ```ts * Route.response(200, { * description: 'Return users paginated', * schema: { * type: 'array' * oneOf: [ * { type: 'string', description: 'User name' } * ] * } * }) * ``` */ response(statusCode, response) { response = Options.create(response, { description: `Status code ${statusCode} response` }); if (!this.route.fastify.schema.response) { this.route.fastify.schema.response = {}; } this.route.fastify.schema.response[statusCode] = response; return this; } /** * Set the route security in swagger docs. * * @example * ```ts * Route.security('apiToken', []) * ``` */ security(key, values) { if (!this.route.fastify.schema.security) { this.route.fastify.schema.security = []; } this.route.fastify.schema.security.push({ [key]: values }); return this; } /** * Set the external documentation url in swagger docs. * * @example * ```ts * Route.externalDocs('https://github.com/athennaio/docs', 'Athenna Framework Documentation') * ``` */ externalDocs(url, description) { if (!this.route.fastify.schema.externalDocs) { this.route.fastify.schema.externalDocs = { url: '', description: '' }; } this.route.fastify.schema.externalDocs.url = url; this.route.fastify.schema.externalDocs.description = description; return this; } /** * Set the type of the content that the API consumes in swagger docs. * * @example * ```ts * Route.consumes(['json', 'yaml', ...]) * ``` */ consumes(consumes) { this.route.fastify.schema.consumes = consumes; return this; } /** * Set the type of the content that the API produces in swagger docs. * * @example * ```ts * Route.produces(['json', 'yaml', ...]) * ``` */ produces(produces) { this.route.fastify.schema.produces = produces; return this; } /** * Get the route as JSON. * * @example * ```ts * Route.toJSON() * ``` */ toJSON() { return { url: this.getUrl(), methods: this.route.methods, name: this.route.name, deleted: this.route.deleted, prefixes: this.route.prefixes, handler: this.route.handler, middlewares: this.route.middlewares, fastify: this.route.fastify }; } getUrl() { const url = this.removeSlashes(this.route.url); const prefix = this.route.prefixes .reverse() .map(p => this.removeSlashes(p)) .join(''); return prefix ? `${prefix}${url === '/' ? '' : url}` : url; } removeSlashes(url) { if (url === '/') { return url; } const matcher = url => `/${url.replace(/^\//, '').replace(/\/$/, '')}`; if (Is.Array(url)) { return url.map(u => matcher(u)); } return matcher(url); } }