Skip to content

Commit f0004f3

Browse files
feat: add headers to edge functions manifest validation (#6542)
* feat: add `headers` to edge functions manifest validation * chore: formatting * refactor: move headers to route definition
1 parent ea236c6 commit f0004f3

File tree

3 files changed

+274
-0
lines changed

3 files changed

+274
-0
lines changed

packages/edge-bundler/node/validation/manifest/__snapshots__/index.test.ts.snap

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,72 @@ REQUIRED must have required property 'format'
5353
6 | ],]
5454
`;
5555
56+
exports[`headers > should throw on additional property in headers 1`] = `
57+
[ManifestValidationError: Validation of Edge Functions manifest failed
58+
ADDTIONAL PROPERTY must NOT have additional properties
59+
60+
33 | "x-custom-header": {
61+
34 | "style": "exists",
62+
> 35 | "foo": "bar"
63+
| ^^^^^ 😲 foo is not expected to be here!
64+
36 | }
65+
37 | }
66+
38 | }]
67+
`;
68+
69+
exports[`headers > should throw on invalid pattern format 1`] = `
70+
[ManifestValidationError: Validation of Edge Functions manifest failed
71+
FORMAT must match format "regexPattern"
72+
73+
33 | "x-custom-header": {
74+
34 | "style": "regex",
75+
> 35 | "pattern": "/^Bearer .+/"
76+
| ^^^^^^^^^^^^^^ 👈🏽 format must match format "regexPattern"
77+
36 | }
78+
37 | }
79+
38 | }]
80+
`;
81+
82+
exports[`headers > should throw on invalid style value 1`] = `
83+
[ManifestValidationError: Validation of Edge Functions manifest failed
84+
ENUM must be equal to one of the allowed values
85+
(exists, missing, regex)
86+
87+
32 | "headers": {
88+
33 | "x-custom-header": {
89+
> 34 | "style": "invalid"
90+
| ^^^^^^^^^ 👈🏽 Unexpected value, should be equal to one of the allowed values
91+
35 | }
92+
36 | }
93+
37 | }]
94+
`;
95+
96+
exports[`headers > should throw on missing style property 1`] = `
97+
[ManifestValidationError: Validation of Edge Functions manifest failed
98+
REQUIRED must have required property 'style'
99+
100+
31 | "bundler_version": "1.6.0",
101+
32 | "headers": {
102+
> 33 | "x-custom-header": {
103+
| ^ ☹️ style is missing here!
104+
34 | "pattern": "^Bearer .+"
105+
35 | }
106+
36 | }]
107+
`;
108+
109+
exports[`headers > should throw when style is regex but pattern is missing 1`] = `
110+
[ManifestValidationError: Validation of Edge Functions manifest failed
111+
REQUIRED must have required property 'pattern'
112+
113+
31 | "bundler_version": "1.6.0",
114+
32 | "headers": {
115+
> 33 | "x-custom-header": {
116+
| ^ ☹️ pattern is missing here!
117+
34 | "style": "regex"
118+
35 | }
119+
36 | }]
120+
`;
121+
56122
exports[`import map URL > should throw on wrong type 1`] = `
57123
[ManifestValidationError: Validation of Edge Functions manifest failed
58124
TYPE must be string
@@ -159,6 +225,72 @@ REQUIRED must have required property 'pattern'
159225
12 | "generator": "@netlify/[email protected]"]
160226
`;
161227
228+
exports[`route headers > should throw on additional property in headers 1`] = `
229+
[ManifestValidationError: Validation of Edge Functions manifest failed
230+
ADDTIONAL PROPERTY must NOT have additional properties
231+
232+
15 | "x-custom-header": {
233+
16 | "style": "exists",
234+
> 17 | "foo": "bar"
235+
| ^^^^^ 😲 foo is not expected to be here!
236+
18 | }
237+
19 | }
238+
20 | }]
239+
`;
240+
241+
exports[`route headers > should throw on invalid pattern format 1`] = `
242+
[ManifestValidationError: Validation of Edge Functions manifest failed
243+
FORMAT must match format "regexPattern"
244+
245+
15 | "x-custom-header": {
246+
16 | "style": "regex",
247+
> 17 | "pattern": "/^Bearer .+/"
248+
| ^^^^^^^^^^^^^^ 👈🏽 format must match format "regexPattern"
249+
18 | }
250+
19 | }
251+
20 | }]
252+
`;
253+
254+
exports[`route headers > should throw on invalid style value 1`] = `
255+
[ManifestValidationError: Validation of Edge Functions manifest failed
256+
ENUM must be equal to one of the allowed values
257+
(exists, missing, regex)
258+
259+
14 | "headers": {
260+
15 | "x-custom-header": {
261+
> 16 | "style": "invalid"
262+
| ^^^^^^^^^ 👈🏽 Unexpected value, should be equal to one of the allowed values
263+
17 | }
264+
18 | }
265+
19 | }]
266+
`;
267+
268+
exports[`route headers > should throw on missing style property 1`] = `
269+
[ManifestValidationError: Validation of Edge Functions manifest failed
270+
REQUIRED must have required property 'style'
271+
272+
13 | "generator": "@netlify/[email protected]",
273+
14 | "headers": {
274+
> 15 | "x-custom-header": {
275+
| ^ ☹️ style is missing here!
276+
16 | "pattern": "^Bearer .+$"
277+
17 | }
278+
18 | }]
279+
`;
280+
281+
exports[`route headers > should throw when style is regex but pattern is missing 1`] = `
282+
[ManifestValidationError: Validation of Edge Functions manifest failed
283+
REQUIRED must have required property 'pattern'
284+
285+
13 | "generator": "@netlify/[email protected]",
286+
14 | "headers": {
287+
> 15 | "x-custom-header": {
288+
| ^ ☹️ pattern is missing here!
289+
16 | "style": "regex"
290+
17 | }
291+
18 | }]
292+
`;
293+
162294
exports[`should show multiple errors 1`] = `
163295
[ManifestValidationError: Validation of Edge Functions manifest failed
164296
ADDTIONAL PROPERTY must NOT have additional properties

packages/edge-bundler/node/validation/manifest/index.test.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,114 @@ describe('import map URL', () => {
180180
expect(() => validateManifest(manifest)).toThrowErrorMatchingSnapshot()
181181
})
182182
})
183+
184+
describe('route headers', () => {
185+
test('should accept valid headers with exists style', () => {
186+
const manifest = getBaseManifest()
187+
manifest.routes[0].headers = {
188+
'x-custom-header': {
189+
style: 'exists',
190+
},
191+
}
192+
193+
expect(() => validateManifest(manifest)).not.toThrowError()
194+
})
195+
196+
test('should accept valid headers with missing style', () => {
197+
const manifest = getBaseManifest()
198+
manifest.routes[0].headers = {
199+
'x-custom-header': {
200+
style: 'missing',
201+
},
202+
}
203+
204+
expect(() => validateManifest(manifest)).not.toThrowError()
205+
})
206+
207+
test('should accept valid headers with regex style and pattern', () => {
208+
const manifest = getBaseManifest()
209+
manifest.routes[0].headers = {
210+
'x-custom-header': {
211+
style: 'regex',
212+
pattern: '^Bearer .+$',
213+
},
214+
}
215+
216+
expect(() => validateManifest(manifest)).not.toThrowError()
217+
})
218+
219+
test('should throw on missing style property', () => {
220+
const manifest = getBaseManifest()
221+
manifest.routes[0].headers = {
222+
'x-custom-header': {
223+
pattern: '^Bearer .+$',
224+
},
225+
}
226+
227+
expect(() => validateManifest(manifest)).toThrowErrorMatchingSnapshot()
228+
})
229+
230+
test('should throw on invalid style value', () => {
231+
const manifest = getBaseManifest()
232+
manifest.routes[0].headers = {
233+
'x-custom-header': {
234+
style: 'invalid',
235+
},
236+
}
237+
238+
expect(() => validateManifest(manifest)).toThrowErrorMatchingSnapshot()
239+
})
240+
241+
test('should throw when style is regex but pattern is missing', () => {
242+
const manifest = getBaseManifest()
243+
manifest.routes[0].headers = {
244+
'x-custom-header': {
245+
style: 'regex',
246+
},
247+
}
248+
249+
expect(() => validateManifest(manifest)).toThrowErrorMatchingSnapshot()
250+
})
251+
252+
test('should throw on invalid pattern format', () => {
253+
const manifest = getBaseManifest()
254+
manifest.routes[0].headers = {
255+
'x-custom-header': {
256+
style: 'regex',
257+
pattern: '/^Bearer .+/',
258+
},
259+
}
260+
261+
expect(() => validateManifest(manifest)).toThrowErrorMatchingSnapshot()
262+
})
263+
264+
test('should throw on additional property in headers', () => {
265+
const manifest = getBaseManifest()
266+
manifest.routes[0].headers = {
267+
'x-custom-header': {
268+
style: 'exists',
269+
foo: 'bar',
270+
},
271+
}
272+
273+
expect(() => validateManifest(manifest)).toThrowErrorMatchingSnapshot()
274+
})
275+
276+
test('should accept multiple headers with different styles', () => {
277+
const manifest = getBaseManifest()
278+
manifest.routes[0].headers = {
279+
'x-exists-header': {
280+
style: 'exists',
281+
},
282+
'x-missing-header': {
283+
style: 'missing',
284+
},
285+
authorization: {
286+
style: 'regex',
287+
pattern: '^Bearer [a-zA-Z0-9]+$',
288+
},
289+
}
290+
291+
expect(() => validateManifest(manifest)).not.toThrowError()
292+
})
293+
})

packages/edge-bundler/node/validation/manifest/schema.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,36 @@ const excludedPatternsSchema = {
1818
},
1919
}
2020

21+
const headersSchema = {
22+
type: 'object',
23+
patternProperties: {
24+
'.*': {
25+
type: 'object',
26+
required: ['style'],
27+
properties: {
28+
pattern: {
29+
type: 'string',
30+
format: 'regexPattern',
31+
},
32+
style: {
33+
type: 'string',
34+
enum: ['exists', 'missing', 'regex'],
35+
},
36+
},
37+
additionalProperties: false,
38+
if: {
39+
properties: {
40+
style: { const: 'regex' },
41+
},
42+
},
43+
then: {
44+
required: ['pattern'],
45+
},
46+
},
47+
},
48+
additionalProperties: false,
49+
}
50+
2151
const routesSchema = {
2252
type: 'object',
2353
required: ['function', 'pattern'],
@@ -36,6 +66,7 @@ const routesSchema = {
3666
type: 'array',
3767
items: { type: 'string', enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'] },
3868
},
69+
headers: headersSchema,
3970
},
4071
additionalProperties: false,
4172
}

0 commit comments

Comments
 (0)