Skip to content

Commit

Permalink
feat: wrap regex validation in feature flag (#302)
Browse files Browse the repository at this point in the history
  • Loading branch information
eduardoboucas committed Feb 14, 2023
1 parent bf54378 commit 862994b
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 11 deletions.
1 change: 1 addition & 0 deletions node/bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ const bundle = async (
bundles: [functionBundle],
declarations,
distDirectory,
featureFlags,
functions,
functionConfig: functionsWithConfig,
importMap: importMapSpecifier,
Expand Down
1 change: 1 addition & 0 deletions node/feature_flags.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const defaultFlags: Record<string, boolean> = {
edge_functions_cache_deno_dir: false,
edge_functions_config_export: false,
edge_functions_fail_unsupported_regex: false,
}

type FeatureFlag = keyof typeof defaultFlags
Expand Down
36 changes: 33 additions & 3 deletions node/manifest.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { env } from 'process'

import { test, expect } from 'vitest'
import { test, expect, vi } from 'vitest'

import { BundleFormat } from './bundle.js'
import { Declaration } from './declaration.js'
Expand Down Expand Up @@ -192,11 +192,41 @@ test('Generates a manifest with layers', () => {
expect(manifest2.layers).toEqual(layers)
})

test('Throws an error if the regular expression contains a negative lookahead', () => {
test('Shows a warning if the regular expression contains a negative lookahead', () => {
const mockConsoleWarn = vi.fn()
const consoleWarn = console.warn

console.warn = mockConsoleWarn

const functions = [{ name: 'func-1', path: '/path/to/func-1.ts' }]
const declarations = [{ function: 'func-1', pattern: '^/\\w+(?=\\d)$' }]
const manifest = generateManifest({
bundles: [],
declarations,
functions,
})

console.warn = consoleWarn

expect(manifest.routes).toEqual([{ function: 'func-1', pattern: '^/\\w+(?=\\d)$' }])
expect(mockConsoleWarn).toHaveBeenCalledOnce()
expect(mockConsoleWarn).toHaveBeenCalledWith(
"Function 'func-1' uses an unsupported regular expression and will not be invoked: Regular expressions with lookaheads are not supported",
)
})

test('Throws an error if the regular expression contains a negative lookahead and the `edge_functions_fail_unsupported_regex` flag is set', () => {
const functions = [{ name: 'func-1', path: '/path/to/func-1.ts' }]
const declarations = [{ function: 'func-1', pattern: '^/\\w+(?=\\d)$' }]

expect(() => generateManifest({ bundles: [], declarations, functions })).toThrowError(
expect(() =>
generateManifest({
bundles: [],
declarations,
featureFlags: { edge_functions_fail_unsupported_regex: true },
functions,
}),
).toThrowError(
/^Could not parse path declaration of function 'func-1': Regular expressions with lookaheads are not supported$/,
)
})
Expand Down
44 changes: 36 additions & 8 deletions node/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { Bundle } from './bundle.js'
import { Cache, FunctionConfig } from './config.js'
import { Declaration, parsePattern } from './declaration.js'
import { EdgeFunction } from './edge_function.js'
import { FeatureFlags } from './feature_flags.js'
import { Layer } from './layer.js'
import { getPackageVersion } from './package_json.js'
import { nonNullable } from './utils/non_nullable.js'
Expand Down Expand Up @@ -35,6 +36,7 @@ interface Manifest {
interface GenerateManifestOptions {
bundles?: Bundle[]
declarations?: Declaration[]
featureFlags?: FeatureFlags
functions: EdgeFunction[]
functionConfig?: Record<string, FunctionConfig>
importMap?: string
Expand Down Expand Up @@ -68,6 +70,7 @@ const sanitizeEdgeFunctionConfig = (config: Record<string, EdgeFunctionConfig>):
const generateManifest = ({
bundles = [],
declarations = [],
featureFlags,
functions,
functionConfig = {},
importMap,
Expand Down Expand Up @@ -95,13 +98,16 @@ const generateManifest = ({
return
}

const pattern = getRegularExpression(declaration)
const pattern = getRegularExpression(declaration, featureFlags?.edge_functions_fail_unsupported_regex)
const route: Route = {
function: func.name,
name: declaration.name,
pattern: serializePattern(pattern),
}
const excludedPattern = getExcludedRegularExpression(declaration)
const excludedPattern = getExcludedRegularExpression(
declaration,
featureFlags?.edge_functions_fail_unsupported_regex,
)

if (excludedPattern) {
manifestFunctionConfig[func.name].excluded_patterns.push(serializePattern(excludedPattern))
Expand Down Expand Up @@ -143,28 +149,50 @@ const pathToRegularExpression = (path: string) => {
return normalizedSource
}

const getRegularExpression = (declaration: Declaration) => {
const getRegularExpression = (declaration: Declaration, failUnsupportedRegex = false) => {
if ('pattern' in declaration) {
try {
return parsePattern(declaration.pattern)
} catch (error: unknown) {
throw new Error(
`Could not parse path declaration of function '${declaration.function}': ${(error as Error).message}`,
// eslint-disable-next-line max-depth
if (failUnsupportedRegex) {
throw new Error(
`Could not parse path declaration of function '${declaration.function}': ${(error as Error).message}`,
)
}

console.warn(
`Function '${declaration.function}' uses an unsupported regular expression and will not be invoked: ${
(error as Error).message
}`,
)

return declaration.pattern
}
}

return pathToRegularExpression(declaration.path)
}

const getExcludedRegularExpression = (declaration: Declaration) => {
const getExcludedRegularExpression = (declaration: Declaration, failUnsupportedRegex = false) => {
if ('excludedPattern' in declaration && declaration.excludedPattern) {
try {
return parsePattern(declaration.excludedPattern)
} catch (error: unknown) {
throw new Error(
`Could not parse path declaration of function '${declaration.function}': ${(error as Error).message}`,
// eslint-disable-next-line max-depth
if (failUnsupportedRegex) {
throw new Error(
`Could not parse path declaration of function '${declaration.function}': ${(error as Error).message}`,
)
}

console.warn(
`Function '${declaration.function}' uses an unsupported regular expression and will therefore not be invoked: ${
(error as Error).message
}`,
)

return declaration.excludedPattern
}
}

Expand Down

0 comments on commit 862994b

Please sign in to comment.