Skip to content

Commit

Permalink
feat: add request.routeOptions object (#4397)
Browse files Browse the repository at this point in the history
  • Loading branch information
debadutta98 committed Nov 5, 2022
1 parent de53fba commit f1bd80e
Show file tree
Hide file tree
Showing 8 changed files with 234 additions and 2 deletions.
21 changes: 20 additions & 1 deletion docs/Reference/Request.md
Expand Up @@ -42,6 +42,17 @@ Request is a core Fastify object containing the following fields:
handling the request
- `routeConfig` - The route [`config`](./Routes.md#routes-config)
object.
- `routeOptions` - The route [`option`](./Routes.md#routes-options) object
- `bodyLimit` - either server limit or route limit
- `method` - the http method for the route
- `url` - the path of the URL to match this route
- `attachValidation` - attach `validationError` to request
(if there is a schema defined)
- `logLevel` - log level defined for this route
- `version` - a semver compatible string that defines the version of the endpoint
- `exposeHeadRoute` - creates a sibling HEAD route for any GET routes
- `prefixTrailingSlash` - string used to determine how to handle passing /
as a route with a prefix.
- [.getValidationFunction(schema | httpPart)](#getvalidationfunction) -
Returns a validation function for the specified schema or http part,
if any of either are set or cached.
Expand Down Expand Up @@ -90,7 +101,15 @@ fastify.post('/:params', options, function (request, reply) {
console.log(request.protocol)
console.log(request.url)
console.log(request.routerMethod)
console.log(request.routerPath)
console.log(request.routeOptions.bodyLimit)
console.log(request.routeOptions.method)
console.log(request.routeOptions.url)
console.log(request.routeOptions.attachValidation)
console.log(request.routeOptions.logLevel)
console.log(request.routeOptions.version)
console.log(request.routeOptions.exposeHeadRoute)
console.log(request.routeOptions.prefixTrailingSlash)
console.log(request.routerPath.logLevel)
request.log.info('some info')
})
```
Expand Down
4 changes: 4 additions & 0 deletions lib/context.js
Expand Up @@ -31,6 +31,8 @@ function Context ({
serializerCompiler,
replySerializer,
schemaErrorFormatter,
exposeHeadRoute,
prefixTrailingSlash,
server,
isFastify
}) {
Expand All @@ -52,6 +54,8 @@ function Context ({
this._parserOptions = {
limit: bodyLimit || server[kBodyLimit]
}
this.exposeHeadRoute = exposeHeadRoute
this.prefixTrailingSlash = prefixTrailingSlash
this.logLevel = logLevel || server[kLogLevel]
this.logSerializers = logSerializers
this[kFourOhFourContext] = null
Expand Down
19 changes: 19 additions & 0 deletions lib/request.js
Expand Up @@ -165,6 +165,25 @@ Object.defineProperties(Request.prototype, {
return this[kRouteContext].config.url
}
},
routeOptions: {
get () {
const context = this[kRouteContext]
const routeLimit = context._parserOptions.limit
const serverLimit = context.server.initialConfig.bodyLimit
const version = context.server.hasConstraintStrategy('version') ? this.raw.headers['accept-version'] : undefined
const options = {
method: context.config.method,
url: context.config.url,
bodyLimit: (routeLimit || serverLimit),
attachValidation: context.attachValidation,
logLevel: context.logLevel,
exposeHeadRoute: context.exposeHeadRoute,
prefixTrailingSlash: context.prefixTrailingSlash,
version
}
return Object.freeze(options)
}
},
routerMethod: {
get () {
return this[kRouteContext].config.method
Expand Down
2 changes: 2 additions & 0 deletions lib/route.js
Expand Up @@ -278,6 +278,8 @@ function buildRouting (options) {
replySerializer: this[kReplySerializerDefault],
validatorCompiler: opts.validatorCompiler,
serializerCompiler: opts.serializerCompiler,
exposeHeadRoute: shouldExposeHead,
prefixTrailingSlash: (opts.prefixTrailingSlash || 'both'),
server: this,
isFastify
})
Expand Down
79 changes: 79 additions & 0 deletions test/bodyLimit.test.js
Expand Up @@ -44,3 +44,82 @@ test('bodyLimit', t => {
})
})
})

test('default request.routeOptions.bodyLimit should be 1048576', t => {
t.plan(4)
const fastify = Fastify()
fastify.post('/default-bodylimit', {
handler (request, reply) {
t.equal(1048576, request.routeOptions.bodyLimit)
reply.send({ })
}
})
fastify.listen({ port: 0 }, function (err) {
t.error(err)
t.teardown(() => { fastify.close() })

sget({
method: 'POST',
url: 'http://localhost:' + fastify.server.address().port + '/default-bodylimit',
headers: { 'Content-Type': 'application/json' },
body: [],
json: true
}, (err, response, body) => {
t.error(err)
t.equal(response.statusCode, 200)
})
})
})

test('request.routeOptions.bodyLimit should be equal to route limit', t => {
t.plan(4)
const fastify = Fastify({ bodyLimit: 1 })
fastify.post('/route-limit', {
bodyLimit: 1000,
handler (request, reply) {
t.equal(1000, request.routeOptions.bodyLimit)
reply.send({})
}
})
fastify.listen({ port: 0 }, function (err) {
t.error(err)
t.teardown(() => { fastify.close() })

sget({
method: 'POST',
url: 'http://localhost:' + fastify.server.address().port + '/route-limit',
headers: { 'Content-Type': 'application/json' },
body: [],
json: true
}, (err, response, body) => {
t.error(err)
t.equal(response.statusCode, 200)
})
})
})

test('request.routeOptions.bodyLimit should be equal to server limit', t => {
t.plan(4)
const fastify = Fastify({ bodyLimit: 100 })
fastify.post('/server-limit', {
handler (request, reply) {
t.equal(100, request.routeOptions.bodyLimit)
reply.send({})
}
})
fastify.listen({ port: 0 }, function (err) {
t.error(err)
t.teardown(() => { fastify.close() })

sget({
method: 'POST',
url: 'http://localhost:' + fastify.server.address().port + '/server-limit',
headers: { 'Content-Type': 'application/json' },
body: [],
json: true
}, (err, response, body) => {
t.error(err)
t.equal(response.statusCode, 200)
})
})
})
96 changes: 96 additions & 0 deletions test/request-error.test.js
@@ -1,6 +1,7 @@
'use strict'

const { connect } = require('net')
const sget = require('simple-get').concat
const t = require('tap')
const test = t.test
const Fastify = require('..')
Expand Down Expand Up @@ -313,3 +314,98 @@ test('default clientError replies with bad request on reused keep-alive connecti
client.write('\r\n\r\n')
})
})

test('request.routeOptions should be immutable', t => {
t.plan(14)
const fastify = Fastify()
const handler = function (req, res) {
t.equal('POST', req.routeOptions.method)
t.equal('/', req.routeOptions.url)
t.throws(() => { req.routeOptions = null }, new TypeError('Cannot set property routeOptions of #<Request> which has only a getter'))
t.throws(() => { req.routeOptions.method = 'INVALID' }, new TypeError('Cannot assign to read only property \'method\' of object \'#<Object>\''))
t.throws(() => { req.routeOptions.url = '//' }, new TypeError('Cannot assign to read only property \'url\' of object \'#<Object>\''))
t.throws(() => { req.routeOptions.bodyLimit = 0xDEADBEEF }, new TypeError('Cannot assign to read only property \'bodyLimit\' of object \'#<Object>\''))
t.throws(() => { req.routeOptions.attachValidation = true }, new TypeError('Cannot assign to read only property \'attachValidation\' of object \'#<Object>\''))
t.throws(() => { req.routeOptions.logLevel = 'invalid' }, new TypeError('Cannot assign to read only property \'logLevel\' of object \'#<Object>\''))
t.throws(() => { req.routeOptions.version = '95.0.1' }, new TypeError('Cannot assign to read only property \'version\' of object \'#<Object>\''))
t.throws(() => { req.routeOptions.prefixTrailingSlash = true }, new TypeError('Cannot assign to read only property \'prefixTrailingSlash\' of object \'#<Object>\''))
t.throws(() => { req.routeOptions.newAttribute = {} }, new TypeError('Cannot add property newAttribute, object is not extensible'))

for (const key of Object.keys(req.routeOptions)) {
if (typeof req.routeOptions[key] === 'object' && req.routeOptions[key] !== null) {
t.fail('Object.freeze must run recursively on nested structures to ensure that routeOptions is immutable.')
}
}

res.send({})
}
fastify.post('/', {
bodyLimit: 1000,
handler
})
fastify.listen({ port: 0 }, function (err) {
t.error(err)
t.teardown(() => { fastify.close() })

sget({
method: 'POST',
url: 'http://localhost:' + fastify.server.address().port,
headers: { 'Content-Type': 'application/json' },
body: [],
json: true
}, (err, response, body) => {
t.error(err)
t.equal(response.statusCode, 200)
})
})
})

test('test request.routeOptions.version', t => {
t.plan(7)
const fastify = Fastify()

fastify.route({
method: 'POST',
url: '/version',
constraints: { version: '1.2.0' },
handler: function (request, reply) {
t.equal('1.2.0', request.routeOptions.version)
reply.send({})
}
})

fastify.route({
method: 'POST',
url: '/version-undefined',
handler: function (request, reply) {
t.equal(undefined, request.routeOptions.version)
reply.send({})
}
})
fastify.listen({ port: 0 }, function (err) {
t.error(err)
t.teardown(() => { fastify.close() })

sget({
method: 'POST',
url: 'http://localhost:' + fastify.server.address().port + '/version',
headers: { 'Content-Type': 'application/json', 'Accept-Version': '1.2.0' },
body: [],
json: true
}, (err, response, body) => {
t.error(err)
t.equal(response.statusCode, 200)
})

sget({
method: 'POST',
url: 'http://localhost:' + fastify.server.address().port + '/version-undefined',
headers: { 'Content-Type': 'application/json' },
body: [],
json: true
}, (err, response, body) => {
t.error(err)
t.equal(response.statusCode, 200)
})
})
})
3 changes: 2 additions & 1 deletion test/types/request.test-d.ts
Expand Up @@ -17,7 +17,7 @@ import fastify, {
} from '../../fastify'
import { RequestParamsDefault, RequestHeadersDefault, RequestQuerystringDefault } from '../../types/utils'
import { FastifyLoggerInstance } from '../../types/logger'
import { FastifyRequest } from '../../types/request'
import { FastifyRequest, RequestRouteOptions } from '../../types/request'
import { FastifyReply } from '../../types/reply'
import { FastifyInstance } from '../../types/instance'
import { RouteGenericInterface } from '../../types/route'
Expand Down Expand Up @@ -66,6 +66,7 @@ const getHandler: RouteHandler = function (request, _reply) {
expectType<string>(request.method)
expectType<string>(request.routerPath)
expectType<string>(request.routerMethod)
expectType<Readonly<RequestRouteOptions>>(request.routeOptions)
expectType<boolean>(request.is404)
expectType<string>(request.hostname)
expectType<string>(request.ip)
Expand Down
12 changes: 12 additions & 0 deletions types/request.d.ts
Expand Up @@ -20,6 +20,17 @@ export interface ValidationFunction {
errors?: null | ErrorObject[];
}

export interface RequestRouteOptions {
method: string,
url: string,
bodyLimit:number,
attachValidation:boolean,
logLevel:string,
version: string | undefined,
exposeHeadRoute: boolean,
prefixTrailingSlash: string
}

/**
* FastifyRequest is an instance of the standard http or http2 request objects.
* It defaults to http.IncomingMessage, and it also extends the relative request object.
Expand Down Expand Up @@ -66,6 +77,7 @@ export interface FastifyRequest<RouteGeneric extends RouteGenericInterface = Rou
readonly method: string;
readonly routerPath: string;
readonly routerMethod: string;
readonly routeOptions: Readonly<RequestRouteOptions>
readonly is404: boolean;
readonly socket: RawRequest['socket'];

Expand Down

0 comments on commit f1bd80e

Please sign in to comment.