diff --git a/packages/edge-bundler/node/validation/manifest/__snapshots__/index.test.ts.snap b/packages/edge-bundler/node/validation/manifest/__snapshots__/index.test.ts.snap index 47d5ffdf1d..3097575ee6 100644 --- a/packages/edge-bundler/node/validation/manifest/__snapshots__/index.test.ts.snap +++ b/packages/edge-bundler/node/validation/manifest/__snapshots__/index.test.ts.snap @@ -53,6 +53,72 @@ REQUIRED must have required property 'format' 6 | ],] `; +exports[`headers > should throw on additional property in headers 1`] = ` +[ManifestValidationError: Validation of Edge Functions manifest failed +ADDTIONAL PROPERTY must NOT have additional properties + + 33 | "x-custom-header": { + 34 | "style": "exists", +> 35 | "foo": "bar" + | ^^^^^ 😲 foo is not expected to be here! + 36 | } + 37 | } + 38 | }] +`; + +exports[`headers > should throw on invalid pattern format 1`] = ` +[ManifestValidationError: Validation of Edge Functions manifest failed +FORMAT must match format "regexPattern" + + 33 | "x-custom-header": { + 34 | "style": "regex", +> 35 | "pattern": "/^Bearer .+/" + | ^^^^^^^^^^^^^^ 👈đŸŊ format must match format "regexPattern" + 36 | } + 37 | } + 38 | }] +`; + +exports[`headers > should throw on invalid style value 1`] = ` +[ManifestValidationError: Validation of Edge Functions manifest failed +ENUM must be equal to one of the allowed values +(exists, missing, regex) + + 32 | "headers": { + 33 | "x-custom-header": { +> 34 | "style": "invalid" + | ^^^^^^^^^ 👈đŸŊ Unexpected value, should be equal to one of the allowed values + 35 | } + 36 | } + 37 | }] +`; + +exports[`headers > should throw on missing style property 1`] = ` +[ManifestValidationError: Validation of Edge Functions manifest failed +REQUIRED must have required property 'style' + + 31 | "bundler_version": "1.6.0", + 32 | "headers": { +> 33 | "x-custom-header": { + | ^ â˜šī¸ style is missing here! + 34 | "pattern": "^Bearer .+" + 35 | } + 36 | }] +`; + +exports[`headers > should throw when style is regex but pattern is missing 1`] = ` +[ManifestValidationError: Validation of Edge Functions manifest failed +REQUIRED must have required property 'pattern' + + 31 | "bundler_version": "1.6.0", + 32 | "headers": { +> 33 | "x-custom-header": { + | ^ â˜šī¸ pattern is missing here! + 34 | "style": "regex" + 35 | } + 36 | }] +`; + exports[`import map URL > should throw on wrong type 1`] = ` [ManifestValidationError: Validation of Edge Functions manifest failed TYPE must be string @@ -159,6 +225,72 @@ REQUIRED must have required property 'pattern' 12 | "generator": "@netlify/fake-plugin@1.0.0"] `; +exports[`route headers > should throw on additional property in headers 1`] = ` +[ManifestValidationError: Validation of Edge Functions manifest failed +ADDTIONAL PROPERTY must NOT have additional properties + + 15 | "x-custom-header": { + 16 | "style": "exists", +> 17 | "foo": "bar" + | ^^^^^ 😲 foo is not expected to be here! + 18 | } + 19 | } + 20 | }] +`; + +exports[`route headers > should throw on invalid pattern format 1`] = ` +[ManifestValidationError: Validation of Edge Functions manifest failed +FORMAT must match format "regexPattern" + + 15 | "x-custom-header": { + 16 | "style": "regex", +> 17 | "pattern": "/^Bearer .+/" + | ^^^^^^^^^^^^^^ 👈đŸŊ format must match format "regexPattern" + 18 | } + 19 | } + 20 | }] +`; + +exports[`route headers > should throw on invalid style value 1`] = ` +[ManifestValidationError: Validation of Edge Functions manifest failed +ENUM must be equal to one of the allowed values +(exists, missing, regex) + + 14 | "headers": { + 15 | "x-custom-header": { +> 16 | "style": "invalid" + | ^^^^^^^^^ 👈đŸŊ Unexpected value, should be equal to one of the allowed values + 17 | } + 18 | } + 19 | }] +`; + +exports[`route headers > should throw on missing style property 1`] = ` +[ManifestValidationError: Validation of Edge Functions manifest failed +REQUIRED must have required property 'style' + + 13 | "generator": "@netlify/fake-plugin@1.0.0", + 14 | "headers": { +> 15 | "x-custom-header": { + | ^ â˜šī¸ style is missing here! + 16 | "pattern": "^Bearer .+$" + 17 | } + 18 | }] +`; + +exports[`route headers > should throw when style is regex but pattern is missing 1`] = ` +[ManifestValidationError: Validation of Edge Functions manifest failed +REQUIRED must have required property 'pattern' + + 13 | "generator": "@netlify/fake-plugin@1.0.0", + 14 | "headers": { +> 15 | "x-custom-header": { + | ^ â˜šī¸ pattern is missing here! + 16 | "style": "regex" + 17 | } + 18 | }] +`; + exports[`should show multiple errors 1`] = ` [ManifestValidationError: Validation of Edge Functions manifest failed ADDTIONAL PROPERTY must NOT have additional properties diff --git a/packages/edge-bundler/node/validation/manifest/index.test.ts b/packages/edge-bundler/node/validation/manifest/index.test.ts index 6a594799bd..8f0b26f3ae 100644 --- a/packages/edge-bundler/node/validation/manifest/index.test.ts +++ b/packages/edge-bundler/node/validation/manifest/index.test.ts @@ -180,3 +180,114 @@ describe('import map URL', () => { expect(() => validateManifest(manifest)).toThrowErrorMatchingSnapshot() }) }) + +describe('route headers', () => { + test('should accept valid headers with exists style', () => { + const manifest = getBaseManifest() + manifest.routes[0].headers = { + 'x-custom-header': { + style: 'exists', + }, + } + + expect(() => validateManifest(manifest)).not.toThrowError() + }) + + test('should accept valid headers with missing style', () => { + const manifest = getBaseManifest() + manifest.routes[0].headers = { + 'x-custom-header': { + style: 'missing', + }, + } + + expect(() => validateManifest(manifest)).not.toThrowError() + }) + + test('should accept valid headers with regex style and pattern', () => { + const manifest = getBaseManifest() + manifest.routes[0].headers = { + 'x-custom-header': { + style: 'regex', + pattern: '^Bearer .+$', + }, + } + + expect(() => validateManifest(manifest)).not.toThrowError() + }) + + test('should throw on missing style property', () => { + const manifest = getBaseManifest() + manifest.routes[0].headers = { + 'x-custom-header': { + pattern: '^Bearer .+$', + }, + } + + expect(() => validateManifest(manifest)).toThrowErrorMatchingSnapshot() + }) + + test('should throw on invalid style value', () => { + const manifest = getBaseManifest() + manifest.routes[0].headers = { + 'x-custom-header': { + style: 'invalid', + }, + } + + expect(() => validateManifest(manifest)).toThrowErrorMatchingSnapshot() + }) + + test('should throw when style is regex but pattern is missing', () => { + const manifest = getBaseManifest() + manifest.routes[0].headers = { + 'x-custom-header': { + style: 'regex', + }, + } + + expect(() => validateManifest(manifest)).toThrowErrorMatchingSnapshot() + }) + + test('should throw on invalid pattern format', () => { + const manifest = getBaseManifest() + manifest.routes[0].headers = { + 'x-custom-header': { + style: 'regex', + pattern: '/^Bearer .+/', + }, + } + + expect(() => validateManifest(manifest)).toThrowErrorMatchingSnapshot() + }) + + test('should throw on additional property in headers', () => { + const manifest = getBaseManifest() + manifest.routes[0].headers = { + 'x-custom-header': { + style: 'exists', + foo: 'bar', + }, + } + + expect(() => validateManifest(manifest)).toThrowErrorMatchingSnapshot() + }) + + test('should accept multiple headers with different styles', () => { + const manifest = getBaseManifest() + manifest.routes[0].headers = { + 'x-exists-header': { + style: 'exists', + }, + 'x-missing-header': { + style: 'missing', + }, + authorization: { + style: 'regex', + pattern: '^Bearer [a-zA-Z0-9]+$', + }, + } + + expect(() => validateManifest(manifest)).not.toThrowError() + }) +}) diff --git a/packages/edge-bundler/node/validation/manifest/schema.ts b/packages/edge-bundler/node/validation/manifest/schema.ts index d244f4999d..7b6cc1660b 100644 --- a/packages/edge-bundler/node/validation/manifest/schema.ts +++ b/packages/edge-bundler/node/validation/manifest/schema.ts @@ -18,6 +18,36 @@ const excludedPatternsSchema = { }, } +const headersSchema = { + type: 'object', + patternProperties: { + '.*': { + type: 'object', + required: ['style'], + properties: { + pattern: { + type: 'string', + format: 'regexPattern', + }, + style: { + type: 'string', + enum: ['exists', 'missing', 'regex'], + }, + }, + additionalProperties: false, + if: { + properties: { + style: { const: 'regex' }, + }, + }, + then: { + required: ['pattern'], + }, + }, + }, + additionalProperties: false, +} + const routesSchema = { type: 'object', required: ['function', 'pattern'], @@ -36,6 +66,7 @@ const routesSchema = { type: 'array', items: { type: 'string', enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'] }, }, + headers: headersSchema, }, additionalProperties: false, }