Skip to content

Commit

Permalink
fix: Add back support for date, time, date-time, and duration formats.
Browse files Browse the repository at this point in the history
re: #262
  • Loading branch information
jwalton committed May 15, 2021
1 parent 7db0422 commit 3c54c8f
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 57 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"dependencies": {
"@apidevtools/json-schema-ref-parser": "^9.0.3",
"ajv": "^8.3.0",
"ajv-formats": "^2.1.0",
"body-parser": "^1.18.3",
"content-type": "^1.0.4",
"deep-freeze": "0.0.1",
Expand Down
10 changes: 2 additions & 8 deletions src/oas3/Schema/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,7 @@ function getParameterDescription(parameterLocation: ParameterLocation) {

function addCustomFormats(ajv: Ajv, customFormats: CustomFormats) {
for (const key of Object.keys(customFormats)) {
const customFormat = customFormats[key];
if (typeof customFormat === 'function' || customFormat instanceof RegExp) {
ajv.addFormat(key, { type: 'string', validate: customFormat });
} else if (customFormat.type === 'string') {
ajv.addFormat(key, { type: 'string', validate: customFormat.validate });
} else if (customFormat.type === 'number') {
ajv.addFormat(key, { type: 'number', validate: customFormat.validate });
}
ajv.addFormat(key, customFormats[key]);
}
}

Expand Down Expand Up @@ -219,6 +212,7 @@ function generateValidator(
removeAdditional: allowTypeCoercion ? 'failing' : false,
allErrors: schemaContext.options.allErrors,
});

addCustomFormats(ajv, customFormats);
const validate = ajv.compile(schema);

Expand Down
72 changes: 27 additions & 45 deletions src/options.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
import ajvFormats from 'ajv-formats';
import ld from 'lodash';

import { MimeTypeRegistry } from './utils/mime';
import TextBodyParser from './bodyParsers/TextBodyParser';
import JsonBodyParser from './bodyParsers/JsonBodyParser';
import BodyParserWrapper from './bodyParsers/BodyParserWrapper';
import JsonBodyParser from './bodyParsers/JsonBodyParser';
import TextBodyParser from './bodyParsers/TextBodyParser';
import { loadControllersSync } from './controllers/loadControllers';

import {
CustomFormats,
ExegesisOptions,
StringParser,
Authenticators,
BodyParser,
Controllers,
Authenticators,
CustomFormats,
ExegesisOptions,
MimeTypeParser,
ResponseValidationCallback,
StringParser,
} from './types';
import { HandleErrorFunction } from './types/options';
import {
HandleErrorFunction,
NumberCustomFormatChecker,
StringCustomFormatChecker,
CustomFormatChecker,
} from './types/options';
import { MimeTypeRegistry } from './utils/mime';

export interface ExegesisCompiledOptions {
customFormats: CustomFormats;
Expand All @@ -34,50 +38,28 @@ export interface ExegesisCompiledOptions {
treatReturnedJsonAsPure: boolean;
}

const INT_32_MIN = -1 * Math.pow(2, 31);
const INT_32_MAX = Math.pow(2, 31) - 1;
// Javascript can only safely support a range of -(2^53 - 1) to (2^53 - 1)
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER
const INT_64_MIN = Number.MIN_SAFE_INTEGER;
const INT_64_MAX = Number.MAX_SAFE_INTEGER;

// See the OAS 3.0 specification for full details about supported formats:
// https://github.com/OAI/OpenAPI-Specification/blob/3.0.2/versions/3.0.2.md#data-types
const defaultValidators: CustomFormats = {
// string:date is taken care of for us:
// https://github.com/epoberezkin/ajv/blob/797dfc8c2b0f51aaa405342916cccb5962dd5f21/lib/compile/formats.js#L34
// string:date-time is from:
// https://tools.ietf.org/html/draft-wright-json-schema-validation-00#section-7.3.1.
// number:int32 and number:int64 are defined as non-fractional integers
// https://tools.ietf.org/html/draft-wright-json-schema-00#section-5.3
int32: {
type: 'number',
validate: (value: number) => value >= INT_32_MIN && value <= INT_32_MAX,
},
int64: {
type: 'number',
validate: (value: number) => value >= INT_64_MIN && value <= INT_64_MAX,
},
double: {
type: 'number',
validate: () => true,
},
float: {
type: 'number',
validate: () => true,
},
// TODO: Support async validators so we don't need all this casting.
int32: ajvFormats.get('int32') as NumberCustomFormatChecker,
int64: ajvFormats.get('int64') as NumberCustomFormatChecker,
double: ajvFormats.get('double') as NumberCustomFormatChecker,
float: ajvFormats.get('float') as NumberCustomFormatChecker,
// Nothing to do for 'password'; this is just a hint for docs.
password: () => true,
// Impossible to validate "binary".
binary: () => true,
// `byte` is base64 encoded data. We *could* validate it here, but if the
// string is long, we might take a while to do it, and the application will
// figure it out quickly enough when it tries to decode it, so we just
// pass it along.
byte: () => true,
byte: ajvFormats.get('byte') as RegExp,
// Not defined by OAS 3, but it's used throughout OAS 3.0.1, so we put it
// here as an alias for 'byte' just in case.
base64: () => true,
base64: ajvFormats.get('byte') as RegExp,
// Various formats we're supposed to support per the JSON Schema RFC.
// https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-00#section-7.3
date: ajvFormats.get('date') as CustomFormatChecker,
time: ajvFormats.get('time') as StringCustomFormatChecker,
'date-time': ajvFormats.get('date-time') as StringCustomFormatChecker,
duration: ajvFormats.get('duration') as RegExp,
};

export function compileOptions(options: ExegesisOptions = {}): ExegesisCompiledOptions {
Expand Down
9 changes: 5 additions & 4 deletions src/types/options.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { StringParser, BodyParser } from './bodyParser';
import { Controllers, Authenticators, ExegesisPlugin } from './core';
import { ResponseValidationCallback } from './validation';
import * as http from 'http';
import { BodyParser, StringParser } from './bodyParser';
import { Authenticators, Controllers, ExegesisPlugin } from './core';
import { ResponseValidationCallback } from './validation';

/**
* A function which validates custom formats.
Expand All @@ -11,7 +11,7 @@ export type CustomFormatChecker = RegExp | ((value: string) => boolean);
export type HandleErrorFunction = (err: Error, context: { req: http.IncomingMessage }) => any;

export interface StringCustomFormatChecker {
type: 'string';
type?: 'string' | undefined;
validate: CustomFormatChecker;
}

Expand All @@ -28,6 +28,7 @@ export interface NumberCustomFormatChecker {
* false the the string is invalid.
* * A `{validate, type}` object, where `type` is either "string" or "number",
* and validate is a `function(string) : boolean`.
* * Any `ajv` format.
*/
export interface CustomFormats {
[key: string]: CustomFormatChecker | StringCustomFormatChecker | NumberCustomFormatChecker;
Expand Down

0 comments on commit 3c54c8f

Please sign in to comment.