Skip to content

Commit 3c54c8f

Browse files
committed
fix: Add back support for date, time, date-time, and duration formats.
re: #262
1 parent 7db0422 commit 3c54c8f

File tree

4 files changed

+35
-57
lines changed

4 files changed

+35
-57
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
"dependencies": {
7979
"@apidevtools/json-schema-ref-parser": "^9.0.3",
8080
"ajv": "^8.3.0",
81+
"ajv-formats": "^2.1.0",
8182
"body-parser": "^1.18.3",
8283
"content-type": "^1.0.4",
8384
"deep-freeze": "0.0.1",

src/oas3/Schema/validators.ts

+2-8
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,7 @@ function getParameterDescription(parameterLocation: ParameterLocation) {
5050

5151
function addCustomFormats(ajv: Ajv, customFormats: CustomFormats) {
5252
for (const key of Object.keys(customFormats)) {
53-
const customFormat = customFormats[key];
54-
if (typeof customFormat === 'function' || customFormat instanceof RegExp) {
55-
ajv.addFormat(key, { type: 'string', validate: customFormat });
56-
} else if (customFormat.type === 'string') {
57-
ajv.addFormat(key, { type: 'string', validate: customFormat.validate });
58-
} else if (customFormat.type === 'number') {
59-
ajv.addFormat(key, { type: 'number', validate: customFormat.validate });
60-
}
53+
ajv.addFormat(key, customFormats[key]);
6154
}
6255
}
6356

@@ -219,6 +212,7 @@ function generateValidator(
219212
removeAdditional: allowTypeCoercion ? 'failing' : false,
220213
allErrors: schemaContext.options.allErrors,
221214
});
215+
222216
addCustomFormats(ajv, customFormats);
223217
const validate = ajv.compile(schema);
224218

src/options.ts

+27-45
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
1+
import ajvFormats from 'ajv-formats';
12
import ld from 'lodash';
2-
3-
import { MimeTypeRegistry } from './utils/mime';
4-
import TextBodyParser from './bodyParsers/TextBodyParser';
5-
import JsonBodyParser from './bodyParsers/JsonBodyParser';
63
import BodyParserWrapper from './bodyParsers/BodyParserWrapper';
4+
import JsonBodyParser from './bodyParsers/JsonBodyParser';
5+
import TextBodyParser from './bodyParsers/TextBodyParser';
76
import { loadControllersSync } from './controllers/loadControllers';
8-
97
import {
10-
CustomFormats,
11-
ExegesisOptions,
12-
StringParser,
8+
Authenticators,
139
BodyParser,
1410
Controllers,
15-
Authenticators,
11+
CustomFormats,
12+
ExegesisOptions,
1613
MimeTypeParser,
1714
ResponseValidationCallback,
15+
StringParser,
1816
} from './types';
19-
import { HandleErrorFunction } from './types/options';
17+
import {
18+
HandleErrorFunction,
19+
NumberCustomFormatChecker,
20+
StringCustomFormatChecker,
21+
CustomFormatChecker,
22+
} from './types/options';
23+
import { MimeTypeRegistry } from './utils/mime';
2024

2125
export interface ExegesisCompiledOptions {
2226
customFormats: CustomFormats;
@@ -34,50 +38,28 @@ export interface ExegesisCompiledOptions {
3438
treatReturnedJsonAsPure: boolean;
3539
}
3640

37-
const INT_32_MIN = -1 * Math.pow(2, 31);
38-
const INT_32_MAX = Math.pow(2, 31) - 1;
39-
// Javascript can only safely support a range of -(2^53 - 1) to (2^53 - 1)
40-
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER
41-
const INT_64_MIN = Number.MIN_SAFE_INTEGER;
42-
const INT_64_MAX = Number.MAX_SAFE_INTEGER;
43-
4441
// See the OAS 3.0 specification for full details about supported formats:
4542
// https://github.com/OAI/OpenAPI-Specification/blob/3.0.2/versions/3.0.2.md#data-types
4643
const defaultValidators: CustomFormats = {
47-
// string:date is taken care of for us:
48-
// https://github.com/epoberezkin/ajv/blob/797dfc8c2b0f51aaa405342916cccb5962dd5f21/lib/compile/formats.js#L34
49-
// string:date-time is from:
50-
// https://tools.ietf.org/html/draft-wright-json-schema-validation-00#section-7.3.1.
51-
// number:int32 and number:int64 are defined as non-fractional integers
52-
// https://tools.ietf.org/html/draft-wright-json-schema-00#section-5.3
53-
int32: {
54-
type: 'number',
55-
validate: (value: number) => value >= INT_32_MIN && value <= INT_32_MAX,
56-
},
57-
int64: {
58-
type: 'number',
59-
validate: (value: number) => value >= INT_64_MIN && value <= INT_64_MAX,
60-
},
61-
double: {
62-
type: 'number',
63-
validate: () => true,
64-
},
65-
float: {
66-
type: 'number',
67-
validate: () => true,
68-
},
44+
// TODO: Support async validators so we don't need all this casting.
45+
int32: ajvFormats.get('int32') as NumberCustomFormatChecker,
46+
int64: ajvFormats.get('int64') as NumberCustomFormatChecker,
47+
double: ajvFormats.get('double') as NumberCustomFormatChecker,
48+
float: ajvFormats.get('float') as NumberCustomFormatChecker,
6949
// Nothing to do for 'password'; this is just a hint for docs.
7050
password: () => true,
7151
// Impossible to validate "binary".
7252
binary: () => true,
73-
// `byte` is base64 encoded data. We *could* validate it here, but if the
74-
// string is long, we might take a while to do it, and the application will
75-
// figure it out quickly enough when it tries to decode it, so we just
76-
// pass it along.
77-
byte: () => true,
53+
byte: ajvFormats.get('byte') as RegExp,
7854
// Not defined by OAS 3, but it's used throughout OAS 3.0.1, so we put it
7955
// here as an alias for 'byte' just in case.
80-
base64: () => true,
56+
base64: ajvFormats.get('byte') as RegExp,
57+
// Various formats we're supposed to support per the JSON Schema RFC.
58+
// https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-00#section-7.3
59+
date: ajvFormats.get('date') as CustomFormatChecker,
60+
time: ajvFormats.get('time') as StringCustomFormatChecker,
61+
'date-time': ajvFormats.get('date-time') as StringCustomFormatChecker,
62+
duration: ajvFormats.get('duration') as RegExp,
8163
};
8264

8365
export function compileOptions(options: ExegesisOptions = {}): ExegesisCompiledOptions {

src/types/options.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { StringParser, BodyParser } from './bodyParser';
2-
import { Controllers, Authenticators, ExegesisPlugin } from './core';
3-
import { ResponseValidationCallback } from './validation';
41
import * as http from 'http';
2+
import { BodyParser, StringParser } from './bodyParser';
3+
import { Authenticators, Controllers, ExegesisPlugin } from './core';
4+
import { ResponseValidationCallback } from './validation';
55

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

1313
export interface StringCustomFormatChecker {
14-
type: 'string';
14+
type?: 'string' | undefined;
1515
validate: CustomFormatChecker;
1616
}
1717

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

0 commit comments

Comments
 (0)