Skip to content

Commit

Permalink
Merge pull request #436 from cdimascio/formats
Browse files Browse the repository at this point in the history
feat: adds support for custom formats
  • Loading branch information
cdimascio committed Nov 2, 2020
2 parents 10de36b + 124afec commit a0ce7e8
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 7 deletions.
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,11 @@ OpenApiValidator.middleware({
},
operationHandlers: false | 'operations/base/path' | { ... },
ignorePaths: /.*\/pets$/,
formats: [{
name: 'my-format',
type: 'string',
validate: v => /^[A-Z]$/.test(v)
}],
unknownFormats: ['phone-number', 'uuid'],
fileUploader: { ... } | true | false,
$refParser: {
Expand Down Expand Up @@ -583,6 +588,40 @@ Determines whether the validator should validate securities e.g. apikey, basic,
}
```

### 鈻笍 formats (optional)

Defines a list of custome formats.

- `[{ ... }]` - array of custom format objects. Each object must have the following properties:
- name: string (required) - the format name
- validate: (v: any) => boolean (required) - the validation function
- type: 'string' | 'number' (optional) - the format's type

e.g.

```javascript
[
{
name: 'my-three-digit-format',
type: 'number',
validate: v => /^\d{3}$/.test(v.toString()) // number with 3 digits
},
{
name: 'my-three-letter-format',
type: 'number',
validate: v => /^[A-Za-z]{3}$/.test(v) // string with 3 letters
},
]
```

Then use it in a spec e.g.

```yaml
my_property:
type: string
format: my-three-letter-format'
```

### 鈻笍 validateFormats (optional)

Specifies the strictness of validation of string formats.
Expand Down
2 changes: 1 addition & 1 deletion examples/1-standard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@
"express-openapi-validator": "file:../.."
},
"devDependencies": {
"nodemon": "^2.0.4"
"nodemon": "^2.0.6"
}
}
7 changes: 7 additions & 0 deletions src/framework/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ export type OperationHandlerOptions = {
resolver: Function;
};

export type Format = {
name: string;
type?: 'number' | 'string';
validate: (v: any) => boolean;
};

export interface OpenApiValidatorOpts {
apiSpec: OpenAPIV3.Document | string;
validateResponses?: boolean | ValidateResponseOpts;
Expand All @@ -65,6 +71,7 @@ export interface OpenApiValidatorOpts {
securityHandlers?: SecurityHandlers;
coerceTypes?: boolean | 'array';
unknownFormats?: true | string[] | 'ignore';
formats?: Format[];
fileUploader?: boolean | multer.Options;
multerOpts?: multer.Options;
$refParser?: {
Expand Down
24 changes: 22 additions & 2 deletions src/openapi.validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,15 @@ export class OpenApiValidator {
this.validateOptions(options);
this.normalizeOptions(options);

if (options.unknownFormats == null) options.unknownFormats === true;
if (options.coerceTypes == null) options.coerceTypes = true;
if (options.validateRequests == null) options.validateRequests = true;
if (options.validateResponses == null) options.validateResponses = false;
if (options.validateSecurity == null) options.validateSecurity = true;
if (options.fileUploader == null) options.fileUploader = {};
if (options.$refParser == null) options.$refParser = { mode: 'bundle' };
if (options.unknownFormats == null) options.unknownFormats === true;
if (options.validateFormats == null) options.validateFormats = 'fast';
if (options.formats == null) options.formats = [];

if (typeof options.operationHandlers === 'string') {
/**
Expand Down Expand Up @@ -264,6 +265,7 @@ export class OpenApiValidator {
unknownFormats,
validateRequests,
validateFormats,
formats,
} = this.options;
const { allowUnknownQueryParameters } = <ValidateRequestOpts>(
validateRequests
Expand All @@ -276,6 +278,13 @@ export class OpenApiValidator {
unknownFormats,
allowUnknownQueryParameters,
format: validateFormats,
formats: formats.reduce((acc, f) => {
acc[f.name] = {
type: f.type,
validate: f.validate,
};
return acc;
}, {}),
});
return (req, res, next) => requestValidator.validate(req, res, next);
}
Expand All @@ -286,6 +295,7 @@ export class OpenApiValidator {
unknownFormats,
validateResponses,
validateFormats,
formats,
} = this.options;
const { removeAdditional } = <ValidateResponseOpts>validateResponses;

Expand All @@ -295,6 +305,13 @@ export class OpenApiValidator {
removeAdditional,
unknownFormats,
format: validateFormats,
formats: formats.reduce((acc, f) => {
acc[f.name] = {
type: f.type,
valdiate: f.validate,
};
return acc;
}, {}),
}).validate();
}

Expand All @@ -318,7 +335,10 @@ export class OpenApiValidator {
expressRoute.indexOf(baseUrl) === 0
? expressRoute.substring(baseUrl.length)
: expressRoute;
router[method.toLowerCase()](path, resolver(basePath, route, context.apiDoc));
router[method.toLowerCase()](
path,
resolver(basePath, route, context.apiDoc),
);
}
}
return router;
Expand Down
73 changes: 69 additions & 4 deletions test/formats.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,31 @@ describe('path params', () => {
before(async () => {
// set up express app
app = await createApp(
{ apiSpec: apiSpecPath },
{
apiSpec: apiSpecPath,
formats: [
{
name: 'three-digits',
type: 'number',
validate: (v) => {
return /^[0-9]{3}$/.test(v.toString())
},
},
{
name: 'three-letters',
type: 'string',
validate: (v) => {
return /^[A-Za-z]{3}$/.test(v);
},
},
],
},
3005,
(app) => {
app.get(`${app.basePath}/fees`, (req, res) => {
res.json([req.query]);
});
app.get(`${app.basePath}/fees`, (req, res) => res.json([req.query]));
app.all(`${app.basePath}/formats/1`, (req, res) =>
res.json([req.query]),
);
app.use((err, req, res, next) => {
console.error(err);
res.status(err.status ?? 500).json({
Expand Down Expand Up @@ -64,6 +83,7 @@ describe('path params', () => {
console.log(body);
expect(body[0]).to.have.property('amount').that.equals(0.0);
}));

it('should handle float type with positive value', async () =>
request(app)
.get(`${app.basePath}/fees`)
Expand All @@ -77,4 +97,49 @@ describe('path params', () => {
console.log(body);
expect(body[0]).to.have.property('amount').that.equals(10.0);
}));

// TODO test fails - bug fix me
it('should require the query parameter number_id has 3 digits', async () =>
request(app)
.get(`${app.basePath}/formats/1`)
.query({
number_id: 3342,
})
.expect(400)
.then((r) => {
const body = r.body;
expect(body.message).to.contain('three-digits');
}));

it('should require the query parameter string_id has 3 letters', async () =>
request(app)
.get(`${app.basePath}/formats/1`)
.query({
string_id: 123,
})
.expect(400)
.then((r) => {
const body = r.body;
expect(body.message).to.contain('three-letters');
}));

it('should require the query parameter string_id has 3 letters', async () =>
request(app)
.post(`${app.basePath}/formats/1`)
.send({
string_id: '12',
})
.expect(400)
.then((r) => {
const body = r.body;
expect(body.message).to.contain('three-letters');
}));

it('should return success if the query parameter string_id has 3 letters', async () =>
request(app)
.post(`${app.basePath}/formats/1`)
.send({
string_id: 'abc',
})
.expect(200));
});
33 changes: 33 additions & 0 deletions test/resources/formats.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,36 @@ paths:
'200':
description: response

/formats/1:
get:
parameters:
- name: string_id
in: query
schema:
type: string
format: three-letters
- name: number_id
in: query
schema:
type: number
format: three-digits
responses:
'200':
description: response
post:
requestBody:
content:
application/json:
schema:
type: object
properties:
number_id:
type: number
format: three-digits
string_id:
type: string
format: three-letters
responses:
'200':
description: response

0 comments on commit a0ce7e8

Please sign in to comment.