Skip to content

Commit

Permalink
fix: improve DX around malformed Telefunc request in dev
Browse files Browse the repository at this point in the history
  • Loading branch information
brillout committed Feb 3, 2022
1 parent dc08539 commit c4b2dd7
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 53 deletions.
11 changes: 2 additions & 9 deletions telefunc/node/server/runTelefunc.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export { runTelefunc }

import type { HttpRequest, TelefuncFiles, Telefunction } from './types'
import type { TelefuncFiles, Telefunction } from './types'
import type { ViteDevServer } from 'vite'
import { assert, assertUsage, checkType, objectAssign } from '../utils'
import { getContextOptional } from './getContext'
Expand Down Expand Up @@ -46,7 +46,7 @@ async function runTelefunc(runContext: Parameters<typeof runTelefunc_>[0]) {
}

async function runTelefunc_(runContext: {
httpRequest: HttpRequest
httpRequest: { url: string; method: string; body: unknown }
viteDevServer: ViteDevServer | null
telefuncFiles: TelefuncFiles | null
isProduction: boolean
Expand All @@ -57,13 +57,6 @@ async function runTelefunc_(runContext: {
objectAssign(runContext, {
providedContext: getContextOptional() || null,
})

if (runContext.httpRequest.method !== 'POST' && runContext.httpRequest.method !== 'post') {
assert(runContext.isProduction) // We don't expect any third-party requests in development and we can assume requests to always originate from the Telefunc Client.
return malformedRequest
}
assert(runContext.httpRequest.url === runContext.telefuncUrl)

{
const parsed = parseHttpRequest(runContext)
if (parsed.isMalformed) {
Expand Down
16 changes: 0 additions & 16 deletions telefunc/node/server/runTelefunc/assertHttpRequest.ts

This file was deleted.

52 changes: 46 additions & 6 deletions telefunc/node/server/runTelefunc/parseHttpRequest.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
export { parseHttpRequest }

import { parse } from '@brillout/json-s/parse'
import { assertUsage, hasProp, getPluginError } from '../../utils'
import { getTelefunctionKey } from './getTelefunctionKey'
import { assertUsage, hasProp, getPluginError, getUrlPathname } from '../../utils'

function parseHttpRequest(runContext: { httpRequest: { body: unknown }; isProduction: boolean; telefuncUrl: string }):
const devErrMsgPrefix =
'Malformed request in development. This is unexpected since, in development, all requests are expected to originate from the Telefunc Client. If this error is happening in production, then either the environment variable `NODE_ENV="production"` or `telefunc({ isProduction: true })` is missing.'

function parseHttpRequest(runContext: {
httpRequest: { body: unknown; url: string; method: string }
isProduction: boolean
telefuncUrl: string
}):
| {
telefunctionFilePath: string
telefunctionExportName: string
Expand All @@ -13,20 +20,24 @@ function parseHttpRequest(runContext: { httpRequest: { body: unknown }; isProduc
isMalformed: false
}
| { isMalformed: true } {
assertUrl(runContext)

if (isWrongMethod(runContext)) {
return { isMalformed: true }
}

const { body } = runContext.httpRequest
if (typeof body !== 'string') {
if (!runContext.isProduction) {
assertBody(body, runContext)
} else {
// In production `body` can be any value really.
// Therefore we `assertBody(body)` only development.
assertBody(body, runContext)
}
return { isMalformed: true }
}
const bodyString: string = body

const devErrMsgPrefix =
'Malformed request in development. This is unexpected since, in development, all requests are expected to originate from the Telefunc Client. If this error is happening in production, then this means that you forgot to set the environment variable `NODE_ENV="production"` or `telefunc({ isProduction: true })`.'

let bodyParsed: unknown
try {
bodyParsed = parse(bodyString)
Expand All @@ -43,6 +54,8 @@ function parseHttpRequest(runContext: { httpRequest: { body: unknown }; isProduc
.join(' '),
),
)
} else {
// In production any kind of malformed request can happen
}
return { isMalformed: true }
}
Expand Down Expand Up @@ -103,3 +116,30 @@ function assertBody(body: unknown, runContext: { telefuncUrl: string }) {
["`telefunc({ body })`: argument `body` is an empty JSON object (`body === '{}'`).", errorNote].join(' '),
)
}

function isWrongMethod(runContext: { httpRequest: { method: string }; isProduction: boolean }) {
if (['POST', 'post'].includes(runContext.httpRequest.method)) {
return false
}
if (!runContext.isProduction) {
console.error(
getPluginError(
[
devErrMsgPrefix,
`The HTTP request method should be \`POST\` (or \`post\`) but \`method === ${runContext.httpRequest.method}\`.`,
].join(' '),
),
)
} else {
// In production any kind of malformed request are expected, e.g. GET requests to `_telefunc`.
}
return true
}

function assertUrl(runContext: { httpRequest: { url: string }; telefuncUrl: string }) {
const urlPathname = getUrlPathname(runContext.httpRequest.url)
assertUsage(
urlPathname === runContext.telefuncUrl,
`telefunc({ url }): The HTTP request \`url\` pathname \`${urlPathname}\` should be \`${runContext.telefuncUrl}\`. Make sure that \`url\` is the HTTP request URL, or change \`telefuncConfig.telefuncUrl\` to \`${urlPathname}\`.`,
)
}
27 changes: 13 additions & 14 deletions telefunc/node/server/telefunc.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
export { telefunc }

import { assertHttpRequest } from './runTelefunc/assertHttpRequest'
import { runTelefunc } from './runTelefunc'
import { HttpRequest } from './types'
import { assert, assertUsage, getUrlPathname, objectAssign } from '../utils'
import { assert, assertUsage, hasProp, isObject, objectAssign } from '../utils'
import { telefuncConfig } from './telefuncConfig'

/**
Expand All @@ -13,24 +11,25 @@ import { telefuncConfig } from './telefuncConfig'
* @param httpRequest.body HTTP request body
* @returns HTTP response
*/
async function telefunc(httpRequest: HttpRequest) {
async function telefunc(httpRequest: { url: string; body: string; method: 'POST' | 'post' }) {
assertHttpRequest(httpRequest, arguments.length)

const runContext = {}
objectAssign(runContext, { httpRequest })
objectAssign(runContext, telefuncConfig)

assertUrl(runContext)

const httpResponse = await runTelefunc(runContext)
assert(httpResponse)
return httpResponse
}

function assertUrl(runContext: { httpRequest: { url: string }; telefuncUrl: string }) {
const urlPathname = getUrlPathname(runContext.httpRequest.url)
assertUsage(
urlPathname === runContext.telefuncUrl,
`telefunc({ url }): The HTTP request \`url\` pathname \`${urlPathname}\` should be \`${runContext.telefuncUrl}\`. Make sure that \`url\` is the HTTP request URL, or change \`telefuncConfig.telefuncUrl\` to \`${urlPathname}\`.`,
)
function assertHttpRequest(httpRequest: unknown, numberOfArguments: number) {
assertUsage(httpRequest, '`telefunc(httpRequest)`: argument `httpRequest` is missing.')
assertUsage(numberOfArguments === 1, '`telefunc()`: all arguments should be passed as a single argument object.')
assertUsage(isObject(httpRequest), '`telefunc(httpRequest)`: argument `httpRequest` should be an object.')
assertUsage(httpRequest, '`telefunc(httpRequest)`: argument `httpRequest` is missing.')
assertUsage(hasProp(httpRequest, 'url'), '`telefunc({ url })`: argument `url` is missing.')
assertUsage(hasProp(httpRequest, 'url', 'string'), '`telefunc({ url })`: argument `url` should be a string.')
assertUsage(hasProp(httpRequest, 'method'), '`telefunc({ method })`: argument `method` is missing.')
assertUsage(hasProp(httpRequest, 'method', 'string'), '`telefunc({ method })`: argument `method` should be a string.')
assertUsage(hasProp(httpRequest, 'body'), '`telefunc({ body })`: argument `body` is missing.')
// We further assert the `httpRequest` in `./runTelefunc/parseHttpRequest.ts`
}
8 changes: 0 additions & 8 deletions telefunc/node/server/types.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
export type { HttpRequest }
export type { TelefuncFiles }
export type { Telefunction }

type Telefunction = (...args: unknown[]) => Promise<unknown>

type HttpRequest = {
url: string
method: string
body: unknown
}

type FileExports = Record<string, unknown>
type TelefuncFiles = Record<string, FileExports>

0 comments on commit c4b2dd7

Please sign in to comment.