fastify 5.7.1 → 5.7.2
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.
- package/.idea/fastify.iml +12 -0
- package/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/.idea/jsLibraryMappings.xml +6 -0
- package/.idea/jsLinters/eslint.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/LICENSE +1 -4
- package/SPONSORS.md +2 -2
- package/docs/Guides/Ecosystem.md +2 -1
- package/docs/Guides/Plugins-Guide.md +8 -8
- package/docs/Reference/Server.md +6 -2
- package/docs/Reference/Validation-and-Serialization.md +15 -6
- package/fastify.js +1 -1
- package/lib/content-type-parser.js +40 -30
- package/lib/content-type.js +152 -0
- package/lib/error-serializer.js +24 -10
- package/lib/handle-request.js +12 -4
- package/package.json +2 -2
- package/test/content-parser.test.js +21 -39
- package/test/content-type.test.js +102 -1
- package/test/custom-parser.0.test.js +3 -3
- package/test/custom-parser.1.test.js +0 -35
- package/test/custom-parser.3.test.js +1 -1
- package/test/schema-validation.test.js +66 -33
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<module type="WEB_MODULE" version="4">
|
|
3
|
+
<component name="NewModuleRootManager">
|
|
4
|
+
<content url="file://$MODULE_DIR$">
|
|
5
|
+
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
|
6
|
+
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
|
7
|
+
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
|
8
|
+
</content>
|
|
9
|
+
<orderEntry type="inheritedJdk" />
|
|
10
|
+
<orderEntry type="sourceFolder" forTests="false" />
|
|
11
|
+
</component>
|
|
12
|
+
</module>
|
package/.idea/vcs.xml
ADDED
package/LICENSE
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2016-present The Fastify Team
|
|
4
|
-
|
|
5
|
-
The Fastify team members are listed at https://github.com/fastify/fastify#team
|
|
6
|
-
and in the README file.
|
|
3
|
+
Copyright (c) 2016-present The Fastify Team (members are listed in the README file)
|
|
7
4
|
|
|
8
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/SPONSORS.md
CHANGED
|
@@ -9,7 +9,7 @@ or [GitHub Sponsors](https://github.com/sponsors/fastify)!
|
|
|
9
9
|
|
|
10
10
|
## Tier 4
|
|
11
11
|
|
|
12
|
-
- [SerpApi](
|
|
12
|
+
- [SerpApi](https://serpapi.com/?utm_source=fastify)
|
|
13
13
|
|
|
14
14
|
## Tier 3
|
|
15
15
|
|
|
@@ -17,7 +17,7 @@ or [GitHub Sponsors](https://github.com/sponsors/fastify)!
|
|
|
17
17
|
- [Val Town, Inc.](https://opencollective.com/valtown)
|
|
18
18
|
- [Handsontable - JavaScript Data Grid](https://handsontable.com/docs/react-data-grid/?utm_source=Fastify_GH&utm_medium=sponsorship&utm_campaign=library_sponsorship_2024)
|
|
19
19
|
- [Lokalise - A Localization and Translation Software Tool](https://lokalise.com/?utm_source=Fastify_GH&utm_medium=sponsorship)
|
|
20
|
-
- [
|
|
20
|
+
- [TestMu AI](https://www.testmu.ai/)
|
|
21
21
|
|
|
22
22
|
## Tier 2
|
|
23
23
|
|
package/docs/Guides/Ecosystem.md
CHANGED
|
@@ -427,6 +427,8 @@ section.
|
|
|
427
427
|
context to take place per API call within the Fastify lifecycle of calls.
|
|
428
428
|
- [`fastify-http-errors-enhanced`](https://github.com/ShogunPanda/fastify-http-errors-enhanced)
|
|
429
429
|
An error handling plugin for Fastify that uses enhanced HTTP errors.
|
|
430
|
+
- [`fastify-http-exceptions`](https://github.com/bhouston/fastify-http-exceptions)
|
|
431
|
+
Typed HTTP status exceptions which are automatically converted into Fastify responses.
|
|
430
432
|
- [`fastify-http2https`](https://github.com/lolo32/fastify-http2https) Redirect
|
|
431
433
|
HTTP requests to HTTPS, both using the same port number, or different response
|
|
432
434
|
on HTTP and HTTPS.
|
|
@@ -746,7 +748,6 @@ middlewares into Fastify plugins
|
|
|
746
748
|
- [`typeorm-fastify-plugin`](https://github.com/jclemens24/fastify-typeorm) A simple
|
|
747
749
|
and updated Typeorm plugin for use with Fastify.
|
|
748
750
|
|
|
749
|
-
|
|
750
751
|
#### [Community Tools](#community-tools)
|
|
751
752
|
|
|
752
753
|
- [`@fastify-userland/workflows`](https://github.com/fastify-userland/workflows)
|
|
@@ -204,12 +204,12 @@ of an *arrow function expression*.
|
|
|
204
204
|
|
|
205
205
|
You can do the same for the `request` object:
|
|
206
206
|
```js
|
|
207
|
-
fastify.decorate('
|
|
208
|
-
return req.headers[header
|
|
207
|
+
fastify.decorate('getBoolHeader', (req, name) => {
|
|
208
|
+
return req.headers[name] ?? false // We return `false` if header is missing
|
|
209
209
|
})
|
|
210
210
|
|
|
211
211
|
fastify.addHook('preHandler', (request, reply, done) => {
|
|
212
|
-
request.isHappy = fastify.
|
|
212
|
+
request.isHappy = fastify.getBoolHeader(request, 'happy')
|
|
213
213
|
done()
|
|
214
214
|
})
|
|
215
215
|
|
|
@@ -219,14 +219,14 @@ fastify.get('/happiness', (request, reply) => {
|
|
|
219
219
|
```
|
|
220
220
|
Again, it works, but it can be much better!
|
|
221
221
|
```js
|
|
222
|
-
fastify.decorateRequest('
|
|
223
|
-
this.isHappy = this.headers[
|
|
222
|
+
fastify.decorateRequest('setBoolHeader', function (name) {
|
|
223
|
+
this.isHappy = this.headers[name] ?? false
|
|
224
224
|
})
|
|
225
225
|
|
|
226
226
|
fastify.decorateRequest('isHappy', false) // This will be added to the Request object prototype, yay speed!
|
|
227
227
|
|
|
228
228
|
fastify.addHook('preHandler', (request, reply, done) => {
|
|
229
|
-
request.
|
|
229
|
+
request.setBoolHeader('happy')
|
|
230
230
|
done()
|
|
231
231
|
})
|
|
232
232
|
|
|
@@ -337,11 +337,11 @@ fastify.register((instance, opts, done) => {
|
|
|
337
337
|
}
|
|
338
338
|
})
|
|
339
339
|
|
|
340
|
-
|
|
340
|
+
instance.get('/plugin1', {config: {useUtil: true}}, (request, reply) => {
|
|
341
341
|
reply.send(request)
|
|
342
342
|
})
|
|
343
343
|
|
|
344
|
-
|
|
344
|
+
instance.get('/plugin2', (request, reply) => {
|
|
345
345
|
reply.send(request)
|
|
346
346
|
})
|
|
347
347
|
|
package/docs/Reference/Server.md
CHANGED
|
@@ -534,7 +534,9 @@ recommend using a custom parser to convert only the keys to lowercase.
|
|
|
534
534
|
```js
|
|
535
535
|
const qs = require('qs')
|
|
536
536
|
const fastify = require('fastify')({
|
|
537
|
-
|
|
537
|
+
routerOptions: {
|
|
538
|
+
querystringParser: str => qs.parse(str)
|
|
539
|
+
}
|
|
538
540
|
})
|
|
539
541
|
```
|
|
540
542
|
|
|
@@ -544,7 +546,9 @@ like the example below for case insensitive keys and values:
|
|
|
544
546
|
```js
|
|
545
547
|
const querystring = require('fast-querystring')
|
|
546
548
|
const fastify = require('fastify')({
|
|
547
|
-
|
|
549
|
+
routerOptions: {
|
|
550
|
+
querystringParser: str => querystring.parse(str.toLowerCase())
|
|
551
|
+
}
|
|
548
552
|
})
|
|
549
553
|
```
|
|
550
554
|
|
|
@@ -675,9 +675,12 @@ const schema = {
|
|
|
675
675
|
content: {
|
|
676
676
|
'application/json': {
|
|
677
677
|
schema: {
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
678
|
+
type: 'object',
|
|
679
|
+
properties: {
|
|
680
|
+
name: { type: 'string' },
|
|
681
|
+
image: { type: 'string' },
|
|
682
|
+
address: { type: 'string' }
|
|
683
|
+
}
|
|
681
684
|
}
|
|
682
685
|
},
|
|
683
686
|
'application/vnd.v1+json': {
|
|
@@ -692,8 +695,11 @@ const schema = {
|
|
|
692
695
|
content: {
|
|
693
696
|
'application/vnd.v2+json': {
|
|
694
697
|
schema: {
|
|
695
|
-
|
|
696
|
-
|
|
698
|
+
type: 'object',
|
|
699
|
+
properties: {
|
|
700
|
+
fullName: { type: 'string' },
|
|
701
|
+
phone: { type: 'string' }
|
|
702
|
+
}
|
|
697
703
|
}
|
|
698
704
|
}
|
|
699
705
|
}
|
|
@@ -703,7 +709,10 @@ const schema = {
|
|
|
703
709
|
// */* is match-all content-type
|
|
704
710
|
'*/*': {
|
|
705
711
|
schema: {
|
|
706
|
-
|
|
712
|
+
type: 'object',
|
|
713
|
+
properties: {
|
|
714
|
+
desc: { type: 'string' }
|
|
715
|
+
}
|
|
707
716
|
}
|
|
708
717
|
}
|
|
709
718
|
}
|
package/fastify.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const { AsyncResource } = require('node:async_hooks')
|
|
4
4
|
const { FifoMap: Fifo } = require('toad-cache')
|
|
5
5
|
const { parse: secureJsonParse } = require('secure-json-parse')
|
|
6
|
+
const ContentType = require('./content-type')
|
|
6
7
|
const {
|
|
7
8
|
kDefaultJsonParse,
|
|
8
9
|
kContentTypeParser,
|
|
@@ -75,8 +76,13 @@ ContentTypeParser.prototype.add = function (contentType, opts, parserFn) {
|
|
|
75
76
|
this.customParsers.set('', parser)
|
|
76
77
|
} else {
|
|
77
78
|
if (contentTypeIsString) {
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
const ct = new ContentType(contentType)
|
|
80
|
+
if (ct.isValid === false) {
|
|
81
|
+
throw new FST_ERR_CTP_INVALID_TYPE()
|
|
82
|
+
}
|
|
83
|
+
const normalizedContentType = ct.toString()
|
|
84
|
+
this.parserList.unshift(normalizedContentType)
|
|
85
|
+
this.customParsers.set(normalizedContentType, parser)
|
|
80
86
|
} else {
|
|
81
87
|
validateRegExp(contentType)
|
|
82
88
|
this.parserRegExpList.unshift(contentType)
|
|
@@ -87,7 +93,7 @@ ContentTypeParser.prototype.add = function (contentType, opts, parserFn) {
|
|
|
87
93
|
|
|
88
94
|
ContentTypeParser.prototype.hasParser = function (contentType) {
|
|
89
95
|
if (typeof contentType === 'string') {
|
|
90
|
-
contentType = contentType
|
|
96
|
+
contentType = new ContentType(contentType).toString()
|
|
91
97
|
} else {
|
|
92
98
|
if (!(contentType instanceof RegExp)) throw new FST_ERR_CTP_INVALID_TYPE()
|
|
93
99
|
contentType = contentType.toString()
|
|
@@ -97,45 +103,49 @@ ContentTypeParser.prototype.hasParser = function (contentType) {
|
|
|
97
103
|
}
|
|
98
104
|
|
|
99
105
|
ContentTypeParser.prototype.existingParser = function (contentType) {
|
|
100
|
-
if (contentType === '
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
106
|
+
if (typeof contentType === 'string') {
|
|
107
|
+
const ct = new ContentType(contentType).toString()
|
|
108
|
+
if (contentType === 'application/json' && this.customParsers.has(contentType)) {
|
|
109
|
+
return this.customParsers.get(ct).fn !== this[kDefaultJsonParse]
|
|
110
|
+
}
|
|
111
|
+
if (contentType === 'text/plain' && this.customParsers.has(contentType)) {
|
|
112
|
+
return this.customParsers.get(ct).fn !== defaultPlainTextParser
|
|
113
|
+
}
|
|
105
114
|
}
|
|
106
115
|
|
|
107
116
|
return this.hasParser(contentType)
|
|
108
117
|
}
|
|
109
118
|
|
|
110
119
|
ContentTypeParser.prototype.getParser = function (contentType) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
120
|
+
if (typeof contentType === 'string') {
|
|
121
|
+
contentType = new ContentType(contentType)
|
|
122
|
+
}
|
|
123
|
+
const ct = contentType.toString()
|
|
124
|
+
|
|
125
|
+
let parser = this.cache.get(ct)
|
|
114
126
|
if (parser !== undefined) return parser
|
|
127
|
+
parser = this.customParsers.get(ct)
|
|
128
|
+
if (parser !== undefined) {
|
|
129
|
+
this.cache.set(ct, parser)
|
|
130
|
+
return parser
|
|
131
|
+
}
|
|
115
132
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
caseInsensitiveContentType.charCodeAt(parserListItem.length) === 9 /* `\t` */
|
|
126
|
-
)
|
|
127
|
-
) {
|
|
128
|
-
parser = this.customParsers.get(parserListItem)
|
|
129
|
-
this.cache.set(contentType, parser)
|
|
130
|
-
return parser
|
|
131
|
-
}
|
|
133
|
+
// We have conflicting desires across our test suite. In some cases, we
|
|
134
|
+
// expect to get a parser by just passing the media-type. In others, we expect
|
|
135
|
+
// to get a parser registered under the media-type while also providing
|
|
136
|
+
// parameters. And in yet others, we expect to register a parser under the
|
|
137
|
+
// media-type and have it apply to any request with a header that starts
|
|
138
|
+
// with that type.
|
|
139
|
+
parser = this.customParsers.get(contentType.mediaType)
|
|
140
|
+
if (parser !== undefined) {
|
|
141
|
+
return parser
|
|
132
142
|
}
|
|
133
143
|
|
|
134
144
|
for (let j = 0; j !== this.parserRegExpList.length; ++j) {
|
|
135
145
|
const parserRegExp = this.parserRegExpList[j]
|
|
136
|
-
if (parserRegExp.test(
|
|
146
|
+
if (parserRegExp.test(ct)) {
|
|
137
147
|
parser = this.customParsers.get(parserRegExp.toString())
|
|
138
|
-
this.cache.set(
|
|
148
|
+
this.cache.set(ct, parser)
|
|
139
149
|
return parser
|
|
140
150
|
}
|
|
141
151
|
}
|
|
@@ -154,7 +164,7 @@ ContentTypeParser.prototype.remove = function (contentType) {
|
|
|
154
164
|
let parsers
|
|
155
165
|
|
|
156
166
|
if (typeof contentType === 'string') {
|
|
157
|
-
contentType = contentType
|
|
167
|
+
contentType = new ContentType(contentType).toString()
|
|
158
168
|
parsers = this.parserList
|
|
159
169
|
} else {
|
|
160
170
|
if (!(contentType instanceof RegExp)) throw new FST_ERR_CTP_INVALID_TYPE()
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* keyValuePairsReg is used to split the parameters list into associated
|
|
5
|
+
* key value pairings.
|
|
6
|
+
*
|
|
7
|
+
* @see https://httpwg.org/specs/rfc9110.html#parameter
|
|
8
|
+
* @type {RegExp}
|
|
9
|
+
*/
|
|
10
|
+
const keyValuePairsReg = /([\w!#$%&'*+.^`|~-]+)=([^;]*)/gm
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* typeNameReg is used to validate that the first part of the media-type
|
|
14
|
+
* does not use disallowed characters.
|
|
15
|
+
*
|
|
16
|
+
* @see https://httpwg.org/specs/rfc9110.html#rule.token.separators
|
|
17
|
+
* @type {RegExp}
|
|
18
|
+
*/
|
|
19
|
+
const typeNameReg = /^[\w!#$%&'*+.^`|~-]+$/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* subtypeNameReg is used to validate that the second part of the media-type
|
|
23
|
+
* does not use disallowed characters.
|
|
24
|
+
*
|
|
25
|
+
* @see https://httpwg.org/specs/rfc9110.html#rule.token.separators
|
|
26
|
+
* @type {RegExp}
|
|
27
|
+
*/
|
|
28
|
+
const subtypeNameReg = /^[\w!#$%&'*+.^`|~-]+\s*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* ContentType parses and represents the value of the content-type header.
|
|
32
|
+
*
|
|
33
|
+
* @see https://httpwg.org/specs/rfc9110.html#media.type
|
|
34
|
+
* @see https://httpwg.org/specs/rfc9110.html#parameter
|
|
35
|
+
*/
|
|
36
|
+
class ContentType {
|
|
37
|
+
#valid = false
|
|
38
|
+
#empty = true
|
|
39
|
+
#type = ''
|
|
40
|
+
#subtype = ''
|
|
41
|
+
#parameters = new Map()
|
|
42
|
+
#string
|
|
43
|
+
|
|
44
|
+
constructor (headerValue) {
|
|
45
|
+
if (headerValue == null || headerValue === '' || headerValue === 'undefined') {
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let sepIdx = headerValue.indexOf(';')
|
|
50
|
+
if (sepIdx === -1) {
|
|
51
|
+
// The value is the simplest `type/subtype` variant.
|
|
52
|
+
sepIdx = headerValue.indexOf('/')
|
|
53
|
+
if (sepIdx === -1) {
|
|
54
|
+
// Got a string without the correct `type/subtype` format.
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const type = headerValue.slice(0, sepIdx).trimStart().toLowerCase()
|
|
59
|
+
const subtype = headerValue.slice(sepIdx + 1).trimEnd().toLowerCase()
|
|
60
|
+
|
|
61
|
+
if (
|
|
62
|
+
typeNameReg.test(type) === true &&
|
|
63
|
+
subtypeNameReg.test(subtype) === true
|
|
64
|
+
) {
|
|
65
|
+
this.#valid = true
|
|
66
|
+
this.#empty = false
|
|
67
|
+
this.#type = type
|
|
68
|
+
this.#subtype = subtype
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// We have a `type/subtype; params=list...` header value.
|
|
75
|
+
const mediaType = headerValue.slice(0, sepIdx).toLowerCase()
|
|
76
|
+
const paramsList = headerValue.slice(sepIdx + 1).trim()
|
|
77
|
+
|
|
78
|
+
sepIdx = mediaType.indexOf('/')
|
|
79
|
+
if (sepIdx === -1) {
|
|
80
|
+
// We got an invalid string like `something; params=list...`.
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
const type = mediaType.slice(0, sepIdx).trimStart()
|
|
84
|
+
const subtype = mediaType.slice(sepIdx + 1).trimEnd()
|
|
85
|
+
|
|
86
|
+
if (
|
|
87
|
+
typeNameReg.test(type) === false ||
|
|
88
|
+
subtypeNameReg.test(subtype) === false
|
|
89
|
+
) {
|
|
90
|
+
// Some portion of the media-type is using invalid characters. Therefore,
|
|
91
|
+
// the content-type header is invalid.
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
this.#type = type
|
|
95
|
+
this.#subtype = subtype
|
|
96
|
+
this.#valid = true
|
|
97
|
+
this.#empty = false
|
|
98
|
+
|
|
99
|
+
let matches = keyValuePairsReg.exec(paramsList)
|
|
100
|
+
while (matches) {
|
|
101
|
+
const key = matches[1]
|
|
102
|
+
const value = matches[2]
|
|
103
|
+
if (value[0] === '"') {
|
|
104
|
+
if (value.at(-1) !== '"') {
|
|
105
|
+
this.#parameters.set(key, 'invalid quoted string')
|
|
106
|
+
matches = keyValuePairsReg.exec(paramsList)
|
|
107
|
+
continue
|
|
108
|
+
}
|
|
109
|
+
// We should probably verify the value matches a quoted string
|
|
110
|
+
// (https://httpwg.org/specs/rfc9110.html#rule.quoted-string) value.
|
|
111
|
+
// But we are not really doing much with the parameter values, so we
|
|
112
|
+
// are omitting that at this time.
|
|
113
|
+
this.#parameters.set(key, value.slice(1, value.length - 1))
|
|
114
|
+
} else {
|
|
115
|
+
this.#parameters.set(key, value)
|
|
116
|
+
}
|
|
117
|
+
matches = keyValuePairsReg.exec(paramsList)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
get [Symbol.toStringTag] () { return 'ContentType' }
|
|
122
|
+
|
|
123
|
+
get isEmpty () { return this.#empty }
|
|
124
|
+
|
|
125
|
+
get isValid () { return this.#valid }
|
|
126
|
+
|
|
127
|
+
get mediaType () { return `${this.#type}/${this.#subtype}` }
|
|
128
|
+
|
|
129
|
+
get type () { return this.#type }
|
|
130
|
+
|
|
131
|
+
get subtype () { return this.#subtype }
|
|
132
|
+
|
|
133
|
+
get parameters () { return this.#parameters }
|
|
134
|
+
|
|
135
|
+
toString () {
|
|
136
|
+
/* c8 ignore next: we don't need to verify the cache */
|
|
137
|
+
if (this.#string) return this.#string
|
|
138
|
+
const parameters = []
|
|
139
|
+
for (const [key, value] of this.#parameters.entries()) {
|
|
140
|
+
parameters.push(`${key}="${value}"`)
|
|
141
|
+
}
|
|
142
|
+
const result = [this.#type, '/', this.#subtype]
|
|
143
|
+
if (parameters.length > 0) {
|
|
144
|
+
result.push('; ')
|
|
145
|
+
result.push(parameters.join('; '))
|
|
146
|
+
}
|
|
147
|
+
this.#string = result.join('')
|
|
148
|
+
return this.#string
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
module.exports = ContentType
|
package/lib/error-serializer.js
CHANGED
|
@@ -13,6 +13,20 @@
|
|
|
13
13
|
module.exports = function anonymous(validator,serializer
|
|
14
14
|
) {
|
|
15
15
|
|
|
16
|
+
|
|
17
|
+
const {
|
|
18
|
+
asString,
|
|
19
|
+
asNumber,
|
|
20
|
+
asBoolean,
|
|
21
|
+
asDateTime,
|
|
22
|
+
asDate,
|
|
23
|
+
asTime,
|
|
24
|
+
asUnsafeString
|
|
25
|
+
} = serializer
|
|
26
|
+
|
|
27
|
+
const asInteger = serializer.asInteger.bind(serializer)
|
|
28
|
+
|
|
29
|
+
|
|
16
30
|
const JSON_STR_BEGIN_OBJECT = '{'
|
|
17
31
|
const JSON_STR_END_OBJECT = '}'
|
|
18
32
|
const JSON_STR_BEGIN_ARRAY = '['
|
|
@@ -43,7 +57,7 @@ let addComma = false
|
|
|
43
57
|
if (value !== undefined) {
|
|
44
58
|
!addComma && (addComma = true) || (json += JSON_STR_COMMA)
|
|
45
59
|
json += "\"statusCode\":"
|
|
46
|
-
json +=
|
|
60
|
+
json += asNumber(value)
|
|
47
61
|
}
|
|
48
62
|
|
|
49
63
|
value = obj["code"]
|
|
@@ -57,12 +71,12 @@ let addComma = false
|
|
|
57
71
|
} else if (value instanceof Date) {
|
|
58
72
|
json += JSON_STR_QUOTE + value.toISOString() + JSON_STR_QUOTE
|
|
59
73
|
} else if (value instanceof RegExp) {
|
|
60
|
-
json +=
|
|
74
|
+
json += asString(value.source)
|
|
61
75
|
} else {
|
|
62
|
-
json +=
|
|
76
|
+
json += asString(value.toString())
|
|
63
77
|
}
|
|
64
78
|
} else {
|
|
65
|
-
json +=
|
|
79
|
+
json += asString(value)
|
|
66
80
|
}
|
|
67
81
|
|
|
68
82
|
}
|
|
@@ -78,12 +92,12 @@ let addComma = false
|
|
|
78
92
|
} else if (value instanceof Date) {
|
|
79
93
|
json += JSON_STR_QUOTE + value.toISOString() + JSON_STR_QUOTE
|
|
80
94
|
} else if (value instanceof RegExp) {
|
|
81
|
-
json +=
|
|
95
|
+
json += asString(value.source)
|
|
82
96
|
} else {
|
|
83
|
-
json +=
|
|
97
|
+
json += asString(value.toString())
|
|
84
98
|
}
|
|
85
99
|
} else {
|
|
86
|
-
json +=
|
|
100
|
+
json += asString(value)
|
|
87
101
|
}
|
|
88
102
|
|
|
89
103
|
}
|
|
@@ -99,12 +113,12 @@ let addComma = false
|
|
|
99
113
|
} else if (value instanceof Date) {
|
|
100
114
|
json += JSON_STR_QUOTE + value.toISOString() + JSON_STR_QUOTE
|
|
101
115
|
} else if (value instanceof RegExp) {
|
|
102
|
-
json +=
|
|
116
|
+
json += asString(value.source)
|
|
103
117
|
} else {
|
|
104
|
-
json +=
|
|
118
|
+
json += asString(value.toString())
|
|
105
119
|
}
|
|
106
120
|
} else {
|
|
107
|
-
json +=
|
|
121
|
+
json += asString(value)
|
|
108
122
|
}
|
|
109
123
|
|
|
110
124
|
}
|
package/lib/handle-request.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const diagnostics = require('node:diagnostics_channel')
|
|
4
|
+
const ContentType = require('./content-type')
|
|
5
|
+
const wrapThenable = require('./wrap-thenable')
|
|
4
6
|
const { validate: validateSchema } = require('./validation')
|
|
5
7
|
const { preValidationHookRunner, preHandlerHookRunner } = require('./hooks')
|
|
6
|
-
const
|
|
8
|
+
const { FST_ERR_CTP_INVALID_MEDIA_TYPE } = require('./errors')
|
|
7
9
|
const { setErrorStatusCode } = require('./error-status')
|
|
8
10
|
const {
|
|
9
11
|
kReplyIsError,
|
|
@@ -31,9 +33,9 @@ function handleRequest (err, request, reply) {
|
|
|
31
33
|
|
|
32
34
|
if (this[kSupportedHTTPMethods].bodywith.has(method)) {
|
|
33
35
|
const headers = request.headers
|
|
34
|
-
const
|
|
36
|
+
const ctHeader = headers['content-type']
|
|
35
37
|
|
|
36
|
-
if (
|
|
38
|
+
if (ctHeader === undefined) {
|
|
37
39
|
const contentLength = headers['content-length']
|
|
38
40
|
const transferEncoding = headers['transfer-encoding']
|
|
39
41
|
const isEmptyBody = transferEncoding === undefined &&
|
|
@@ -49,7 +51,13 @@ function handleRequest (err, request, reply) {
|
|
|
49
51
|
return
|
|
50
52
|
}
|
|
51
53
|
|
|
52
|
-
|
|
54
|
+
const contentType = new ContentType(ctHeader)
|
|
55
|
+
if (contentType.isValid === false) {
|
|
56
|
+
reply[kReplyIsError] = true
|
|
57
|
+
reply.status(415).send(new FST_ERR_CTP_INVALID_MEDIA_TYPE())
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
request[kRouteContext].contentTypeParser.run(contentType.toString(), handler, request, reply)
|
|
53
61
|
return
|
|
54
62
|
}
|
|
55
63
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fastify",
|
|
3
|
-
"version": "5.7.
|
|
3
|
+
"version": "5.7.2",
|
|
4
4
|
"description": "Fast and low overhead web framework, for Node.js",
|
|
5
5
|
"main": "fastify.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"benchmark:parser:error": "concurrently -k -s first \"node ./examples/benchmark/parser.js\" \"autocannon -c 100 -d 30 -p 10 -i ./examples/benchmark/body.json -H \"content-type:application/jsoff\" -H \"content-length:123\" -m POST localhost:3000/\"",
|
|
13
13
|
"build:validation": "node build/build-error-serializer.js && node build/build-validation.js",
|
|
14
14
|
"build:sync-version": "node build/sync-version.js",
|
|
15
|
-
"coverage": "c8 --reporter html borp --reporter=@jsumners/line-reporter
|
|
15
|
+
"coverage": "c8 --reporter html borp --reporter=@jsumners/line-reporter",
|
|
16
16
|
"coverage:ci-check-coverage": "borp --reporter=@jsumners/line-reporter --coverage --check-coverage --lines 100",
|
|
17
17
|
"lint": "npm run lint:eslint",
|
|
18
18
|
"lint:fix": "eslint --fix",
|
|
@@ -55,6 +55,7 @@ test('getParser', async t => {
|
|
|
55
55
|
fastify.addContentTypeParser(/^image\/.*/, first)
|
|
56
56
|
fastify.addContentTypeParser(/^application\/.+\+xml/, second)
|
|
57
57
|
fastify.addContentTypeParser('text/html', third)
|
|
58
|
+
fastify.addContentTypeParser('text/html; charset=utf-8', third)
|
|
58
59
|
|
|
59
60
|
t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('application/t+xml').fn, second)
|
|
60
61
|
t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('image/png').fn, first)
|
|
@@ -66,14 +67,15 @@ test('getParser', async t => {
|
|
|
66
67
|
})
|
|
67
68
|
|
|
68
69
|
await t.test('should return matching parser with caching /1', t => {
|
|
69
|
-
t.plan(
|
|
70
|
+
t.plan(7)
|
|
70
71
|
|
|
71
72
|
const fastify = Fastify()
|
|
72
73
|
|
|
73
74
|
fastify.addContentTypeParser('text/html', first)
|
|
74
75
|
|
|
75
|
-
t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html').fn, first)
|
|
76
76
|
t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 0)
|
|
77
|
+
t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html').fn, first)
|
|
78
|
+
t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 1)
|
|
77
79
|
t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html ').fn, first)
|
|
78
80
|
t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 1)
|
|
79
81
|
t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html ').fn, first)
|
|
@@ -81,20 +83,21 @@ test('getParser', async t => {
|
|
|
81
83
|
})
|
|
82
84
|
|
|
83
85
|
await t.test('should return matching parser with caching /2', t => {
|
|
84
|
-
t.plan(
|
|
86
|
+
t.plan(9)
|
|
85
87
|
|
|
86
88
|
const fastify = Fastify()
|
|
87
89
|
|
|
88
90
|
fastify.addContentTypeParser('text/html', first)
|
|
89
91
|
|
|
90
|
-
t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html').fn, first)
|
|
91
92
|
t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 0)
|
|
93
|
+
t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html').fn, first)
|
|
94
|
+
t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 1)
|
|
92
95
|
t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/HTML').fn, first)
|
|
93
96
|
t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 1)
|
|
94
97
|
t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('TEXT/html').fn, first)
|
|
95
|
-
t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size,
|
|
98
|
+
t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 1)
|
|
96
99
|
t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('TEXT/html').fn, first)
|
|
97
|
-
t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size,
|
|
100
|
+
t.assert.strictEqual(fastify[keys.kContentTypeParser].cache.size, 1)
|
|
98
101
|
})
|
|
99
102
|
|
|
100
103
|
await t.test('should return matching parser with caching /3', t => {
|
|
@@ -125,7 +128,7 @@ test('getParser', async t => {
|
|
|
125
128
|
})
|
|
126
129
|
|
|
127
130
|
await t.test('should return parser that catches all if no other is set', t => {
|
|
128
|
-
t.plan(
|
|
131
|
+
t.plan(2)
|
|
129
132
|
|
|
130
133
|
const fastify = Fastify()
|
|
131
134
|
|
|
@@ -134,7 +137,6 @@ test('getParser', async t => {
|
|
|
134
137
|
|
|
135
138
|
t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('image/gif').fn, first)
|
|
136
139
|
t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text/html').fn, second)
|
|
137
|
-
t.assert.strictEqual(fastify[keys.kContentTypeParser].getParser('text').fn, first)
|
|
138
140
|
})
|
|
139
141
|
|
|
140
142
|
await t.test('should return undefined if no matching parser exist', t => {
|
|
@@ -208,7 +210,7 @@ test('add', async t => {
|
|
|
208
210
|
const fastify = Fastify()
|
|
209
211
|
const contentTypeParser = fastify[keys.kContentTypeParser]
|
|
210
212
|
|
|
211
|
-
t.assert.ifError(contentTypeParser.add('test', {}, first))
|
|
213
|
+
t.assert.ifError(contentTypeParser.add('test/type', {}, first))
|
|
212
214
|
t.assert.ifError(contentTypeParser.add(/test/, {}, first))
|
|
213
215
|
t.assert.throws(
|
|
214
216
|
() => contentTypeParser.add({}, {}, first),
|
|
@@ -557,7 +559,7 @@ test('content-type match parameters - regexp', async t => {
|
|
|
557
559
|
|
|
558
560
|
const fastify = Fastify()
|
|
559
561
|
fastify.removeAllContentTypeParsers()
|
|
560
|
-
fastify.addContentTypeParser(/application\/json; charset=utf8/, function (request, body, done) {
|
|
562
|
+
fastify.addContentTypeParser(/application\/json; charset="utf8"/, function (request, body, done) {
|
|
561
563
|
t.assert.ok('should be called')
|
|
562
564
|
done(null, body)
|
|
563
565
|
})
|
|
@@ -697,37 +699,17 @@ test('content-type regexp list should be cloned when plugin override', async t =
|
|
|
697
699
|
}
|
|
698
700
|
})
|
|
699
701
|
|
|
700
|
-
test('
|
|
702
|
+
test('content-type fail when not a valid type', async t => {
|
|
701
703
|
t.plan(1)
|
|
702
704
|
|
|
703
705
|
const fastify = Fastify()
|
|
704
706
|
fastify.removeAllContentTypeParsers()
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
await fastify.inject({
|
|
715
|
-
method: 'POST',
|
|
716
|
-
path: '/',
|
|
717
|
-
headers: {
|
|
718
|
-
'content-type': 'application/json; foo=bar; charset=utf8'
|
|
719
|
-
},
|
|
720
|
-
body: ''
|
|
721
|
-
})
|
|
722
|
-
|
|
723
|
-
await fastify.inject({
|
|
724
|
-
method: 'POST',
|
|
725
|
-
path: '/',
|
|
726
|
-
headers: {
|
|
727
|
-
'content-type': 'image/jpeg'
|
|
728
|
-
},
|
|
729
|
-
body: ''
|
|
730
|
-
})
|
|
731
|
-
|
|
732
|
-
t.assert.ok('end')
|
|
707
|
+
try {
|
|
708
|
+
fastify.addContentTypeParser('type-only', function (request, body, done) {
|
|
709
|
+
t.assert.fail('shouldn\'t be called')
|
|
710
|
+
done(null, body)
|
|
711
|
+
})
|
|
712
|
+
} catch (error) {
|
|
713
|
+
t.assert.equal(error.message, 'The content type should be a string or a RegExp')
|
|
714
|
+
}
|
|
733
715
|
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { test } = require('node:test')
|
|
3
|
+
const { describe, test } = require('node:test')
|
|
4
|
+
const ContentType = require('../lib/content-type')
|
|
4
5
|
const Fastify = require('..')
|
|
5
6
|
|
|
6
7
|
test('should remove content-type for setErrorHandler', async t => {
|
|
@@ -40,3 +41,103 @@ test('should remove content-type for setErrorHandler', async t => {
|
|
|
40
41
|
t.assert.strictEqual(statusCode, 400)
|
|
41
42
|
t.assert.strictEqual(body, JSON.stringify({ foo: 'bar' }))
|
|
42
43
|
})
|
|
44
|
+
|
|
45
|
+
describe('ContentType class', () => {
|
|
46
|
+
test('returns empty instance for empty value', (t) => {
|
|
47
|
+
let found = new ContentType('')
|
|
48
|
+
t.assert.equal(found.isEmpty, true)
|
|
49
|
+
|
|
50
|
+
found = new ContentType('undefined')
|
|
51
|
+
t.assert.equal(found.isEmpty, true)
|
|
52
|
+
|
|
53
|
+
found = new ContentType()
|
|
54
|
+
t.assert.equal(found.isEmpty, true)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('indicates media type is not correct format', (t) => {
|
|
58
|
+
let found = new ContentType('foo')
|
|
59
|
+
t.assert.equal(found.isEmpty, true)
|
|
60
|
+
t.assert.equal(found.isValid, false)
|
|
61
|
+
|
|
62
|
+
found = new ContentType('foo /bar')
|
|
63
|
+
t.assert.equal(found.isEmpty, true)
|
|
64
|
+
t.assert.equal(found.isValid, false)
|
|
65
|
+
|
|
66
|
+
found = new ContentType('foo/ bar')
|
|
67
|
+
t.assert.equal(found.isEmpty, true)
|
|
68
|
+
t.assert.equal(found.isValid, false)
|
|
69
|
+
|
|
70
|
+
found = new ContentType('foo; param=1')
|
|
71
|
+
t.assert.equal(found.isEmpty, true)
|
|
72
|
+
t.assert.equal(found.isValid, false)
|
|
73
|
+
|
|
74
|
+
found = new ContentType('foo/π; param=1')
|
|
75
|
+
t.assert.equal(found.isEmpty, true)
|
|
76
|
+
t.assert.equal(found.isValid, false)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('returns a plain media type instance', (t) => {
|
|
80
|
+
const found = new ContentType('Application/JSON')
|
|
81
|
+
t.assert.equal(found.mediaType, 'application/json')
|
|
82
|
+
t.assert.equal(found.type, 'application')
|
|
83
|
+
t.assert.equal(found.subtype, 'json')
|
|
84
|
+
t.assert.equal(found.parameters.size, 0)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('handles empty parameters list', (t) => {
|
|
88
|
+
const found = new ContentType('Application/JSON ;')
|
|
89
|
+
t.assert.equal(found.isEmpty, false)
|
|
90
|
+
t.assert.equal(found.mediaType, 'application/json')
|
|
91
|
+
t.assert.equal(found.type, 'application')
|
|
92
|
+
t.assert.equal(found.subtype, 'json')
|
|
93
|
+
t.assert.equal(found.parameters.size, 0)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
test('returns a media type instance with parameters', (t) => {
|
|
97
|
+
const found = new ContentType('Application/JSON ; charset=utf-8; foo=BaR;baz=" 42"')
|
|
98
|
+
t.assert.equal(found.isEmpty, false)
|
|
99
|
+
t.assert.equal(found.mediaType, 'application/json')
|
|
100
|
+
t.assert.equal(found.type, 'application')
|
|
101
|
+
t.assert.equal(found.subtype, 'json')
|
|
102
|
+
t.assert.equal(found.parameters.size, 3)
|
|
103
|
+
|
|
104
|
+
const expected = [
|
|
105
|
+
['charset', 'utf-8'],
|
|
106
|
+
['foo', 'BaR'],
|
|
107
|
+
['baz', ' 42']
|
|
108
|
+
]
|
|
109
|
+
t.assert.deepStrictEqual(
|
|
110
|
+
Array.from(found.parameters.entries()),
|
|
111
|
+
expected
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
t.assert.equal(
|
|
115
|
+
found.toString(),
|
|
116
|
+
'application/json; charset="utf-8"; foo="BaR"; baz=" 42"'
|
|
117
|
+
)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
test('skips invalid quoted string parameters', (t) => {
|
|
121
|
+
const found = new ContentType('Application/JSON ; charset=utf-8; foo=BaR;baz=" 42')
|
|
122
|
+
t.assert.equal(found.isEmpty, false)
|
|
123
|
+
t.assert.equal(found.mediaType, 'application/json')
|
|
124
|
+
t.assert.equal(found.type, 'application')
|
|
125
|
+
t.assert.equal(found.subtype, 'json')
|
|
126
|
+
t.assert.equal(found.parameters.size, 3)
|
|
127
|
+
|
|
128
|
+
const expected = [
|
|
129
|
+
['charset', 'utf-8'],
|
|
130
|
+
['foo', 'BaR'],
|
|
131
|
+
['baz', 'invalid quoted string']
|
|
132
|
+
]
|
|
133
|
+
t.assert.deepStrictEqual(
|
|
134
|
+
Array.from(found.parameters.entries()),
|
|
135
|
+
expected
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
t.assert.equal(
|
|
139
|
+
found.toString(),
|
|
140
|
+
'application/json; charset="utf-8"; foo="BaR"; baz="invalid quoted string"'
|
|
141
|
+
)
|
|
142
|
+
})
|
|
143
|
+
})
|
|
@@ -354,7 +354,7 @@ test('catch all content type parser', async (t) => {
|
|
|
354
354
|
method: 'POST',
|
|
355
355
|
body: 'hello',
|
|
356
356
|
headers: {
|
|
357
|
-
'Content-Type': 'very-weird-content-type'
|
|
357
|
+
'Content-Type': 'very-weird-content-type/foo'
|
|
358
358
|
}
|
|
359
359
|
})
|
|
360
360
|
|
|
@@ -363,7 +363,7 @@ test('catch all content type parser', async (t) => {
|
|
|
363
363
|
t.assert.strictEqual(await result2.text(), 'hello')
|
|
364
364
|
})
|
|
365
365
|
|
|
366
|
-
test('catch all content type parser should not interfere with other
|
|
366
|
+
test('catch all content type parser should not interfere with other content type parsers', async (t) => {
|
|
367
367
|
t.plan(6)
|
|
368
368
|
const fastify = Fastify()
|
|
369
369
|
|
|
@@ -404,7 +404,7 @@ test('catch all content type parser should not interfere with other conte type p
|
|
|
404
404
|
method: 'POST',
|
|
405
405
|
body: 'hello',
|
|
406
406
|
headers: {
|
|
407
|
-
'Content-Type': 'very-weird-content-type'
|
|
407
|
+
'Content-Type': 'very-weird-content-type/foo'
|
|
408
408
|
}
|
|
409
409
|
})
|
|
410
410
|
|
|
@@ -89,41 +89,6 @@ test('Should get the body as string /1', async (t) => {
|
|
|
89
89
|
t.assert.strictEqual(await result.text(), 'hello world')
|
|
90
90
|
})
|
|
91
91
|
|
|
92
|
-
test('Should get the body as string /2', async (t) => {
|
|
93
|
-
t.plan(4)
|
|
94
|
-
const fastify = Fastify()
|
|
95
|
-
|
|
96
|
-
fastify.post('/', (req, reply) => {
|
|
97
|
-
reply.send(req.body)
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
fastify.addContentTypeParser('text/plain/test', { parseAs: 'string' }, function (req, body, done) {
|
|
101
|
-
t.assert.ok('called')
|
|
102
|
-
t.assert.ok(typeof body === 'string')
|
|
103
|
-
try {
|
|
104
|
-
const plainText = body
|
|
105
|
-
done(null, plainText)
|
|
106
|
-
} catch (err) {
|
|
107
|
-
err.statusCode = 400
|
|
108
|
-
done(err, undefined)
|
|
109
|
-
}
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
const fastifyServer = await fastify.listen({ port: 0 })
|
|
113
|
-
t.after(() => fastify.close())
|
|
114
|
-
|
|
115
|
-
const result = await fetch(fastifyServer, {
|
|
116
|
-
method: 'POST',
|
|
117
|
-
body: 'hello world',
|
|
118
|
-
headers: {
|
|
119
|
-
'Content-Type': ' text/plain/test '
|
|
120
|
-
}
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
t.assert.strictEqual(result.status, 200)
|
|
124
|
-
t.assert.strictEqual(await result.text(), 'hello world')
|
|
125
|
-
})
|
|
126
|
-
|
|
127
92
|
test('Should get the body as buffer', async (t) => {
|
|
128
93
|
t.plan(4)
|
|
129
94
|
const fastify = Fastify()
|
|
@@ -189,7 +189,7 @@ test('catch all content type parser should not interfere with content type parse
|
|
|
189
189
|
|
|
190
190
|
const assertions = [
|
|
191
191
|
{ body: '{"myKey":"myValue"}', contentType: 'application/json', expected: JSON.stringify({ myKey: 'myValue' }) },
|
|
192
|
-
{ body: 'body', contentType: 'very-weird-content-type', expected: 'body' },
|
|
192
|
+
{ body: 'body', contentType: 'very-weird-content-type/foo', expected: 'body' },
|
|
193
193
|
{ body: 'my text', contentType: 'text/html', expected: 'my texthtml' }
|
|
194
194
|
]
|
|
195
195
|
|
|
@@ -1416,7 +1416,7 @@ test('Schema validation will not be bypass by different content type', async t =
|
|
|
1416
1416
|
t.after(() => fastify.close())
|
|
1417
1417
|
const address = fastify.listeningOrigin
|
|
1418
1418
|
|
|
1419
|
-
|
|
1419
|
+
let found = await fetch(address, {
|
|
1420
1420
|
method: 'POST',
|
|
1421
1421
|
url: '/',
|
|
1422
1422
|
headers: {
|
|
@@ -1424,10 +1424,10 @@ test('Schema validation will not be bypass by different content type', async t =
|
|
|
1424
1424
|
},
|
|
1425
1425
|
body: JSON.stringify({ foo: 'string' })
|
|
1426
1426
|
})
|
|
1427
|
-
t.assert.strictEqual(
|
|
1428
|
-
await
|
|
1427
|
+
t.assert.strictEqual(found.status, 200)
|
|
1428
|
+
await found.bytes()
|
|
1429
1429
|
|
|
1430
|
-
|
|
1430
|
+
found = await fetch(address, {
|
|
1431
1431
|
method: 'POST',
|
|
1432
1432
|
url: '/',
|
|
1433
1433
|
headers: {
|
|
@@ -1435,10 +1435,10 @@ test('Schema validation will not be bypass by different content type', async t =
|
|
|
1435
1435
|
},
|
|
1436
1436
|
body: JSON.stringify({ foo: 'string' })
|
|
1437
1437
|
})
|
|
1438
|
-
t.assert.strictEqual(
|
|
1439
|
-
await
|
|
1438
|
+
t.assert.strictEqual(found.status, 200)
|
|
1439
|
+
await found.bytes()
|
|
1440
1440
|
|
|
1441
|
-
|
|
1441
|
+
found = await fetch(address, {
|
|
1442
1442
|
method: 'POST',
|
|
1443
1443
|
url: '/',
|
|
1444
1444
|
headers: {
|
|
@@ -1446,10 +1446,10 @@ test('Schema validation will not be bypass by different content type', async t =
|
|
|
1446
1446
|
},
|
|
1447
1447
|
body: JSON.stringify({ foo: 'string' })
|
|
1448
1448
|
})
|
|
1449
|
-
t.assert.strictEqual(
|
|
1450
|
-
await
|
|
1449
|
+
t.assert.strictEqual(found.status, 200)
|
|
1450
|
+
await found.bytes()
|
|
1451
1451
|
|
|
1452
|
-
|
|
1452
|
+
found = await fetch(address, {
|
|
1453
1453
|
method: 'POST',
|
|
1454
1454
|
url: '/',
|
|
1455
1455
|
headers: {
|
|
@@ -1457,10 +1457,10 @@ test('Schema validation will not be bypass by different content type', async t =
|
|
|
1457
1457
|
},
|
|
1458
1458
|
body: JSON.stringify({ invalid: 'string' })
|
|
1459
1459
|
})
|
|
1460
|
-
t.assert.strictEqual(
|
|
1461
|
-
t.assert.strictEqual((await
|
|
1460
|
+
t.assert.strictEqual(found.status, 400)
|
|
1461
|
+
t.assert.strictEqual((await found.json()).code, 'FST_ERR_VALIDATION')
|
|
1462
1462
|
|
|
1463
|
-
|
|
1463
|
+
found = await fetch(address, {
|
|
1464
1464
|
method: 'POST',
|
|
1465
1465
|
url: '/',
|
|
1466
1466
|
headers: {
|
|
@@ -1468,10 +1468,10 @@ test('Schema validation will not be bypass by different content type', async t =
|
|
|
1468
1468
|
},
|
|
1469
1469
|
body: JSON.stringify({ invalid: 'string' })
|
|
1470
1470
|
})
|
|
1471
|
-
t.assert.strictEqual(
|
|
1472
|
-
t.assert.strictEqual((await
|
|
1471
|
+
t.assert.strictEqual(found.status, 400)
|
|
1472
|
+
t.assert.strictEqual((await found.json()).code, 'FST_ERR_VALIDATION')
|
|
1473
1473
|
|
|
1474
|
-
|
|
1474
|
+
found = await fetch(address, {
|
|
1475
1475
|
method: 'POST',
|
|
1476
1476
|
url: '/',
|
|
1477
1477
|
headers: {
|
|
@@ -1479,10 +1479,10 @@ test('Schema validation will not be bypass by different content type', async t =
|
|
|
1479
1479
|
},
|
|
1480
1480
|
body: JSON.stringify({ invalid: 'string' })
|
|
1481
1481
|
})
|
|
1482
|
-
t.assert.strictEqual(
|
|
1483
|
-
t.assert.strictEqual((await
|
|
1482
|
+
t.assert.strictEqual(found.status, 400)
|
|
1483
|
+
t.assert.strictEqual((await found.json()).code, 'FST_ERR_VALIDATION')
|
|
1484
1484
|
|
|
1485
|
-
|
|
1485
|
+
found = await fetch(address, {
|
|
1486
1486
|
method: 'POST',
|
|
1487
1487
|
url: '/',
|
|
1488
1488
|
headers: {
|
|
@@ -1490,10 +1490,10 @@ test('Schema validation will not be bypass by different content type', async t =
|
|
|
1490
1490
|
},
|
|
1491
1491
|
body: JSON.stringify({ invalid: 'string' })
|
|
1492
1492
|
})
|
|
1493
|
-
t.assert.strictEqual(
|
|
1494
|
-
t.assert.strictEqual((await
|
|
1493
|
+
t.assert.strictEqual(found.status, 415)
|
|
1494
|
+
t.assert.strictEqual((await found.json()).code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE')
|
|
1495
1495
|
|
|
1496
|
-
|
|
1496
|
+
found = await fetch(address, {
|
|
1497
1497
|
method: 'POST',
|
|
1498
1498
|
url: '/',
|
|
1499
1499
|
headers: {
|
|
@@ -1501,10 +1501,10 @@ test('Schema validation will not be bypass by different content type', async t =
|
|
|
1501
1501
|
},
|
|
1502
1502
|
body: JSON.stringify({ invalid: 'string' })
|
|
1503
1503
|
})
|
|
1504
|
-
t.assert.strictEqual(
|
|
1505
|
-
t.assert.strictEqual((await
|
|
1504
|
+
t.assert.strictEqual(found.status, 415)
|
|
1505
|
+
t.assert.strictEqual((await found.json()).code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE')
|
|
1506
1506
|
|
|
1507
|
-
|
|
1507
|
+
found = await fetch(address, {
|
|
1508
1508
|
method: 'POST',
|
|
1509
1509
|
url: '/',
|
|
1510
1510
|
headers: {
|
|
@@ -1512,10 +1512,10 @@ test('Schema validation will not be bypass by different content type', async t =
|
|
|
1512
1512
|
},
|
|
1513
1513
|
body: JSON.stringify({ invalid: 'string' })
|
|
1514
1514
|
})
|
|
1515
|
-
t.assert.strictEqual(
|
|
1516
|
-
t.assert.strictEqual((await
|
|
1515
|
+
t.assert.strictEqual(found.status, 415)
|
|
1516
|
+
t.assert.strictEqual((await found.json()).code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE')
|
|
1517
1517
|
|
|
1518
|
-
|
|
1518
|
+
found = await fetch(address, {
|
|
1519
1519
|
method: 'POST',
|
|
1520
1520
|
url: '/',
|
|
1521
1521
|
headers: {
|
|
@@ -1523,10 +1523,10 @@ test('Schema validation will not be bypass by different content type', async t =
|
|
|
1523
1523
|
},
|
|
1524
1524
|
body: JSON.stringify({ invalid: 'string' })
|
|
1525
1525
|
})
|
|
1526
|
-
t.assert.strictEqual(
|
|
1527
|
-
t.assert.strictEqual((await
|
|
1526
|
+
t.assert.strictEqual(found.status, 400)
|
|
1527
|
+
t.assert.strictEqual((await found.json()).code, 'FST_ERR_VALIDATION')
|
|
1528
1528
|
|
|
1529
|
-
|
|
1529
|
+
found = await fetch(address, {
|
|
1530
1530
|
method: 'POST',
|
|
1531
1531
|
url: '/',
|
|
1532
1532
|
headers: {
|
|
@@ -1534,6 +1534,39 @@ test('Schema validation will not be bypass by different content type', async t =
|
|
|
1534
1534
|
},
|
|
1535
1535
|
body: JSON.stringify({ invalid: 'string' })
|
|
1536
1536
|
})
|
|
1537
|
-
t.assert.strictEqual(
|
|
1538
|
-
t.assert.strictEqual((await
|
|
1537
|
+
t.assert.strictEqual(found.status, 400)
|
|
1538
|
+
t.assert.strictEqual((await found.json()).code, 'FST_ERR_VALIDATION')
|
|
1539
|
+
|
|
1540
|
+
found = await fetch(address, {
|
|
1541
|
+
method: 'POST',
|
|
1542
|
+
url: '/',
|
|
1543
|
+
headers: {
|
|
1544
|
+
'content-type': 'ApPlIcAtIoN/JsOn\ta'
|
|
1545
|
+
},
|
|
1546
|
+
body: JSON.stringify({ invalid: 'string' })
|
|
1547
|
+
})
|
|
1548
|
+
t.assert.strictEqual(found.status, 415)
|
|
1549
|
+
t.assert.strictEqual((await found.json()).code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE')
|
|
1550
|
+
|
|
1551
|
+
found = await fetch(address, {
|
|
1552
|
+
method: 'POST',
|
|
1553
|
+
url: '/',
|
|
1554
|
+
headers: {
|
|
1555
|
+
'content-type': 'ApPlIcAtIoN/JsOn\ta; charset=utf-8'
|
|
1556
|
+
},
|
|
1557
|
+
body: JSON.stringify({ invalid: 'string' })
|
|
1558
|
+
})
|
|
1559
|
+
t.assert.strictEqual(found.status, 415)
|
|
1560
|
+
t.assert.strictEqual((await found.json()).code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE')
|
|
1561
|
+
|
|
1562
|
+
found = await fetch(address, {
|
|
1563
|
+
method: 'POST',
|
|
1564
|
+
url: '/',
|
|
1565
|
+
headers: {
|
|
1566
|
+
'content-type': 'application/ json'
|
|
1567
|
+
},
|
|
1568
|
+
body: JSON.stringify({ invalid: 'string' })
|
|
1569
|
+
})
|
|
1570
|
+
t.assert.strictEqual(found.status, 415)
|
|
1571
|
+
t.assert.strictEqual((await found.json()).code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE')
|
|
1539
1572
|
})
|