Skip to content

Commit

Permalink
refactor: add soft response schema validation (#1657)
Browse files Browse the repository at this point in the history
* refactor: remove most schema refs

* refactor: generalize request/response schemas

* refactor: simplify schema date formats

* refactor: add soft response schema validation

* refactor: fix emptySchema definition

* refactor: update json-schema-to-ts and use refs
  • Loading branch information
olav committed Jun 8, 2022
1 parent dadbc3a commit 13ef025
Show file tree
Hide file tree
Showing 62 changed files with 903 additions and 636 deletions.
4 changes: 3 additions & 1 deletion package.json
Expand Up @@ -77,6 +77,8 @@
},
"dependencies": {
"@unleash/express-openapi": "^0.2.0",
"ajv": "^8.11.0",
"ajv-formats": "^2.1.1",
"async": "^3.2.3",
"bcryptjs": "^2.4.3",
"compression": "^1.7.4",
Expand All @@ -97,7 +99,7 @@
"helmet": "^5.0.0",
"joi": "^17.3.0",
"js-yaml": "^4.1.0",
"json-schema-to-ts": "^2.0.0",
"json-schema-to-ts": "^2.5.3",
"knex": "^2.0.0",
"log4js": "^6.0.0",
"make-fetch-happen": "^10.1.2",
Expand Down
133 changes: 94 additions & 39 deletions src/lib/openapi/index.ts
@@ -1,22 +1,98 @@
import { OpenAPIV3 } from 'openapi-types';
import { featuresSchema } from './spec/features-schema';
import { overrideSchema } from './spec/override-schema';
import { strategySchema } from './spec/strategy-schema';
import { variantSchema } from './spec/variant-schema';
import { createFeatureSchema } from './spec/create-feature-schema';
import { cloneFeatureSchema } from './spec/clone-feature-schema';
import { constraintSchema } from './spec/constraint-schema';
import { tagSchema } from './spec/tag-schema';
import { tagsResponseSchema } from './spec/tags-response-schema';
import { createFeatureSchema } from './spec/create-feature-schema';
import { createStrategySchema } from './spec/create-strategy-schema';
import { emptySchema } from './spec/empty-schema';
import { featureEnvironmentSchema } from './spec/feature-environment-schema';
import { featureSchema } from './spec/feature-schema';
import { featureStrategySchema } from './spec/feature-strategy-schema';
import { featureVariantsSchema } from './spec/feature-variants-schema';
import { featuresSchema } from './spec/features-schema';
import { mapValues } from '../util/map-values';
import { omitKeys } from '../util/omit-keys';
import { overrideSchema } from './spec/override-schema';
import { parametersSchema } from './spec/parameters-schema';
import { featureEnvironmentSchema } from './spec/feature-environment-schema';
import { emptyResponseSchema } from './spec/empty-response-schema';
import { patchOperationSchema } from './spec/patch-operation-schema';
import { updateFeatureSchema } from './spec/updateFeatureSchema';
import { patchSchema } from './spec/patch-schema';
import { patchesSchema } from './spec/patches-schema';
import { strategySchema } from './spec/strategy-schema';
import { tagSchema } from './spec/tag-schema';
import { tagsSchema } from './spec/tags-schema';
import { updateFeatureSchema } from './spec/update-feature-schema';
import { updateStrategySchema } from './spec/update-strategy-schema';
import { cloneFeatureSchema } from './spec/clone-feature-schema';
import { featureStrategySchema } from './spec/feature-strategy-schema';
import { variantSchema } from './spec/variant-schema';
import { variantsSchema } from './spec/variants-schema';

// Schemas must have $id property on the form "#/components/schemas/mySchema".
export type SchemaId = typeof schemas[keyof typeof schemas]['$id'];

// Schemas must list all $ref schemas in "components", including nested schemas.
export type SchemaRef = typeof schemas[keyof typeof schemas]['components'];

export interface AdminApiOperation
extends Omit<OpenAPIV3.OperationObject, 'tags'> {
tags: ['admin'];
}

export interface ClientApiOperation
extends Omit<OpenAPIV3.OperationObject, 'tags'> {
tags: ['client'];
}

export const schemas = {
cloneFeatureSchema,
constraintSchema,
createFeatureSchema,
createStrategySchema,
emptySchema,
featureEnvironmentSchema,
featureSchema,
featureStrategySchema,
featureVariantsSchema,
featuresSchema,
overrideSchema,
parametersSchema,
patchSchema,
patchesSchema,
strategySchema,
tagSchema,
tagsSchema,
updateFeatureSchema,
updateStrategySchema,
variantSchema,
variantsSchema,
};

export const createRequestSchema = (
schemaName: string,
): OpenAPIV3.RequestBodyObject => {
return {
description: schemaName,
required: true,
content: {
'application/json': {
schema: {
$ref: `#/components/schemas/${schemaName}`,
},
},
},
};
};

export const createResponseSchema = (
schemaName: string,
): OpenAPIV3.ResponseObject => {
return {
description: schemaName,
content: {
'application/json': {
schema: {
$ref: `#/components/schemas/${schemaName}`,
},
},
},
};
};

export const createOpenApiSchema = (
serverUrl?: string,
Expand All @@ -26,13 +102,9 @@ export const createOpenApiSchema = (
servers: serverUrl ? [{ url: serverUrl }] : [],
info: {
title: 'Unleash API',
version: process.env.npm_package_version,
version: process.env.npm_package_version!,
},
security: [
{
apiKey: [],
},
],
security: [{ apiKey: [] }],
components: {
securitySchemes: {
apiKey: {
Expand All @@ -41,26 +113,9 @@ export const createOpenApiSchema = (
name: 'Authorization',
},
},
schemas: {
constraintSchema,
cloneFeatureSchema,
createFeatureSchema,
createStrategySchema,
featureSchema,
featuresSchema,
featureEnvironmentSchema,
featureStrategySchema,
emptyResponseSchema,
overrideSchema,
parametersSchema,
patchOperationSchema,
strategySchema,
updateStrategySchema,
updateFeatureSchema,
variantSchema,
tagSchema,
tagsResponseSchema,
},
schemas: mapValues(schemas, (schema) =>
omitKeys(schema, '$id', 'components'),
),
},
};
};
69 changes: 69 additions & 0 deletions src/lib/openapi/spec/__snapshots__/feature-schema.test.ts.snap
@@ -0,0 +1,69 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`featureSchema constraints 1`] = `
Object {
"data": Object {
"name": "a",
"strategies": Array [
Object {
"constraints": Array [
Object {
"contextName": "a",
},
],
"name": "a",
},
],
},
"errors": Array [
Object {
"instancePath": "/strategies/0/constraints/0",
"keyword": "required",
"message": "must have required property 'operator'",
"params": Object {
"missingProperty": "operator",
},
"schemaPath": "#/components/schemas/constraintSchema/required",
},
],
"schema": "#/components/schemas/featureSchema",
}
`;

exports[`featureSchema overrides 1`] = `
Object {
"data": Object {
"name": "a",
"variants": Array [
Object {
"name": "a",
"overrides": Array [
Object {
"contextName": "a",
"values": "b",
},
],
"payload": Object {
"type": "a",
"value": "b",
},
"stickiness": "a",
"weight": 1,
"weightType": "a",
},
],
},
"errors": Array [
Object {
"instancePath": "/variants/0/overrides/0/values",
"keyword": "type",
"message": "must be array",
"params": Object {
"type": "array",
},
"schemaPath": "#/components/schemas/overrideSchema/properties/values/type",
},
],
"schema": "#/components/schemas/featureSchema",
}
`;
12 changes: 0 additions & 12 deletions src/lib/openapi/spec/clone-feature-request.ts

This file was deleted.

11 changes: 5 additions & 6 deletions src/lib/openapi/spec/clone-feature-schema.ts
@@ -1,6 +1,7 @@
import { createSchemaObject, CreateSchemaType } from '../types';
import { FromSchema } from 'json-schema-to-ts';

const schema = {
export const cloneFeatureSchema = {
$id: '#/components/schemas/cloneFeatureSchema',
type: 'object',
required: ['name'],
properties: {
Expand All @@ -11,9 +12,7 @@ const schema = {
type: 'boolean',
},
},
'components/schemas': {},
components: {},
} as const;

export type CloneFeatureSchema = CreateSchemaType<typeof schema>;

export const cloneFeatureSchema = createSchemaObject(schema);
export type CloneFeatureSchema = FromSchema<typeof cloneFeatureSchema>;
11 changes: 5 additions & 6 deletions src/lib/openapi/spec/constraint-schema.ts
@@ -1,7 +1,8 @@
import { createSchemaObject, CreateSchemaType } from '../types';
import { FromSchema } from 'json-schema-to-ts';
import { ALL_OPERATORS } from '../../util/constants';

const schema = {
export const constraintSchema = {
$id: '#/components/schemas/constraintSchema',
type: 'object',
additionalProperties: false,
required: ['contextName', 'operator'],
Expand Down Expand Up @@ -29,9 +30,7 @@ const schema = {
type: 'string',
},
},
'components/schemas': {},
components: {},
} as const;

export type ConstraintSchema = CreateSchemaType<typeof schema>;

export const constraintSchema = createSchemaObject(schema);
export type ConstraintSchema = FromSchema<typeof constraintSchema>;
12 changes: 0 additions & 12 deletions src/lib/openapi/spec/create-feature-request.ts

This file was deleted.

11 changes: 5 additions & 6 deletions src/lib/openapi/spec/create-feature-schema.ts
@@ -1,6 +1,7 @@
import { createSchemaObject, CreateSchemaType } from '../types';
import { FromSchema } from 'json-schema-to-ts';

const schema = {
export const createFeatureSchema = {
$id: '#/components/schemas/createFeatureSchema',
type: 'object',
required: ['name'],
properties: {
Expand All @@ -17,9 +18,7 @@ const schema = {
type: 'boolean',
},
},
'components/schemas': {},
components: {},
} as const;

export type CreateFeatureSchema = CreateSchemaType<typeof schema>;

export const createFeatureSchema = createSchemaObject(schema);
export type CreateFeatureSchema = FromSchema<typeof createFeatureSchema>;
12 changes: 0 additions & 12 deletions src/lib/openapi/spec/create-strategy-request.ts

This file was deleted.

17 changes: 9 additions & 8 deletions src/lib/openapi/spec/create-strategy-schema.ts
@@ -1,8 +1,9 @@
import { createSchemaObject, CreateSchemaType } from '../types';
import { FromSchema } from 'json-schema-to-ts';
import { parametersSchema } from './parameters-schema';
import { constraintSchema } from './constraint-schema';

const schema = {
export const createStrategySchema = {
$id: '#/components/schemas/createStrategySchema',
type: 'object',
additionalProperties: false,
required: ['name'],
Expand All @@ -23,12 +24,12 @@ const schema = {
$ref: '#/components/schemas/parametersSchema',
},
},
'components/schemas': {
constraintSchema,
parametersSchema,
components: {
schemas: {
constraintSchema,
parametersSchema,
},
},
} as const;

export type CreateStrategySchema = CreateSchemaType<typeof schema>;

export const createStrategySchema = createSchemaObject(schema);
export type CreateStrategySchema = FromSchema<typeof createStrategySchema>;
12 changes: 0 additions & 12 deletions src/lib/openapi/spec/create-tag-request.ts

This file was deleted.

0 comments on commit 13ef025

Please sign in to comment.