-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Do not log initialization info in prod (#99)
* Do not log initialization info in prod This change prevents loggin the initialization info in prod environment, as the logging state is handled in memory and serverless environments will log it on every invocation of the docs route handlers. This also refactors the project structure so that app router and pages router specific code is split into separate folders. * Improve test coverage
- Loading branch information
Showing
17 changed files
with
659 additions
and
527 deletions.
There are no files selected for viewing
66 changes: 66 additions & 0 deletions
66
packages/next-rest-framework/src/app-router/docs-route-handler.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { type NextRequest, NextResponse } from 'next/server'; | ||
import { DEFAULT_ERRORS, NEXT_REST_FRAMEWORK_USER_AGENT } from '../constants'; | ||
import { type BaseQuery, type NextRestFrameworkConfig } from '../types'; | ||
import { | ||
generatePathsFromDev, | ||
getConfig, | ||
syncOpenApiSpec, | ||
logInitInfo, | ||
logNextRestFrameworkError, | ||
getHtmlForDocs | ||
} from '../utils'; | ||
|
||
export const docsRouteHandler = (_config?: NextRestFrameworkConfig) => { | ||
const config = getConfig(_config); | ||
|
||
const handler = async (req: NextRequest, _context: { params: BaseQuery }) => { | ||
try { | ||
const { headers, nextUrl } = req; | ||
const host = headers.get('host') ?? ''; | ||
|
||
if (process.env.NODE_ENV !== 'production') { | ||
const proto = headers.get('x-forwarded-proto') ?? 'http'; | ||
const baseUrl = `${proto}://${host}`; | ||
const url = baseUrl + nextUrl.pathname; | ||
|
||
// Return 403 if called internally by the framework. | ||
if (headers.get('user-agent') === NEXT_REST_FRAMEWORK_USER_AGENT) { | ||
return NextResponse.json( | ||
{ | ||
message: `${NEXT_REST_FRAMEWORK_USER_AGENT} user agent is not allowed.` | ||
}, | ||
{ status: 403 } | ||
); | ||
} | ||
|
||
if (!config.suppressInfo) { | ||
logInitInfo({ config, baseUrl, url }); | ||
} | ||
|
||
if (config.autoGenerateOpenApiSpec) { | ||
const paths = await generatePathsFromDev({ config, baseUrl, url }); | ||
await syncOpenApiSpec({ config, paths }); | ||
} | ||
} | ||
|
||
const html = getHtmlForDocs({ config, host }); | ||
|
||
return new NextResponse(html, { | ||
headers: { | ||
'Content-Type': 'text/html' | ||
}, | ||
status: 200 | ||
}); | ||
} catch (error) { | ||
logNextRestFrameworkError(error); | ||
|
||
return NextResponse.json( | ||
{ message: DEFAULT_ERRORS.unexpectedError }, | ||
{ status: 500 } | ||
); | ||
} | ||
}; | ||
|
||
handler.nextRestFrameworkConfig = config; | ||
return handler; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from './docs-route-handler'; | ||
export * from './route-handler'; | ||
export * from './route-operation'; |
155 changes: 155 additions & 0 deletions
155
packages/next-rest-framework/src/app-router/route-handler.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
import { NextRequest, NextResponse } from 'next/server'; | ||
import { DEFAULT_ERRORS, NEXT_REST_FRAMEWORK_USER_AGENT } from '../constants'; | ||
import { type RouteParams, type BaseQuery } from '../types'; | ||
import { | ||
getPathsFromMethodHandlers, | ||
isValidMethod, | ||
validateSchema, | ||
logNextRestFrameworkError | ||
} from '../utils'; | ||
|
||
export const routeHandler = (methodHandlers: RouteParams) => { | ||
const handler = async (req: NextRequest, context: { params: BaseQuery }) => { | ||
try { | ||
const { method, headers, nextUrl } = req; | ||
const { pathname } = nextUrl; | ||
|
||
const handleMethodNotAllowed = () => { | ||
return NextResponse.json( | ||
{ message: DEFAULT_ERRORS.methodNotAllowed }, | ||
{ | ||
status: 405, | ||
headers: { | ||
Allow: Object.keys(methodHandlers).join(', ') | ||
} | ||
} | ||
); | ||
}; | ||
|
||
if (!isValidMethod(method)) { | ||
return handleMethodNotAllowed(); | ||
} | ||
|
||
if ( | ||
process.env.NODE_ENV !== 'production' && | ||
headers.get('user-agent') === NEXT_REST_FRAMEWORK_USER_AGENT | ||
) { | ||
const route = decodeURIComponent(pathname ?? ''); | ||
|
||
try { | ||
const nextRestFrameworkPaths = getPathsFromMethodHandlers({ | ||
methodHandlers, | ||
route | ||
}); | ||
|
||
return NextResponse.json({ nextRestFrameworkPaths }, { status: 200 }); | ||
} catch (error) { | ||
throw Error(`OpenAPI spec generation failed for route: ${route} | ||
${error}`); | ||
} | ||
} | ||
|
||
const methodHandler = methodHandlers[method]; | ||
|
||
if (!methodHandler) { | ||
return handleMethodNotAllowed(); | ||
} | ||
|
||
const { input, handler, middleware } = methodHandler._config; | ||
|
||
if (middleware) { | ||
const res = await middleware(new NextRequest(req.clone()), context); | ||
|
||
if (res) { | ||
return res; | ||
} | ||
} | ||
|
||
if (input) { | ||
const { body: bodySchema, query: querySchema, contentType } = input; | ||
|
||
if ( | ||
contentType && | ||
headers.get('content-type')?.split(';')[0] !== contentType | ||
) { | ||
return NextResponse.json( | ||
{ message: DEFAULT_ERRORS.invalidMediaType }, | ||
{ status: 415 } | ||
); | ||
} | ||
|
||
if (bodySchema) { | ||
try { | ||
const reqClone = req.clone(); | ||
const body = await reqClone.json(); | ||
|
||
const { valid, errors } = await validateSchema({ | ||
schema: bodySchema, | ||
obj: body | ||
}); | ||
|
||
if (!valid) { | ||
return NextResponse.json( | ||
{ | ||
message: 'Invalid request body.', | ||
errors | ||
}, | ||
{ | ||
status: 400 | ||
} | ||
); | ||
} | ||
} catch (error) { | ||
return NextResponse.json( | ||
{ | ||
message: 'Missing request body.' | ||
}, | ||
{ | ||
status: 400 | ||
} | ||
); | ||
} | ||
} | ||
if (querySchema) { | ||
const { valid, errors } = await validateSchema({ | ||
schema: querySchema, | ||
obj: Object.fromEntries(new URLSearchParams(req.nextUrl.search)) | ||
}); | ||
|
||
if (!valid) { | ||
return NextResponse.json( | ||
{ | ||
message: 'Invalid query parameters.', | ||
errors | ||
}, | ||
{ | ||
status: 400 | ||
} | ||
); | ||
} | ||
} | ||
} | ||
|
||
if (!handler) { | ||
throw Error('Handler not found.'); | ||
} | ||
|
||
const res = await handler(req, context); | ||
return res; | ||
} catch (error) { | ||
logNextRestFrameworkError(error); | ||
return NextResponse.json( | ||
{ message: DEFAULT_ERRORS.unexpectedError }, | ||
{ status: 500 } | ||
); | ||
} | ||
}; | ||
|
||
handler.getPaths = (route: string) => | ||
getPathsFromMethodHandlers({ | ||
methodHandlers, | ||
route | ||
}); | ||
|
||
return handler; | ||
}; |
55 changes: 55 additions & 0 deletions
55
packages/next-rest-framework/src/app-router/route-operation.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { | ||
type RouteOperation, | ||
type RouteOperationDefinition, | ||
type InputObject, | ||
type OutputObject, | ||
type NextRouteHandler | ||
} from '../types'; | ||
|
||
export const routeOperation: RouteOperation = (openApiOperation) => { | ||
const createConfig = <Middleware, Handler>( | ||
input: InputObject | undefined, | ||
output: readonly OutputObject[] | undefined, | ||
middleware: Middleware | undefined, | ||
handler: Handler | undefined | ||
): RouteOperationDefinition => ({ | ||
_config: { | ||
openApiOperation, | ||
input, | ||
output, | ||
middleware: middleware as NextRouteHandler, | ||
handler: handler as NextRouteHandler | ||
} | ||
}); | ||
|
||
return { | ||
input: (input) => ({ | ||
output: (output) => ({ | ||
middleware: (middleware) => ({ | ||
handler: (handler) => createConfig(input, output, middleware, handler) | ||
}), | ||
handler: (handler) => createConfig(input, output, undefined, handler) | ||
}), | ||
middleware: (middleware) => ({ | ||
output: (output) => ({ | ||
handler: (handler) => createConfig(input, output, middleware, handler) | ||
}), | ||
handler: (handler) => | ||
createConfig(input, undefined, middleware, handler) | ||
}), | ||
handler: (handler) => createConfig(input, undefined, undefined, handler) | ||
}), | ||
output: (output) => ({ | ||
middleware: (middleware) => ({ | ||
handler: (handler) => | ||
createConfig(undefined, output, middleware, handler) | ||
}), | ||
handler: (handler) => createConfig(undefined, output, undefined, handler) | ||
}), | ||
middleware: (middleware) => ({ | ||
handler: (handler) => | ||
createConfig(undefined, undefined, middleware, handler) | ||
}), | ||
handler: (handler) => createConfig(undefined, undefined, undefined, handler) | ||
}; | ||
}; |
Oops, something went wrong.
d0ce834
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
next-rest-framework-demo – ./apps/example
next-rest-framework-demo.vercel.app
next-rest-framework-demo-blomqma.vercel.app
next-rest-framework-demo-git-main-blomqma.vercel.app
d0ce834
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
next-rest-framework – ./docs
next-rest-framework.vercel.app
next-rest-framework-git-main-blomqma.vercel.app
next-rest-framework-blomqma.vercel.app