Skip to content

Commit

Permalink
fix: fix broken OpenAPI spec (#1846)
Browse files Browse the repository at this point in the history
* 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 8425675.

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.
  • Loading branch information
thomasheartman committed Jul 28, 2022
1 parent f6192b5 commit 6afc0a6
Show file tree
Hide file tree
Showing 15 changed files with 407 additions and 139 deletions.
4 changes: 3 additions & 1 deletion package.json
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
2 changes: 0 additions & 2 deletions src/lib/openapi/index.ts
Expand Up @@ -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';
Expand Down Expand Up @@ -139,7 +138,6 @@ export const schemas = {
environmentsSchema,
eventSchema,
eventsSchema,
exportParametersSchema,
featureEnvironmentMetricsSchema,
featureEnvironmentSchema,
featureEventsSchema,
Expand Down
1 change: 0 additions & 1 deletion src/lib/openapi/spec/client-features-query-schema.ts
Expand Up @@ -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: {
Expand Down
32 changes: 0 additions & 32 deletions src/lib/openapi/spec/export-parameters-schema.ts

This file was deleted.

143 changes: 143 additions & 0 deletions src/lib/openapi/spec/export-query-parameters.ts
@@ -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
>;
1 change: 0 additions & 1 deletion src/lib/openapi/spec/feedback-schema.ts
Expand Up @@ -4,7 +4,6 @@ export const feedbackSchema = {
$id: '#/components/schemas/feedbackSchema',
type: 'object',
additionalProperties: false,
required: [],
properties: {
userId: {
type: 'number',
Expand Down
8 changes: 4 additions & 4 deletions src/lib/openapi/spec/playground-feature-schema.ts
Expand Up @@ -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,
Expand All @@ -34,7 +34,7 @@ export const playgroundFeatureSchema = {
},
},
nullable: true,
examples: ['green'],
example: { name: 'green', enabled: true },
},
variants: { type: 'array', items: { $ref: variantSchema.$id } },
},
Expand Down
4 changes: 2 additions & 2 deletions src/lib/openapi/spec/playground-request-schema.ts
Expand Up @@ -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.',
},
{
Expand Down
22 changes: 10 additions & 12 deletions src/lib/openapi/spec/sdk-context-schema.ts
Expand Up @@ -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;
Expand Down
32 changes: 32 additions & 0 deletions src/lib/openapi/util/from-query-params.ts
@@ -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>;
4 changes: 4 additions & 0 deletions src/lib/openapi/validate.ts
Expand Up @@ -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,
Expand Down

0 comments on commit 6afc0a6

Please sign in to comment.