Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: validate edge function signature at build time #200

Merged
merged 15 commits into from
Nov 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions deno/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ try {
Deno.exit(exitCodes.ImportError)
}

if (typeof func.default !== 'function') {
Deno.exit(exitCodes.InvalidDefaultExport)
}

if (func.config === undefined) {
Deno.exit(exitCodes.NoConfig)
}
Expand Down
113 changes: 113 additions & 0 deletions node/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ const importMapFile = {
},
}

const invalidDefaultExportErr = (path: string) =>
`Default export in '${path}' must be a function. More on the Edge Functions API at https://ntl.fyi/edge-api.`

test('`getFunctionConfig` extracts configuration properties from function file', async () => {
const { path: tmpDir } = await tmp.dir()
const deno = new DenoBridge({
Expand Down Expand Up @@ -219,3 +222,113 @@ test('Loads function paths from the in-source `config` function', async () => {

await fs.rmdir(tmpDir.path, { recursive: true })
})

test('Passes validation if default export exists and is a function', async () => {
const { path: tmpDir } = await tmp.dir()
const deno = new DenoBridge({
cacheDirectory: tmpDir,
})

const func = {
name: 'func1',
source: `
const func = () => new Response("Hello world!")
export default func
`,
}

const logger = {
user: vi.fn().mockResolvedValue(null),
system: vi.fn().mockResolvedValue(null),
}
const path = join(tmpDir, `${func.name}.ts`)

await fs.writeFile(path, func.source)

expect(async () => {
await getFunctionConfig(
{
name: func.name,
path,
},
new ImportMap([importMapFile]),
deno,
logger,
)
}).not.toThrow()

await deleteAsync(tmpDir, { force: true })
})

test('Fails validation if default export is not function', async () => {
const { path: tmpDir } = await tmp.dir()
const deno = new DenoBridge({
cacheDirectory: tmpDir,
})

const func = {
name: 'func2',
source: `
const func = new Response("Hello world!")
export default func
`,
}

const logger = {
user: vi.fn().mockResolvedValue(null),
system: vi.fn().mockResolvedValue(null),
}
const path = join(tmpDir, `${func.name}.ts`)

await fs.writeFile(path, func.source)

const config = getFunctionConfig(
{
name: func.name,
path,
},
new ImportMap([importMapFile]),
deno,
logger,
)

await expect(config).rejects.toThrowError(invalidDefaultExportErr(path))

await deleteAsync(tmpDir, { force: true })
})

test('Fails validation if default export is not present', async () => {
const { path: tmpDir } = await tmp.dir()
const deno = new DenoBridge({
cacheDirectory: tmpDir,
})

const func = {
name: 'func3',
source: `
export const func = () => new Response("Hello world!")
`,
}

const logger = {
user: vi.fn().mockResolvedValue(null),
system: vi.fn().mockResolvedValue(null),
}
const path = join(tmpDir, `${func.name}.ts`)

await fs.writeFile(path, func.source)

const config = getFunctionConfig(
{
name: func.name,
path,
},
new ImportMap([importMapFile]),
deno,
logger,
)

await expect(config).rejects.toThrowError(invalidDefaultExportErr(path))

await deleteAsync(tmpDir, { force: true })
})
6 changes: 6 additions & 0 deletions node/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ enum ConfigExitCode {
InvalidExport,
RuntimeError,
SerializationError,
InvalidDefaultExport,
}

export const enum Cache {
Expand Down Expand Up @@ -121,6 +122,11 @@ const logConfigError = (func: EdgeFunction, exitCode: number, stderr: string, lo

break

case ConfigExitCode.InvalidDefaultExport:
throw new Error(
`Default export in '${func.path}' must be a function. More on the Edge Functions API at https://ntl.fyi/edge-api.`,
)

default:
log.user(`Could not load configuration for edge function at '${func.path}'`)
log.user(stderr)
Expand Down