Skip to content

Commit

Permalink
Add routes with external dependency to example app
Browse files Browse the repository at this point in the history
This allows testing that API routes with third-party dependencies
like JSDom don't cause issues in the OpenAPI generation.

This commit also adds an example on writing custom scripts
for generating/validating the OpenAPI spec.

The deep object comparison as part of the OpenAPI
generation/validation is also replaced with a faster
stringified comparison between the generated and
existing OpenAPI specs.

Co-authored-by: Austin Kelleher <[email protected]>
  • Loading branch information
blomqma and austinkelleher committed Apr 16, 2024
1 parent 2f09414 commit 26cac4f
Show file tree
Hide file tree
Showing 9 changed files with 373 additions and 8 deletions.
4 changes: 4 additions & 0 deletions apps/example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,18 @@
"start": "next start",
"generate": "pnpm prebuild && NODE_OPTIONS='--import=tsx' next-rest-framework generate",
"validate": "pnpm prebuild && NODE_OPTIONS='--import=tsx' next-rest-framework validate",
"custom-generate-openapi": "pnpm prebuild && tsx ./src/scripts/custom-generate-openapi.ts",
"custom-validate-openapi": "pnpm prebuild && tsx ./src/scripts/custom-validate-openapi.ts",
"lint": "tsc && next lint"
},
"dependencies": {
"jsdom": "24.0.0",
"next-rest-framework": "workspace:*",
"tsx": "4.7.2",
"zod-form-data": "2.0.2"
},
"devDependencies": {
"@types/jsdom": "^21.1.6",
"autoprefixer": "10.0.1",
"eslint-config-next": "14.0.4",
"postcss": "8.4.33",
Expand Down
51 changes: 51 additions & 0 deletions apps/example/public/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,31 @@
}
}
},
"/api/v1/route-with-external-dep": {
"get": {
"operationId": "routeWithExternalDep",
"responses": {
"200": {
"description": "Response for status 200",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RouteWithExternalDep200ResponseBody"
}
}
}
},
"500": {
"description": "An unknown error occurred, trying again might help.",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/UnexpectedError" }
}
}
}
}
}
},
"/api/v1/route-with-params/{slug}": {
"get": {
"operationId": "getParams",
Expand Down Expand Up @@ -526,6 +551,31 @@
}
}
},
"/api/v2/route-with-external-dep": {
"get": {
"operationId": "routeWithExternalDep",
"responses": {
"200": {
"description": "Response for status 200",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RouteWithExternalDep200ResponseBody"
}
}
}
},
"500": {
"description": "An unknown error occurred, trying again might help.",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/UnexpectedError" }
}
}
}
}
}
},
"/api/v2/route-with-params/{slug}": {
"get": {
"operationId": "getPathParams",
Expand Down Expand Up @@ -1076,6 +1126,7 @@
"file": { "type": "string", "format": "binary" }
}
},
"RouteWithExternalDep200ResponseBody": {},
"UnexpectedError": {
"type": "object",
"properties": { "message": { "type": "string" } },
Expand Down
21 changes: 21 additions & 0 deletions apps/example/src/app/api/v2/route-with-external-dep/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { route, routeOperation } from 'next-rest-framework';
import { JSDOM } from 'jsdom';
import { NextResponse } from 'next/server';
import { z } from 'zod';

export const { GET } = route({
routeWithExternalDep: routeOperation({
method: 'GET'
})
.outputs([
{
contentType: 'application/json',
status: 200,
body: z.custom<JSDOM>()
}
])
.handler(() => {
const dom = new JSDOM('<!DOCTYPE html><p>Hello world</p>');
return NextResponse.json(dom);
})
});
20 changes: 20 additions & 0 deletions apps/example/src/pages/api/v1/route-with-external-dep/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { apiRoute, apiRouteOperation } from 'next-rest-framework';
import { JSDOM } from 'jsdom';
import { z } from 'zod';

export default apiRoute({
routeWithExternalDep: apiRouteOperation({
method: 'GET'
})
.outputs([
{
contentType: 'application/json',
status: 200,
body: z.custom<JSDOM>()
}
])
.handler((_req, res) => {
const dom = new JSDOM('<!DOCTYPE html><p>Hello world</p>');
res.json(dom);
})
});
7 changes: 7 additions & 0 deletions apps/example/src/scripts/custom-generate-openapi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { generate } from 'next-rest-framework/dist/cli/generate';

generate({ configPath: '/api/v2' })
.then(() => {
console.log('Completed building OpenAPI schema from custom script.');
})
.catch(console.error);
7 changes: 7 additions & 0 deletions apps/example/src/scripts/custom-validate-openapi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { validate } from 'next-rest-framework/dist/cli/validate';

validate({ configPath: '/api/v2' })
.then(() => {
console.log('Completed validating OpenAPI schema from custom script.');
})
.catch(console.error);
3 changes: 1 addition & 2 deletions packages/next-rest-framework/src/cli/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { existsSync, readFileSync, writeFileSync } from 'fs';
import { join } from 'path';
import * as prettier from 'prettier';
import { findConfig, generateOpenApiSpec } from './utils';
import { isEqualWith } from 'lodash';

const writeOpenApiSpec = async ({
path,
Expand Down Expand Up @@ -49,7 +48,7 @@ export const generate = async ({ configPath }: { configPath?: string }) => {
const data = readFileSync(path);
const openApiSpec = JSON.parse(data.toString());

if (!isEqualWith(openApiSpec, spec)) {
if (!(JSON.stringify(openApiSpec) === JSON.stringify(spec))) {
console.info(
chalk.yellowBright(
'OpenAPI spec changed, regenerating `openapi.json`...'
Expand Down
3 changes: 1 addition & 2 deletions packages/next-rest-framework/src/cli/validate.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { join } from 'path';
import { findConfig, generateOpenApiSpec } from './utils';
import { readFileSync } from 'fs';
import { isEqualWith } from 'lodash';
import chalk from 'chalk';

// Check if the OpenAPI spec is up-to-date.
Expand All @@ -19,7 +18,7 @@ export const validate = async ({ configPath }: { configPath?: string }) => {
const data = readFileSync(path);
const openApiSpec = JSON.parse(data.toString());

if (!isEqualWith(openApiSpec, spec)) {
if (!(JSON.stringify(openApiSpec) === JSON.stringify(spec))) {
console.error(
chalk.red(
'API spec changed is not up-to-date. Run `next-rest-framework generate` to update it.'
Expand Down
Loading

0 comments on commit 26cac4f

Please sign in to comment.