mirror of
https://github.com/Unleash/unleash.git
synced 2025-03-04 00:18:40 +01:00
fix: fix broken OpenAPI spec (#1846)
* Wip: fix openapi spec
* Feat: add openapi enforcer for enforcing the generated schema
* Chore: Allow the example keyword in params
* Feat: add validator tests and fix some errors
* Use @apidevtools/swagger-parser for schema validation
* Wip: refactor tests for updated schema name
* Feat: update request params creation method
* Feat: add query params to state
* Refactor: move mapping test into separate function
* Refactor: rename request-parameters -> query-parameters
* Refactor: expose only finished query parameters
* Wip: fixup param types
* Refactor: remove unused types
* Chore: rename and cleanup
* Chore: cleanup
* Fix: Update snapshot
* Fix: use ?? Instead of paramToBool to get defaults
* Wip: generate query param object type from openapi params list
* Wip: use generated types for export query params
* Revert "Fix: use ?? Instead of paramToBool to get defaults"
This reverts commit 842567500b
.
Because we accept bools, strings, and numbers, this is the only way to
do it.
* Chore: update and pin json-schema-to-ts
* Fix: use `&` to merge types
* Update snapshot
* Chore: rename export-parameters-schema -> export-query-parameters
When it ends in `schema`, the tests expect it to be included in the
openapi index file.
This commit is contained in:
parent
f6192b50b0
commit
6afc0a6954
@ -100,7 +100,7 @@
|
||||
"helmet": "^5.0.0",
|
||||
"joi": "^17.3.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"json-schema-to-ts": "^2.5.3",
|
||||
"json-schema-to-ts": "2.5.4",
|
||||
"knex": "^2.0.0",
|
||||
"log4js": "^6.0.0",
|
||||
"make-fetch-happen": "^10.1.2",
|
||||
@ -120,12 +120,14 @@
|
||||
"semver": "^7.3.5",
|
||||
"serve-favicon": "^2.5.0",
|
||||
"stoppable": "^1.1.0",
|
||||
"ts-toolbelt": "^9.6.0",
|
||||
"type-is": "^1.6.18",
|
||||
"unleash-client": "^3.15.0",
|
||||
"unleash-frontend": "4.14.0-beta.6",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@apidevtools/swagger-parser": "^10.1.0",
|
||||
"@babel/core": "7.18.9",
|
||||
"@types/bcryptjs": "2.4.2",
|
||||
"@types/express": "4.17.13",
|
||||
|
@ -30,7 +30,6 @@ import { environmentSchema } from './spec/environment-schema';
|
||||
import { environmentsSchema } from './spec/environments-schema';
|
||||
import { eventSchema } from './spec/event-schema';
|
||||
import { eventsSchema } from './spec/events-schema';
|
||||
import { exportParametersSchema } from './spec/export-parameters-schema';
|
||||
import { featureEnvironmentMetricsSchema } from './spec/feature-environment-metrics-schema';
|
||||
import { featureEnvironmentSchema } from './spec/feature-environment-schema';
|
||||
import { featureEventsSchema } from './spec/feature-events-schema';
|
||||
@ -139,7 +138,6 @@ export const schemas = {
|
||||
environmentsSchema,
|
||||
eventSchema,
|
||||
eventsSchema,
|
||||
exportParametersSchema,
|
||||
featureEnvironmentMetricsSchema,
|
||||
featureEnvironmentSchema,
|
||||
featureEventsSchema,
|
||||
|
@ -3,7 +3,6 @@ import { FromSchema } from 'json-schema-to-ts';
|
||||
export const clientFeaturesQuerySchema = {
|
||||
$id: '#/components/schemas/clientFeaturesQuerySchema',
|
||||
type: 'object',
|
||||
required: [],
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
tag: {
|
||||
|
@ -1,32 +0,0 @@
|
||||
import { FromSchema } from 'json-schema-to-ts';
|
||||
|
||||
export const exportParametersSchema = {
|
||||
$id: '#/components/schemas/exportParametersSchema',
|
||||
type: 'object',
|
||||
properties: {
|
||||
format: {
|
||||
type: 'string',
|
||||
},
|
||||
download: {
|
||||
type: 'boolean',
|
||||
},
|
||||
strategies: {
|
||||
type: 'boolean',
|
||||
},
|
||||
featureToggles: {
|
||||
type: 'boolean',
|
||||
},
|
||||
projects: {
|
||||
type: 'boolean',
|
||||
},
|
||||
tags: {
|
||||
type: 'boolean',
|
||||
},
|
||||
environments: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
components: {},
|
||||
} as const;
|
||||
|
||||
export type ExportParametersSchema = FromSchema<typeof exportParametersSchema>;
|
143
src/lib/openapi/spec/export-query-parameters.ts
Normal file
143
src/lib/openapi/spec/export-query-parameters.ts
Normal file
@ -0,0 +1,143 @@
|
||||
import { FromQueryParams } from '../util/from-query-params';
|
||||
|
||||
export const exportQueryParameters = [
|
||||
{
|
||||
name: 'format',
|
||||
schema: {
|
||||
type: 'string',
|
||||
enum: ['json', 'yaml'],
|
||||
default: 'json',
|
||||
},
|
||||
description: 'Desired export format. Must be either `json` or `yaml`.',
|
||||
in: 'query',
|
||||
},
|
||||
{
|
||||
name: 'download',
|
||||
schema: {
|
||||
default: false,
|
||||
anyOf: [
|
||||
{
|
||||
type: 'boolean',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
minLength: 1,
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
},
|
||||
],
|
||||
},
|
||||
description: 'Whether exported data should be downloaded as a file.',
|
||||
in: 'query',
|
||||
},
|
||||
{
|
||||
name: 'strategies',
|
||||
schema: {
|
||||
default: true,
|
||||
anyOf: [
|
||||
{
|
||||
type: 'boolean',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
minLength: 1,
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
},
|
||||
],
|
||||
},
|
||||
description:
|
||||
'Whether strategies should be included in the exported data.',
|
||||
in: 'query',
|
||||
},
|
||||
{
|
||||
name: 'featureToggles',
|
||||
schema: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'boolean',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
minLength: 1,
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
},
|
||||
],
|
||||
default: true,
|
||||
},
|
||||
description:
|
||||
'Whether feature toggles should be included in the exported data.',
|
||||
in: 'query',
|
||||
},
|
||||
{
|
||||
name: 'projects',
|
||||
schema: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'boolean',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
minLength: 1,
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
},
|
||||
],
|
||||
default: true,
|
||||
},
|
||||
description:
|
||||
'Whether projects should be included in the exported data.',
|
||||
in: 'query',
|
||||
},
|
||||
{
|
||||
name: 'tags',
|
||||
schema: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'boolean',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
minLength: 1,
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
},
|
||||
],
|
||||
default: true,
|
||||
},
|
||||
description:
|
||||
'Whether tag types, tags, and feature_tags should be included in the exported data.',
|
||||
in: 'query',
|
||||
},
|
||||
{
|
||||
name: 'environments',
|
||||
schema: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'boolean',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
minLength: 1,
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
},
|
||||
],
|
||||
default: true,
|
||||
},
|
||||
description:
|
||||
'Whether environments should be included in the exported data.',
|
||||
in: 'query',
|
||||
},
|
||||
] as const;
|
||||
|
||||
export type ExportQueryParameters = FromQueryParams<
|
||||
typeof exportQueryParameters
|
||||
>;
|
@ -4,7 +4,6 @@ export const feedbackSchema = {
|
||||
$id: '#/components/schemas/feedbackSchema',
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: [],
|
||||
properties: {
|
||||
userId: {
|
||||
type: 'number',
|
||||
|
@ -10,9 +10,9 @@ export const playgroundFeatureSchema = {
|
||||
additionalProperties: false,
|
||||
required: ['name', 'projectId', 'isEnabled', 'variant', 'variants'],
|
||||
properties: {
|
||||
name: { type: 'string', examples: ['my-feature'] },
|
||||
projectId: { type: 'string', examples: ['my-project'] },
|
||||
isEnabled: { type: 'boolean', examples: [true] },
|
||||
name: { type: 'string', example: 'my-feature' },
|
||||
projectId: { type: 'string', example: 'my-project' },
|
||||
isEnabled: { type: 'boolean', example: true },
|
||||
variant: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
@ -34,7 +34,7 @@ export const playgroundFeatureSchema = {
|
||||
},
|
||||
},
|
||||
nullable: true,
|
||||
examples: ['green'],
|
||||
example: { name: 'green', enabled: true },
|
||||
},
|
||||
variants: { type: 'array', items: { $ref: variantSchema.$id } },
|
||||
},
|
||||
|
@ -8,13 +8,13 @@ export const playgroundRequestSchema = {
|
||||
type: 'object',
|
||||
required: ['environment', 'context'],
|
||||
properties: {
|
||||
environment: { type: 'string', examples: ['development'] },
|
||||
environment: { type: 'string', example: 'development' },
|
||||
projects: {
|
||||
oneOf: [
|
||||
{
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
examples: ['my-project', 'my-other-project'],
|
||||
example: ['my-project'],
|
||||
description: 'A list of projects to check for toggles in.',
|
||||
},
|
||||
{
|
||||
|
@ -6,40 +6,38 @@ export const sdkContextSchema = {
|
||||
type: 'object',
|
||||
additionalProperties: {
|
||||
type: 'string',
|
||||
examples: ['top-level custom context value'],
|
||||
example: 'top-level custom context value',
|
||||
},
|
||||
required: ['appName'],
|
||||
properties: {
|
||||
appName: {
|
||||
type: 'string',
|
||||
minLength: 1,
|
||||
examples: ['My cool application.'],
|
||||
example: 'My cool application.',
|
||||
},
|
||||
currentTime: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
examples: ['2022-07-05T12:56:41+02:00'],
|
||||
example: '2022-07-05T12:56:41+02:00',
|
||||
},
|
||||
environment: { type: 'string', deprecated: true },
|
||||
properties: {
|
||||
type: 'object',
|
||||
additionalProperties: { type: 'string' },
|
||||
examples: [
|
||||
{
|
||||
customContextField: 'this is one!',
|
||||
otherCustomField: 3,
|
||||
},
|
||||
],
|
||||
example: {
|
||||
customContextField: 'this is one!',
|
||||
otherCustomField: '3',
|
||||
},
|
||||
},
|
||||
remoteAddress: {
|
||||
type: 'string',
|
||||
examples: ['192.168.1.1'],
|
||||
example: '192.168.1.1',
|
||||
},
|
||||
sessionId: {
|
||||
type: 'string',
|
||||
examples: ['b65e7b23-fec0-4814-a129-0e9861ef18fc'],
|
||||
example: 'b65e7b23-fec0-4814-a129-0e9861ef18fc',
|
||||
},
|
||||
userId: { type: 'string', examples: ['username@provider.com'] },
|
||||
userId: { type: 'string', example: 'username@provider.com' },
|
||||
},
|
||||
components: {},
|
||||
} as const;
|
||||
|
32
src/lib/openapi/util/from-query-params.ts
Normal file
32
src/lib/openapi/util/from-query-params.ts
Normal file
@ -0,0 +1,32 @@
|
||||
// module to create typescript types from query param lists. Based on
|
||||
// input in this GitHub issue:
|
||||
// https://github.com/ThomasAribart/json-schema-to-ts/issues/82
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
|
||||
import { O, L, A } from 'ts-toolbelt';
|
||||
|
||||
type OpenApiParam = {
|
||||
readonly name: string;
|
||||
readonly schema: JSONSchema;
|
||||
// Parameter types:
|
||||
// https://swagger.io/docs/specification/describing-parameters/#types
|
||||
readonly in: 'query' | 'path' | 'header' | 'cookie';
|
||||
};
|
||||
|
||||
type RecurseOnParams<
|
||||
P extends readonly OpenApiParam[],
|
||||
R extends O.Object = {},
|
||||
> = {
|
||||
continue: RecurseOnParams<
|
||||
L.Tail<P>,
|
||||
L.Head<P>['in'] extends 'query'
|
||||
? R & {
|
||||
[key in L.Head<P>['name']]: FromSchema<L.Head<P>['schema']>;
|
||||
}
|
||||
: R
|
||||
>;
|
||||
stop: A.Compute<R>;
|
||||
}[P extends readonly [OpenApiParam, ...OpenApiParam[]] ? 'continue' : 'stop'];
|
||||
|
||||
export type FromQueryParams<P extends readonly OpenApiParam[]> =
|
||||
RecurseOnParams<P>;
|
@ -16,6 +16,10 @@ const ajv = new Ajv({
|
||||
|
||||
addFormats(ajv, ['date-time']);
|
||||
|
||||
// example was superseded by examples in openapi 3.1, but we're still on 3.0, so
|
||||
// let's add it back in!
|
||||
ajv.addKeyword('example');
|
||||
|
||||
export const validateSchema = (
|
||||
schema: SchemaId,
|
||||
data: unknown,
|
||||
|
@ -14,8 +14,12 @@ import { IAuthRequest } from '../unleash-types';
|
||||
import { OpenApiService } from '../../services/openapi-service';
|
||||
import { createRequestSchema } from '../../openapi/util/create-request-schema';
|
||||
import { createResponseSchema } from '../../openapi/util/create-response-schema';
|
||||
import { ExportParametersSchema } from '../../openapi/spec/export-parameters-schema';
|
||||
import {
|
||||
exportQueryParameters,
|
||||
ExportQueryParameters,
|
||||
} from '../../openapi/spec/export-query-parameters';
|
||||
import { emptyResponse } from '../../openapi/util/standard-responses';
|
||||
import { OpenAPIV3 } from 'openapi-types';
|
||||
|
||||
const upload = multer({ limits: { fileSize: 5242880 } });
|
||||
const paramToBool = (param, def) => {
|
||||
@ -75,11 +79,8 @@ class StateController extends Controller {
|
||||
responses: {
|
||||
200: createResponseSchema('stateSchema'),
|
||||
},
|
||||
parameters: [
|
||||
{
|
||||
$ref: '#/components/schema/exportParametersSchema',
|
||||
},
|
||||
],
|
||||
parameters:
|
||||
exportQueryParameters as unknown as OpenAPIV3.ParameterObject[],
|
||||
}),
|
||||
],
|
||||
});
|
||||
@ -114,7 +115,7 @@ class StateController extends Controller {
|
||||
}
|
||||
|
||||
async export(
|
||||
req: Request<unknown, unknown, unknown, ExportParametersSchema>,
|
||||
req: Request<unknown, unknown, unknown, ExportQueryParameters>,
|
||||
res: Response,
|
||||
): Promise<void> {
|
||||
const { format } = req.query;
|
||||
|
@ -553,7 +553,6 @@ Object {
|
||||
"type": "array",
|
||||
},
|
||||
},
|
||||
"required": Array [],
|
||||
"type": "object",
|
||||
},
|
||||
"clientFeaturesSchema": Object {
|
||||
@ -1018,32 +1017,6 @@ Object {
|
||||
],
|
||||
"type": "object",
|
||||
},
|
||||
"exportParametersSchema": Object {
|
||||
"properties": Object {
|
||||
"download": Object {
|
||||
"type": "boolean",
|
||||
},
|
||||
"environments": Object {
|
||||
"type": "boolean",
|
||||
},
|
||||
"featureToggles": Object {
|
||||
"type": "boolean",
|
||||
},
|
||||
"format": Object {
|
||||
"type": "string",
|
||||
},
|
||||
"projects": Object {
|
||||
"type": "boolean",
|
||||
},
|
||||
"strategies": Object {
|
||||
"type": "boolean",
|
||||
},
|
||||
"tags": Object {
|
||||
"type": "boolean",
|
||||
},
|
||||
},
|
||||
"type": "object",
|
||||
},
|
||||
"featureEnvironmentMetricsSchema": Object {
|
||||
"additionalProperties": false,
|
||||
"properties": Object {
|
||||
@ -1465,7 +1438,6 @@ Object {
|
||||
"type": "number",
|
||||
},
|
||||
},
|
||||
"required": Array [],
|
||||
"type": "object",
|
||||
},
|
||||
"groupSchema": Object {
|
||||
@ -1834,28 +1806,23 @@ Object {
|
||||
"description": "A simplified feature toggle model intended for the Unleash playground.",
|
||||
"properties": Object {
|
||||
"isEnabled": Object {
|
||||
"examples": Array [
|
||||
true,
|
||||
],
|
||||
"example": true,
|
||||
"type": "boolean",
|
||||
},
|
||||
"name": Object {
|
||||
"examples": Array [
|
||||
"my-feature",
|
||||
],
|
||||
"example": "my-feature",
|
||||
"type": "string",
|
||||
},
|
||||
"projectId": Object {
|
||||
"examples": Array [
|
||||
"my-project",
|
||||
],
|
||||
"example": "my-project",
|
||||
"type": "string",
|
||||
},
|
||||
"variant": Object {
|
||||
"additionalProperties": false,
|
||||
"examples": Array [
|
||||
"green",
|
||||
],
|
||||
"example": Object {
|
||||
"enabled": true,
|
||||
"name": "green",
|
||||
},
|
||||
"nullable": true,
|
||||
"properties": Object {
|
||||
"enabled": Object {
|
||||
@ -1915,18 +1882,15 @@ Object {
|
||||
"$ref": "#/components/schemas/sdkContextSchema",
|
||||
},
|
||||
"environment": Object {
|
||||
"examples": Array [
|
||||
"development",
|
||||
],
|
||||
"example": "development",
|
||||
"type": "string",
|
||||
},
|
||||
"projects": Object {
|
||||
"oneOf": Array [
|
||||
Object {
|
||||
"description": "A list of projects to check for toggles in.",
|
||||
"examples": Array [
|
||||
"example": Array [
|
||||
"my-project",
|
||||
"my-other-project",
|
||||
],
|
||||
"items": Object {
|
||||
"type": "string",
|
||||
@ -2074,24 +2038,18 @@ Object {
|
||||
},
|
||||
"sdkContextSchema": Object {
|
||||
"additionalProperties": Object {
|
||||
"examples": Array [
|
||||
"top-level custom context value",
|
||||
],
|
||||
"example": "top-level custom context value",
|
||||
"type": "string",
|
||||
},
|
||||
"description": "The Unleash context as modeled in client SDKs",
|
||||
"properties": Object {
|
||||
"appName": Object {
|
||||
"examples": Array [
|
||||
"My cool application.",
|
||||
],
|
||||
"example": "My cool application.",
|
||||
"minLength": 1,
|
||||
"type": "string",
|
||||
},
|
||||
"currentTime": Object {
|
||||
"examples": Array [
|
||||
"2022-07-05T12:56:41+02:00",
|
||||
],
|
||||
"example": "2022-07-05T12:56:41+02:00",
|
||||
"format": "date-time",
|
||||
"type": "string",
|
||||
},
|
||||
@ -2103,30 +2061,22 @@ Object {
|
||||
"additionalProperties": Object {
|
||||
"type": "string",
|
||||
},
|
||||
"examples": Array [
|
||||
Object {
|
||||
"customContextField": "this is one!",
|
||||
"otherCustomField": 3,
|
||||
},
|
||||
],
|
||||
"example": Object {
|
||||
"customContextField": "this is one!",
|
||||
"otherCustomField": "3",
|
||||
},
|
||||
"type": "object",
|
||||
},
|
||||
"remoteAddress": Object {
|
||||
"examples": Array [
|
||||
"192.168.1.1",
|
||||
],
|
||||
"example": "192.168.1.1",
|
||||
"type": "string",
|
||||
},
|
||||
"sessionId": Object {
|
||||
"examples": Array [
|
||||
"b65e7b23-fec0-4814-a129-0e9861ef18fc",
|
||||
],
|
||||
"example": "b65e7b23-fec0-4814-a129-0e9861ef18fc",
|
||||
"type": "string",
|
||||
},
|
||||
"userId": Object {
|
||||
"examples": Array [
|
||||
"username@provider.com",
|
||||
],
|
||||
"example": "username@provider.com",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
@ -5168,7 +5118,137 @@ If the provided project does not exist, the list of events will be empty.",
|
||||
"operationId": "export",
|
||||
"parameters": Array [
|
||||
Object {
|
||||
"$ref": "#/components/schema/exportParametersSchema",
|
||||
"description": "Desired export format. Must be either \`json\` or \`yaml\`.",
|
||||
"in": "query",
|
||||
"name": "format",
|
||||
"schema": Object {
|
||||
"default": "json",
|
||||
"enum": Array [
|
||||
"json",
|
||||
"yaml",
|
||||
],
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"description": "Whether exported data should be downloaded as a file.",
|
||||
"in": "query",
|
||||
"name": "download",
|
||||
"schema": Object {
|
||||
"anyOf": Array [
|
||||
Object {
|
||||
"type": "boolean",
|
||||
},
|
||||
Object {
|
||||
"minLength": 1,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"type": "number",
|
||||
},
|
||||
],
|
||||
"default": false,
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"description": "Whether strategies should be included in the exported data.",
|
||||
"in": "query",
|
||||
"name": "strategies",
|
||||
"schema": Object {
|
||||
"anyOf": Array [
|
||||
Object {
|
||||
"type": "boolean",
|
||||
},
|
||||
Object {
|
||||
"minLength": 1,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"type": "number",
|
||||
},
|
||||
],
|
||||
"default": true,
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"description": "Whether feature toggles should be included in the exported data.",
|
||||
"in": "query",
|
||||
"name": "featureToggles",
|
||||
"schema": Object {
|
||||
"anyOf": Array [
|
||||
Object {
|
||||
"type": "boolean",
|
||||
},
|
||||
Object {
|
||||
"minLength": 1,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"type": "number",
|
||||
},
|
||||
],
|
||||
"default": true,
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"description": "Whether projects should be included in the exported data.",
|
||||
"in": "query",
|
||||
"name": "projects",
|
||||
"schema": Object {
|
||||
"anyOf": Array [
|
||||
Object {
|
||||
"type": "boolean",
|
||||
},
|
||||
Object {
|
||||
"minLength": 1,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"type": "number",
|
||||
},
|
||||
],
|
||||
"default": true,
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"description": "Whether tag types, tags, and feature_tags should be included in the exported data.",
|
||||
"in": "query",
|
||||
"name": "tags",
|
||||
"schema": Object {
|
||||
"anyOf": Array [
|
||||
Object {
|
||||
"type": "boolean",
|
||||
},
|
||||
Object {
|
||||
"minLength": 1,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"type": "number",
|
||||
},
|
||||
],
|
||||
"default": true,
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"description": "Whether environments should be included in the exported data.",
|
||||
"in": "query",
|
||||
"name": "environments",
|
||||
"schema": Object {
|
||||
"anyOf": Array [
|
||||
Object {
|
||||
"type": "boolean",
|
||||
},
|
||||
Object {
|
||||
"minLength": 1,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"type": "number",
|
||||
},
|
||||
],
|
||||
"default": true,
|
||||
},
|
||||
},
|
||||
],
|
||||
"responses": Object {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { setupApp } from '../../helpers/test-helper';
|
||||
import dbInit from '../../helpers/database-init';
|
||||
import getLogger from '../../../fixtures/no-logger';
|
||||
import SwaggerParser from '@apidevtools/swagger-parser';
|
||||
|
||||
let app;
|
||||
let db;
|
||||
@ -36,3 +37,19 @@ test('should serve the OpenAPI spec', async () => {
|
||||
expect(res.body).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
test('the generated OpenAPI spec is valid', async () => {
|
||||
const { body } = await app.request
|
||||
.get('/docs/openapi.json')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200);
|
||||
|
||||
// this throws if the swagger parser can't parse it correctly
|
||||
// also parses examples, but _does_ do some string coercion in examples
|
||||
try {
|
||||
await SwaggerParser.validate(body);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
41
yarn.lock
41
yarn.lock
@ -9,6 +9,15 @@
|
||||
dependencies:
|
||||
"@jridgewell/trace-mapping" "^0.3.0"
|
||||
|
||||
"@apidevtools/json-schema-ref-parser@9.0.6":
|
||||
version "9.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.6.tgz#5d9000a3ac1fd25404da886da6b266adcd99cf1c"
|
||||
integrity sha512-M3YgsLjI0lZxvrpeGVk9Ap032W6TPQkH6pRAZz81Ac3WUNF79VQooAFnp8umjvVzUmD93NkogxEwbSce7qMsUg==
|
||||
dependencies:
|
||||
"@jsdevtools/ono" "^7.1.3"
|
||||
call-me-maybe "^1.0.1"
|
||||
js-yaml "^3.13.1"
|
||||
|
||||
"@apidevtools/json-schema-ref-parser@^9.0.6":
|
||||
version "9.0.9"
|
||||
resolved "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz"
|
||||
@ -19,7 +28,7 @@
|
||||
call-me-maybe "^1.0.1"
|
||||
js-yaml "^4.1.0"
|
||||
|
||||
"@apidevtools/openapi-schemas@^2.0.4":
|
||||
"@apidevtools/openapi-schemas@^2.0.4", "@apidevtools/openapi-schemas@^2.1.0":
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz"
|
||||
integrity sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==
|
||||
@ -41,6 +50,19 @@
|
||||
call-me-maybe "^1.0.1"
|
||||
z-schema "^5.0.1"
|
||||
|
||||
"@apidevtools/swagger-parser@^10.1.0":
|
||||
version "10.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@apidevtools/swagger-parser/-/swagger-parser-10.1.0.tgz#a987d71e5be61feb623203be0c96e5985b192ab6"
|
||||
integrity sha512-9Kt7EuS/7WbMAUv2gSziqjvxwDbFSg3Xeyfuj5laUODX8o/k/CpsAKiQ8W7/R88eXFTMbJYg6+7uAmOWNKmwnw==
|
||||
dependencies:
|
||||
"@apidevtools/json-schema-ref-parser" "9.0.6"
|
||||
"@apidevtools/openapi-schemas" "^2.1.0"
|
||||
"@apidevtools/swagger-methods" "^3.0.2"
|
||||
"@jsdevtools/ono" "^7.1.3"
|
||||
ajv "^8.6.3"
|
||||
ajv-draft-04 "^1.0.0"
|
||||
call-me-maybe "^1.0.1"
|
||||
|
||||
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13":
|
||||
version "7.12.13"
|
||||
resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz"
|
||||
@ -1417,6 +1439,11 @@ aggregate-error@^3.0.0:
|
||||
clean-stack "^2.0.0"
|
||||
indent-string "^4.0.0"
|
||||
|
||||
ajv-draft-04@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz#3b64761b268ba0b9e668f0b41ba53fce0ad77fc8"
|
||||
integrity sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==
|
||||
|
||||
ajv-formats@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520"
|
||||
@ -1434,7 +1461,7 @@ ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4:
|
||||
json-schema-traverse "^0.4.1"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
ajv@^8.0.0, ajv@^8.11.0:
|
||||
ajv@^8.0.0, ajv@^8.11.0, ajv@^8.6.3:
|
||||
version "8.11.0"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f"
|
||||
integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==
|
||||
@ -4928,10 +4955,10 @@ json-parse-even-better-errors@^2.3.0:
|
||||
resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz"
|
||||
integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
|
||||
|
||||
json-schema-to-ts@^2.5.3:
|
||||
version "2.5.3"
|
||||
resolved "https://registry.yarnpkg.com/json-schema-to-ts/-/json-schema-to-ts-2.5.3.tgz#10a1ad27a3cc6117ae9c652cc583a9e0ed10f0c8"
|
||||
integrity sha512-2vABI+1IZNkChaPfLu7PG192ZY9gvRY00RbuN3VGlNNZkvYRpIECdBZPBVMe41r3wX0sl9emjRyhHT3gTm7HIg==
|
||||
json-schema-to-ts@2.5.4:
|
||||
version "2.5.4"
|
||||
resolved "https://registry.yarnpkg.com/json-schema-to-ts/-/json-schema-to-ts-2.5.4.tgz#64008cf5e203284289922bd622bff82043a1a4ed"
|
||||
integrity sha512-wlaYrGg+aYq0aEjSDY3cAFNzJVD2GvdrVIlvMdrbOLwkaMarXBiX+k0qm5Myb2aI3xjvdqsZoGs63JPS/M8+dg==
|
||||
dependencies:
|
||||
"@types/json-schema" "^7.0.9"
|
||||
ts-algebra "^1.1.1"
|
||||
@ -7658,7 +7685,7 @@ ts-node@10.9.1:
|
||||
|
||||
ts-toolbelt@^9.6.0:
|
||||
version "9.6.0"
|
||||
resolved "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz"
|
||||
resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz#50a25426cfed500d4a09bd1b3afb6f28879edfd5"
|
||||
integrity sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==
|
||||
|
||||
tsc-watch@5.0.3:
|
||||
|
Loading…
Reference in New Issue
Block a user