fastify 4.5.2 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,14 +23,6 @@ class Serializer {
23
23
  }
24
24
  }
25
25
 
26
- asAny (i) {
27
- return JSON.stringify(i)
28
- }
29
-
30
- asNull () {
31
- return 'null'
32
- }
33
-
34
26
  asInteger (i) {
35
27
  if (typeof i === 'bigint') {
36
28
  return i.toString()
@@ -47,10 +39,6 @@ class Serializer {
47
39
  }
48
40
  }
49
41
 
50
- asIntegerNullable (i) {
51
- return i === null ? 'null' : this.asInteger(i)
52
- }
53
-
54
42
  asNumber (i) {
55
43
  const num = Number(i)
56
44
  if (Number.isNaN(num)) {
@@ -62,54 +50,43 @@ class Serializer {
62
50
  }
63
51
  }
64
52
 
65
- asNumberNullable (i) {
66
- return i === null ? 'null' : this.asNumber(i)
67
- }
68
-
69
53
  asBoolean (bool) {
70
54
  return bool && 'true' || 'false' // eslint-disable-line
71
55
  }
72
56
 
73
- asBooleanNullable (bool) {
74
- return bool === null ? 'null' : this.asBoolean(bool)
75
- }
76
-
77
57
  asDateTime (date) {
78
58
  if (date === null) return '""'
79
59
  if (date instanceof Date) {
80
60
  return '"' + date.toISOString() + '"'
81
61
  }
62
+ if (typeof date === 'string') {
63
+ return '"' + date + '"'
64
+ }
82
65
  throw new Error(`The value "${date}" cannot be converted to a date-time.`)
83
66
  }
84
67
 
85
- asDateTimeNullable (date) {
86
- return date === null ? 'null' : this.asDateTime(date)
87
- }
88
-
89
68
  asDate (date) {
90
69
  if (date === null) return '""'
91
70
  if (date instanceof Date) {
92
71
  return '"' + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(0, 10) + '"'
93
72
  }
73
+ if (typeof date === 'string') {
74
+ return '"' + date + '"'
75
+ }
94
76
  throw new Error(`The value "${date}" cannot be converted to a date.`)
95
77
  }
96
78
 
97
- asDateNullable (date) {
98
- return date === null ? 'null' : this.asDate(date)
99
- }
100
-
101
79
  asTime (date) {
102
80
  if (date === null) return '""'
103
81
  if (date instanceof Date) {
104
82
  return '"' + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(11, 19) + '"'
105
83
  }
84
+ if (typeof date === 'string') {
85
+ return '"' + date + '"'
86
+ }
106
87
  throw new Error(`The value "${date}" cannot be converted to a time.`)
107
88
  }
108
89
 
109
- asTimeNullable (date) {
110
- return date === null ? 'null' : this.asTime(date)
111
- }
112
-
113
90
  asString (str) {
114
91
  const quotes = '"'
115
92
  if (str instanceof Date) {
@@ -129,10 +106,6 @@ class Serializer {
129
106
  }
130
107
  }
131
108
 
132
- asStringNullable (str) {
133
- return str === null ? 'null' : this.asString(str)
134
- }
135
-
136
109
  // magically escape strings for json
137
110
  // relying on their charCodeAt
138
111
  // everything below 32 needs JSON.stringify()
@@ -200,7 +173,7 @@ class Serializer {
200
173
  }
201
174
 
202
175
  json += "\"statusCode\"" + ':'
203
- json += serializer.asNumber.bind(serializer)(obj["statusCode"])
176
+ json += serializer.asNumber(obj["statusCode"])
204
177
  }
205
178
 
206
179
  if (obj["code"] !== undefined) {
@@ -212,7 +185,7 @@ class Serializer {
212
185
  }
213
186
 
214
187
  json += "\"code\"" + ':'
215
- json += serializer.asString.bind(serializer)(obj["code"])
188
+ json += serializer.asString(obj["code"])
216
189
  }
217
190
 
218
191
  if (obj["error"] !== undefined) {
@@ -224,7 +197,7 @@ class Serializer {
224
197
  }
225
198
 
226
199
  json += "\"error\"" + ':'
227
- json += serializer.asString.bind(serializer)(obj["error"])
200
+ json += serializer.asString(obj["error"])
228
201
  }
229
202
 
230
203
  if (obj["message"] !== undefined) {
@@ -236,7 +209,7 @@ class Serializer {
236
209
  }
237
210
 
238
211
  json += "\"message\"" + ':'
239
- json += serializer.asString.bind(serializer)(obj["message"])
212
+ json += serializer.asString(obj["message"])
240
213
  }
241
214
 
242
215
  json += '}'
package/lib/route.js CHANGED
@@ -83,6 +83,7 @@ function buildRouting (options) {
83
83
  },
84
84
  routing: router.lookup.bind(router), // router func to find the right handler to call
85
85
  route, // configure a route in the fastify instance
86
+ hasRoute,
86
87
  prepareRoute,
87
88
  getDefaultRoute: function () {
88
89
  return router.defaultRoute
@@ -141,6 +142,14 @@ function buildRouting (options) {
141
142
  return route.call(this, { options, isFastify })
142
143
  }
143
144
 
145
+ function hasRoute ({ options }) {
146
+ return router.find(
147
+ options.method,
148
+ options.url || '',
149
+ options.constraints
150
+ ) !== null
151
+ }
152
+
144
153
  // Route management
145
154
  function route ({ options, isFastify }) {
146
155
  // Since we are mutating/assigning only top level props, it is fine to have a shallow copy using the spread operator
package/lib/schemas.js CHANGED
@@ -62,6 +62,7 @@ function normalizeSchema (routeSchemas, serverOptions) {
62
62
  // let's check if our schemas have a custom prototype
63
63
  for (const key of ['headers', 'querystring', 'params', 'body']) {
64
64
  if (typeof routeSchemas[key] === 'object' && Object.getPrototypeOf(routeSchemas[key]) !== Object.prototype) {
65
+ routeSchemas[kSchemaVisited] = true
65
66
  return routeSchemas
66
67
  }
67
68
  }
package/lib/server.js CHANGED
@@ -319,7 +319,7 @@ function normalizeListenArgs (args) {
319
319
  options.backlog = argsLength > 1 ? lastArg : undefined
320
320
  } else {
321
321
  /* Deal with listen ([port[, host[, backlog]]]) */
322
- options.port = argsLength >= 1 && Number.isInteger(firstArg) ? firstArg : 0
322
+ options.port = argsLength >= 1 && Number.isInteger(firstArg) ? firstArg : normalizePort(firstArg)
323
323
  // This will listen to what localhost is.
324
324
  // It can be 127.0.0.1 or ::1, depending on the operating system.
325
325
  // Fixes https://github.com/fastify/fastify/issues/1022.
@@ -330,6 +330,11 @@ function normalizeListenArgs (args) {
330
330
  return options
331
331
  }
332
332
 
333
+ function normalizePort (firstArg) {
334
+ const port = parseInt(firstArg, 10)
335
+ return port >= 0 && !Number.isNaN(port) ? port : 0
336
+ }
337
+
333
338
  function logServerAddress (server) {
334
339
  let address = server.address()
335
340
  const isUnixSocket = typeof address === 'string'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify",
3
- "version": "4.5.2",
3
+ "version": "4.6.0",
4
4
  "description": "Fast and low overhead web framework, for Node.js",
5
5
  "main": "fastify.js",
6
6
  "type": "commonjs",
@@ -164,7 +164,7 @@
164
164
  "split2": "^4.1.0",
165
165
  "standard": "^17.0.0-2",
166
166
  "tap": "^16.2.0",
167
- "tsd": "^0.22.0",
167
+ "tsd": "^0.23.0",
168
168
  "typescript": "^4.7.2",
169
169
  "undici": "^5.4.0",
170
170
  "vary": "^1.1.2",
@@ -177,7 +177,7 @@
177
177
  "abstract-logging": "^2.0.1",
178
178
  "avvio": "^8.1.3",
179
179
  "find-my-way": "^7.0.0",
180
- "light-my-request": "^5.0.0",
180
+ "light-my-request": "^5.5.1",
181
181
  "pino": "^8.0.0",
182
182
  "process-warning": "^2.0.0",
183
183
  "proxy-addr": "^2.0.7",
package/test/404s.test.js CHANGED
@@ -1945,7 +1945,7 @@ test('Send 404 when frameworkError calls reply.callNotFound', t => {
1945
1945
  t.end()
1946
1946
  })
1947
1947
 
1948
- test('hooks are applied to not found handlers', async ({ equal }) => {
1948
+ test('hooks are applied to not found handlers /1', async ({ equal }) => {
1949
1949
  const fastify = Fastify()
1950
1950
 
1951
1951
  // adding await here is fundamental for this test
@@ -423,6 +423,28 @@ test('promise was fulfilled with undefined', t => {
423
423
  })
424
424
  })
425
425
 
426
+ test('promise was fulfilled with undefined using inject', async (t) => {
427
+ const stream = split(JSON.parse)
428
+ const fastify = Fastify({
429
+ logger: {
430
+ stream,
431
+ level: 'error'
432
+ }
433
+ })
434
+
435
+ fastify.get('/', async (req, reply) => {
436
+ })
437
+
438
+ stream.once('data', line => {
439
+ t.fail('should not log an error')
440
+ })
441
+
442
+ const res = await fastify.inject('/')
443
+
444
+ t.equal(res.body, '')
445
+ t.equal(res.statusCode, 200)
446
+ })
447
+
426
448
  test('error is not logged because promise was fulfilled with undefined but response was sent before promise resolution', t => {
427
449
  t.plan(4)
428
450
 
@@ -642,7 +642,7 @@ test('should register empty values', t => {
642
642
 
643
643
  fastify.register((instance, opts, done) => {
644
644
  instance.decorate('test', null)
645
- t.ok(instance.hasOwnProperty('test'))
645
+ t.ok(Object.prototype.hasOwnProperty.call(instance, 'test'))
646
646
  done()
647
647
  })
648
648
 
@@ -0,0 +1,77 @@
1
+ 'use strict'
2
+
3
+ const t = require('tap')
4
+ const test = t.test
5
+ const Fastify = require('../fastify')
6
+
7
+ test('hasRoute', t => {
8
+ t.plan(4)
9
+ const test = t.test
10
+ const fastify = Fastify()
11
+
12
+ test('hasRoute - invalid options', t => {
13
+ t.plan(3)
14
+
15
+ t.equal(fastify.hasRoute({ }), false)
16
+
17
+ t.equal(fastify.hasRoute({ method: 'GET' }), false)
18
+
19
+ t.equal(fastify.hasRoute({ constraints: [] }), false)
20
+ })
21
+
22
+ test('hasRoute - primitive method', t => {
23
+ t.plan(2)
24
+ fastify.route({
25
+ method: 'GET',
26
+ url: '/',
27
+ handler: function (req, reply) {
28
+ reply.send({ hello: 'world' })
29
+ }
30
+ })
31
+
32
+ t.equal(fastify.hasRoute({
33
+ method: 'GET',
34
+ url: '/'
35
+ }), true)
36
+
37
+ t.equal(fastify.hasRoute({
38
+ method: 'POST',
39
+ url: '/'
40
+ }), false)
41
+ })
42
+
43
+ test('hasRoute - with constraints', t => {
44
+ t.plan(2)
45
+ fastify.route({
46
+ method: 'GET',
47
+ url: '/',
48
+ constraints: { version: '1.2.0' },
49
+ handler: (req, reply) => {
50
+ reply.send({ hello: 'world' })
51
+ }
52
+ })
53
+
54
+ t.equal(fastify.hasRoute({
55
+ method: 'GET',
56
+ url: '/',
57
+ constraints: { version: '1.2.0' }
58
+ }), true)
59
+
60
+ t.equal(fastify.hasRoute({
61
+ method: 'GET',
62
+ url: '/',
63
+ constraints: { version: '1.3.0' }
64
+ }), false)
65
+ })
66
+
67
+ test('hasRoute - parametric route regexp with constraints', t => {
68
+ t.plan(1)
69
+ // parametric with regexp
70
+ fastify.get('/example/:file(^\\d+).png', function (request, reply) { })
71
+
72
+ t.equal(fastify.hasRoute({
73
+ method: 'GET',
74
+ url: '/example/12345.png'
75
+ }), true)
76
+ })
77
+ })
@@ -138,7 +138,7 @@ test('decorate should recognize getter/setter objects', t => {
138
138
  this._a = val
139
139
  }
140
140
  })
141
- t.equal(one.hasOwnProperty('foo'), true)
141
+ t.equal(Object.prototype.hasOwnProperty.call(one, 'foo'), true)
142
142
  t.equal(one.foo, undefined)
143
143
  one.foo = 'a'
144
144
  t.equal(one.foo, 'a')
@@ -154,6 +154,6 @@ test('decorate should recognize getter/setter objects', t => {
154
154
  decorator.add.call(two, 'foo', {
155
155
  getter: () => 'a getter'
156
156
  })
157
- t.equal(two.hasOwnProperty('foo'), true)
157
+ t.equal(Object.prototype.hasOwnProperty.call(two, 'foo'), true)
158
158
  t.equal(two.foo, 'a getter')
159
159
  })
@@ -200,3 +200,36 @@ test('listen when firstArg is { path: string(pipe) } and with backlog and callba
200
200
  t.equal(address, '\\\\.\\pipe\\testPipe3')
201
201
  })
202
202
  })
203
+
204
+ test('listen accepts a port as string, and callback', t => {
205
+ t.plan(2)
206
+ const fastify = Fastify()
207
+ t.teardown(fastify.close.bind(fastify))
208
+ const port = 3000
209
+ fastify.listen(port.toString(), localhost, (err) => {
210
+ t.equal(fastify.server.address().port, port)
211
+ t.error(err)
212
+ })
213
+ })
214
+
215
+ test('listen accepts a port as string, address and callback', t => {
216
+ t.plan(3)
217
+ const fastify = Fastify()
218
+ t.teardown(fastify.close.bind(fastify))
219
+ const port = 3000
220
+ fastify.listen(port.toString(), localhost, (err) => {
221
+ t.equal(fastify.server.address().port, port)
222
+ t.equal(fastify.server.address().address, localhost)
223
+ t.error(err)
224
+ })
225
+ })
226
+
227
+ test('listen with invalid port string without callback with (address)', t => {
228
+ t.plan(1)
229
+ const fastify = Fastify()
230
+ t.teardown(fastify.close.bind(fastify))
231
+ fastify.listen('-1')
232
+ .then(address => {
233
+ t.equal(address, `http://${localhostForURL}:${fastify.server.address().port}`)
234
+ })
235
+ })
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { test } = require('tap')
4
4
  const Joi = require('joi')
5
+ const yup = require('yup')
5
6
  const AJV = require('ajv')
6
7
  const S = require('fluent-json-schema')
7
8
  const Fastify = require('..')
@@ -673,3 +674,31 @@ test('JOI validation overwrite request headers', t => {
673
674
  })
674
675
  })
675
676
  })
677
+
678
+ test('Custom schema object should not trigger FST_ERR_SCH_DUPLICATE', async t => {
679
+ const fastify = Fastify()
680
+ const handler = () => { }
681
+
682
+ fastify.get('/the/url', {
683
+ schema: {
684
+ query: yup.object({
685
+ foo: yup.string()
686
+ })
687
+ },
688
+ validatorCompiler: ({ schema, method, url, httpPart }) => {
689
+ return function (data) {
690
+ // with option strict = false, yup `validateSync` function returns the coerced value if validation was successful, or throws if validation failed
691
+ try {
692
+ const result = schema.validateSync(data, {})
693
+ return { value: result }
694
+ } catch (e) {
695
+ return { error: e }
696
+ }
697
+ }
698
+ },
699
+ handler
700
+ })
701
+
702
+ await fastify.ready()
703
+ t.pass('fastify is ready')
704
+ })
@@ -8,6 +8,7 @@ import fastify, {
8
8
  LightMyRequestResponse,
9
9
  LightMyRequestCallback,
10
10
  InjectOptions, FastifyBaseLogger,
11
+ RouteGenericInterface,
11
12
  ValidationResult
12
13
  } from '../../fastify'
13
14
  import { ErrorObject as AjvErrorObject } from 'ajv'
@@ -24,6 +25,7 @@ expectType<FastifyInstance<http.Server, http.IncomingMessage, http.ServerRespons
24
25
  expectType<FastifyInstance<http.Server, http.IncomingMessage, http.ServerResponse> & PromiseLike<FastifyInstance<http.Server, http.IncomingMessage, http.ServerResponse>>>(fastify({}))
25
26
  // https server
26
27
  expectType<FastifyInstance<https.Server, http.IncomingMessage, http.ServerResponse> & PromiseLike<FastifyInstance<https.Server, http.IncomingMessage, http.ServerResponse>>>(fastify({ https: {} }))
28
+ expectType<FastifyInstance<https.Server, http.IncomingMessage, http.ServerResponse> & PromiseLike<FastifyInstance<https.Server, http.IncomingMessage, http.ServerResponse>>>(fastify({ https: null }))
27
29
  // http2 server
28
30
  expectType<FastifyInstance<http2.Http2Server, http2.Http2ServerRequest, http2.Http2ServerResponse> & PromiseLike<FastifyInstance<http2.Http2Server, http2.Http2ServerRequest, http2.Http2ServerResponse>>>(fastify({ http2: true, http2SessionTimeout: 1000 }))
29
31
  expectType<FastifyInstance<http2.Http2SecureServer, http2.Http2ServerRequest, http2.Http2ServerResponse> & PromiseLike<FastifyInstance<http2.Http2SecureServer, http2.Http2ServerRequest, http2.Http2ServerResponse>>>(fastify({ http2: true, https: {}, http2SessionTimeout: 1000 }))
@@ -231,3 +233,10 @@ const ajvErrorObject: AjvErrorObject = {
231
233
  message: ''
232
234
  }
233
235
  expectAssignable<ValidationResult>(ajvErrorObject)
236
+
237
+ const routeGeneric: RouteGenericInterface = {}
238
+ expectType<unknown>(routeGeneric.Body)
239
+ expectType<unknown>(routeGeneric.Headers)
240
+ expectType<unknown>(routeGeneric.Params)
241
+ expectType<unknown>(routeGeneric.Querystring)
242
+ expectType<unknown>(routeGeneric.Reply)
@@ -230,3 +230,14 @@ expectType<FastifyInstance>(fastify().route({
230
230
  method: 'GET',
231
231
  handler: routeHandlerWithReturnValue
232
232
  }))
233
+
234
+ expectType<boolean>(fastify().hasRoute({
235
+ url: '/',
236
+ method: 'GET'
237
+ }))
238
+
239
+ expectType<boolean>(fastify().hasRoute({
240
+ url: '/',
241
+ method: 'GET',
242
+ constraints: { version: '1.2.0' }
243
+ }))
@@ -4,32 +4,6 @@ const net = require('net')
4
4
  const t = require('tap')
5
5
  const Fastify = require('../fastify')
6
6
 
7
- t.test('Will return 505 HTTP error if HTTP version (default) is not supported', t => {
8
- const fastify = Fastify()
9
-
10
- t.teardown(fastify.close.bind(fastify))
11
-
12
- fastify.get('/', (req, reply) => {
13
- reply.send({ hello: 'world' })
14
- })
15
-
16
- fastify.listen({ port: 0 }, err => {
17
- t.error(err)
18
-
19
- const port = fastify.server.address().port
20
- const client = net.createConnection({ port }, () => {
21
- client.write('GET / HTTP/5.1\r\n\r\n')
22
-
23
- client.once('data', data => {
24
- t.match(data.toString(), /505 HTTP Version Not Supported/i)
25
- client.end(() => {
26
- t.end()
27
- })
28
- })
29
- })
30
- })
31
- })
32
-
33
7
  t.test('Will return 505 HTTP error if HTTP version (2.0 when server is 1.1) is not supported', t => {
34
8
  const fastify = Fastify()
35
9