Skip to content

Commit

Permalink
feat: add manifest validation (#208)
Browse files Browse the repository at this point in the history
* feat: add manifest validation

* chore: disable color in test
  • Loading branch information
danez committed Nov 21, 2022
1 parent 03ee0b4 commit 17ee035
Show file tree
Hide file tree
Showing 13 changed files with 1,342 additions and 803 deletions.
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module.exports = {
},
rules: {
complexity: 'off',
'import/extensions': 'off',
'max-lines': 'off',
'max-statements': 'off',
'node/no-missing-import': 'off',
Expand Down
11 changes: 7 additions & 4 deletions node/bundle.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
interface Bundle {
export enum BundleFormat {
ESZIP2 = 'eszip2',
JS = 'js',
}

export interface Bundle {
extension: string
format: string
format: BundleFormat
hash: string
}

export type { Bundle }
4 changes: 2 additions & 2 deletions node/formats/eszip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { join } from 'path'

import type { WriteStage2Options } from '../../shared/stage2.js'
import { DenoBridge } from '../bridge.js'
import type { Bundle } from '../bundle.js'
import { Bundle, BundleFormat } from '../bundle.js'
import { wrapBundleError } from '../bundle_error.js'
import { EdgeFunction } from '../edge_function.js'
import { FeatureFlags } from '../feature_flags.js'
Expand Down Expand Up @@ -58,7 +58,7 @@ const bundleESZIP = async ({

const hash = await getFileHash(destPath)

return { extension, format: 'eszip2', hash }
return { extension, format: BundleFormat.ESZIP2, hash }
}

const getESZIPPaths = () => {
Expand Down
4 changes: 2 additions & 2 deletions node/formats/javascript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { pathToFileURL } from 'url'
import { deleteAsync } from 'del'

import { DenoBridge } from '../bridge.js'
import type { Bundle } from '../bundle.js'
import { Bundle, BundleFormat } from '../bundle.js'
import { wrapBundleError } from '../bundle_error.js'
import { EdgeFunction } from '../edge_function.js'
import { ImportMap } from '../import_map.js'
Expand Down Expand Up @@ -52,7 +52,7 @@ const bundleJS = async ({

const hash = await getFileHash(jsBundlePath)

return { extension, format: 'js', hash }
return { extension, format: BundleFormat.JS, hash }
}

const defaultFormatExportTypeError: FormatFunction = (name) =>
Expand Down
1 change: 1 addition & 0 deletions node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export { DenoBridge } from './bridge.js'
export { findFunctions as find } from './finder.js'
export { generateManifest } from './manifest.js'
export { serve } from './server/server.js'
export { validateManifest, ManifestValidationError } from './validation/manifest/index.js'
13 changes: 7 additions & 6 deletions node/manifest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,18 @@ import { env } from 'process'

import { test, expect } from 'vitest'

import { BundleFormat } from './bundle.js'
import { generateManifest } from './manifest.js'

test('Generates a manifest with different bundles', () => {
const bundle1 = {
extension: '.ext1',
format: 'format1',
format: BundleFormat.ESZIP2,
hash: '123456',
}
const bundle2 = {
extension: '.ext2',
format: 'format2',
format: BundleFormat.ESZIP2,
hash: '654321',
}
const functions = [{ name: 'func-1', path: '/path/to/func-1.ts' }]
Expand Down Expand Up @@ -53,7 +54,7 @@ test('Generates a manifest with display names', () => {
test('Excludes functions for which there are function files but no matching config declarations', () => {
const bundle1 = {
extension: '.ext2',
format: 'format1',
format: BundleFormat.ESZIP2,
hash: '123456',
}
const functions = [
Expand All @@ -71,7 +72,7 @@ test('Excludes functions for which there are function files but no matching conf
test('Excludes functions for which there are config declarations but no matching function files', () => {
const bundle1 = {
extension: '.ext2',
format: 'format1',
format: BundleFormat.ESZIP2,
hash: '123456',
}
const functions = [{ name: 'func-2', path: '/path/to/func-2.ts' }]
Expand Down Expand Up @@ -101,12 +102,12 @@ test('Generates a manifest without bundles', () => {
test('Generates a manifest with pre and post-cache routes', () => {
const bundle1 = {
extension: '.ext1',
format: 'format1',
format: BundleFormat.ESZIP2,
hash: '123456',
}
const bundle2 = {
extension: '.ext2',
format: 'format2',
format: BundleFormat.ESZIP2,
hash: '654321',
}
const functions = [
Expand Down
176 changes: 176 additions & 0 deletions node/validation/manifest/__snapshots__/index.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// Vitest Snapshot v1

exports[`bundle > should throw on additional property in bundle 1`] = `
"Validation of Edge Functions manifest failed
ADDTIONAL PROPERTY must NOT have additional properties
4 | \\"asset\\": \\"f35baff44129a8f6be7db68590b2efd86ed4ba29000e2edbcaddc5d620d7d043.js\\",
5 | \\"format\\": \\"js\\",
> 6 | \\"foo\\": \\"bar\\"
| ^^^^^ 😲 foo is not expected to be here!
7 | }
8 | ],
9 | \\"routes\\": ["
`;

exports[`bundle > should throw on invalid format 1`] = `
"Validation of Edge Functions manifest failed
ENUM must be equal to one of the allowed values
(eszip2, js)
3 | {
4 | \\"asset\\": \\"f35baff44129a8f6be7db68590b2efd86ed4ba29000e2edbcaddc5d620d7d043.js\\",
> 5 | \\"format\\": \\"foo\\"
| ^^^^^ 👈🏽 Unexpected value, should be equal to one of the allowed values
6 | }
7 | ],
8 | \\"routes\\": ["
`;

exports[`bundle > should throw on missing asset 1`] = `
"Validation of Edge Functions manifest failed
REQUIRED must have required property 'asset'
1 | {
2 | \\"bundles\\": [
> 3 | {
| ^ ☹️ asset is missing here!
4 | \\"format\\": \\"js\\"
5 | }
6 | ],"
`;
exports[`bundle > should throw on missing format 1`] = `
"Validation of Edge Functions manifest failed
REQUIRED must have required property 'format'
1 | {
2 | \\"bundles\\": [
> 3 | {
| ^ ☹️ format is missing here!
4 | \\"asset\\": \\"f35baff44129a8f6be7db68590b2efd86ed4ba29000e2edbcaddc5d620d7d043.js\\"
5 | }
6 | ],"
`;
exports[`layers > should throw on additional property 1`] = `
"Validation of Edge Functions manifest failed
ADDTIONAL PROPERTY must NOT have additional properties
25 | \\"name\\": \\"name\\",
26 | \\"local\\": \\"local\\",
> 27 | \\"foo\\": \\"bar\\"
| ^^^^^ 😲 foo is not expected to be here!
28 | }
29 | ],
30 | \\"bundler_version\\": \\"1.6.0\\""
`;
exports[`layers > should throw on missing flag 1`] = `
"Validation of Edge Functions manifest failed
REQUIRED must have required property 'flag'
21 | ],
22 | \\"layers\\": [
> 23 | {
| ^ ☹️ flag is missing here!
24 | \\"name\\": \\"name\\",
25 | \\"local\\": \\"local\\"
26 | }"
`;
exports[`layers > should throw on missing name 1`] = `
"Validation of Edge Functions manifest failed
REQUIRED must have required property 'name'
21 | ],
22 | \\"layers\\": [
> 23 | {
| ^ ☹️ name is missing here!
24 | \\"flag\\": \\"flag\\",
25 | \\"local\\": \\"local\\"
26 | }"
`;
exports[`route > should throw on additional property 1`] = `
"Validation of Edge Functions manifest failed
ADDTIONAL PROPERTY must NOT have additional properties
11 | \\"function\\": \\"hello\\",
12 | \\"pattern\\": \\"^/hello/?$\\",
> 13 | \\"foo\\": \\"bar\\"
| ^^^^^ 😲 foo is not expected to be here!
14 | }
15 | ],
16 | \\"post_cache_routes\\": ["
`;
exports[`route > should throw on invalid pattern 1`] = `
"Validation of Edge Functions manifest failed
FORMAT pattern needs to be a regex that starts with ^ and ends with $ without any additional slashes before and afterwards
10 | \\"name\\": \\"name\\",
11 | \\"function\\": \\"hello\\",
> 12 | \\"pattern\\": \\"/^/hello/?$/\\"
| ^^^^^^^^^^^^^^ 👈🏽 format pattern needs to be a regex that starts with ^ and ends with $ without any additional slashes before and afterwards
13 | }
14 | ],
15 | \\"post_cache_routes\\": ["
`;
exports[`route > should throw on missing function 1`] = `
"Validation of Edge Functions manifest failed
REQUIRED must have required property 'function'
7 | ],
8 | \\"routes\\": [
> 9 | {
| ^ ☹️ function is missing here!
10 | \\"name\\": \\"name\\",
11 | \\"pattern\\": \\"^/hello/?$\\"
12 | }"
`;
exports[`route > should throw on missing pattern 1`] = `
"Validation of Edge Functions manifest failed
REQUIRED must have required property 'pattern'
7 | ],
8 | \\"routes\\": [
> 9 | {
| ^ ☹️ pattern is missing here!
10 | \\"name\\": \\"name\\",
11 | \\"function\\": \\"hello\\"
12 | }"
`;
exports[`should show multiple errors 1`] = `
"Validation of Edge Functions manifest failed
ADDTIONAL PROPERTY must NOT have additional properties
28 | ],
29 | \\"bundler_version\\": \\"1.6.0\\",
> 30 | \\"foo\\": \\"bar\\",
| ^^^^^ 😲 foo is not expected to be here!
31 | \\"baz\\": \\"bar\\"
32 | }
ADDTIONAL PROPERTY must NOT have additional properties
29 | \\"bundler_version\\": \\"1.6.0\\",
30 | \\"foo\\": \\"bar\\",
> 31 | \\"baz\\": \\"bar\\"
| ^^^^^ 😲 baz is not expected to be here!
32 | }"
`;
exports[`should throw on additional property on root level 1`] = `
"Validation of Edge Functions manifest failed
ADDTIONAL PROPERTY must NOT have additional properties
28 | ],
29 | \\"bundler_version\\": \\"1.6.0\\",
> 30 | \\"foo\\": \\"bar\\"
| ^^^^^ 😲 foo is not expected to be here!
31 | }"
`;
10 changes: 10 additions & 0 deletions node/validation/manifest/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export default class ManifestValidationError extends Error {
constructor(message: string | undefined) {
super(`Validation of Edge Functions manifest failed\n${message}`)

this.name = 'ManifestValidationError'

// https://github.com/microsoft/TypeScript-wiki/blob/0fecbda7263f130c57394d779b8ca13f0a2e9123/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
Object.setPrototypeOf(this, ManifestValidationError.prototype)
}
}

0 comments on commit 17ee035

Please sign in to comment.