Skip to content

Commit

Permalink
ResponseValidator's Ajv can be useful too.
Browse files Browse the repository at this point in the history
So we return an object that contains both request ajv and response ajv :
```javascript
ajvs = {
  req : 'Ajv object'
  res : 'Ajv object'
}
```
#683
  • Loading branch information
pilerou committed Dec 24, 2021
1 parent ecb8424 commit 8fc7226
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 26 deletions.
29 changes: 25 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1156,17 +1156,35 @@ Instead of initialize OpenApiValidator with middleware, you can get a configured


```javascript
const ajv = await OpenApiValidator.ajv({
const ajvs = await OpenApiValidator.ajv({
apiSpec: './openapi.yaml',
validateRequests: true, // (default)
validateResponses: true, // false by default
});

const req : ReqClass = {
const customObj = {
id : '507f191e810c19729de860ea',
}

ajv.validate(
const isReqValid = ajvs.req.validate(
{
type: 'object',
properties: {
id: {
$ref: '#/components/schemas/ObjectId',
},
},
required: ['token'],
additionalProperties: false,
},
customObj
);

// isReqValid = true
// No error in ajvs.req.errors


const isResValid = ajvs.res.validate(
{
type: 'object',
properties: {
Expand All @@ -1177,8 +1195,11 @@ ajv.validate(
required: ['token'],
additionalProperties: false,
},
req
customObj
);

// isResValid = true
// No error in ajvs.res.errors
```

## FAQ
Expand Down
6 changes: 6 additions & 0 deletions src/framework/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as ajv from 'ajv';
import * as multer from 'multer';
import { Request, Response, NextFunction } from 'express';
import { Ajv } from 'ajv';
export { OpenAPIFrameworkArgs };

export type BodySchema =
Expand Down Expand Up @@ -155,6 +156,11 @@ export namespace OpenAPIV3 {
version: string;
}

export interface Ajvs {
req?: Ajv,
res?: Ajv
}

export interface ContactObject {
name?: string;
url?: string;
Expand Down
5 changes: 5 additions & 0 deletions src/middlewares/openapi.response.validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
} from '../framework/types';
import * as mediaTypeParser from 'media-typer';
import * as contentTypeParser from 'content-type';
import { Ajv } from 'ajv';

interface ValidateResult {
validators: { [key: string]: ajv.ValidateFunction };
Expand Down Expand Up @@ -318,4 +319,8 @@ export class ResponseValidator {
mediaTypeParsed.subtype === 'json' || mediaTypeParsed.suffix === 'json'
);
}

public getAJV() : Ajv {
return this.ajvBody;
}
}
13 changes: 11 additions & 2 deletions src/openapi.validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { defaultSerDes } from './framework/base.serdes';
import { SchemaPreprocessor } from './middlewares/parsers/schema.preprocessor';
import { AjvOptions } from './framework/ajv/options';
import { Ajv } from 'ajv';
import Ajvs = OpenAPIV3.Ajvs;

export {
OpenApiValidatorOpts,
Expand Down Expand Up @@ -91,7 +92,7 @@ export class OpenApiValidator {
this.ajvOpts = new AjvOptions(options);
}

installAjv(spec: Promise<Spec>): Promise<Ajv> {
installAjv(spec: Promise<Spec>): Promise<Ajvs> {
return spec
.then((spec) => {
const apiDoc = spec.apiDoc;
Expand All @@ -107,7 +108,15 @@ export class OpenApiValidator {
responseApiDoc: sp.apiDocRes,
error: null,
};*/
return new middlewares.RequestValidator(apiDoc, this.ajvOpts.request).getAJV();
return {
req : new middlewares.RequestValidator(apiDoc, this.ajvOpts.request).getAJV(),
res : new middlewares.ResponseValidator(
apiDoc,
this.ajvOpts.response,
// This has already been converted from boolean if required
this.options.validateResponses as ValidateResponseOpts,)
.getAJV(),
};
});
}

Expand Down
106 changes: 86 additions & 20 deletions test/ajv.return.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { expect } from 'chai';

import { date, dateTime } from '../src/framework/base.serdes';
import * as OpenApiValidator from '../src';
import { Ajv } from 'ajv';
import { OpenAPIV3 } from '../src/framework/types';
import Ajvs = OpenAPIV3.Ajvs;

const apiSpecPath = path.join('test', 'resources', 'serdes.yaml');

Expand All @@ -20,10 +21,25 @@ class ObjectID {
}

describe('ajv.return', () => {
let ajv : Ajv = null;
let ajvs : Ajvs = null;

class ReqRes {
id?: string|ObjectID
}

const customSchema = {
type: 'object',
properties: {
id: {
$ref: '#/components/schemas/ObjectId',
},
},
required: ['id'],
additionalProperties: false,
};

before(async () => {
ajv = await OpenApiValidator.ajv({
ajvs = await OpenApiValidator.ajv({
apiSpec: apiSpecPath,
validateRequests: {
coerceTypes: true
Expand All @@ -44,30 +60,80 @@ describe('ajv.return', () => {
});
});

it('should control BAD id format and throw an error', async () => {
class ReqClass {
id: string|ObjectID
}

const req : ReqClass = {
it('should control request and deserialize string to object', async () => {
const req : ReqRes = {
id : '507f191e810c19729de860ea',
}

ajv.validate(
{
type: 'object',
properties: {
id: {
$ref: '#/components/schemas/ObjectId',
},
},
required: ['token'],
additionalProperties: false,
},
const isValid = ajvs.req.validate(
customSchema,
req
);
expect(isValid).to.be.equal(true);
expect(ajvs.req.errors).to.be.equal(null);
expect(req.id instanceof ObjectID).to.be.true;
});

it('should control request and return error if id is not set', async () => {
const req : ReqRes = {
// No id but it is required
// id : '507f191e810c19729de860ea',
}

const isValid = ajvs.req.validate(
customSchema,
req
);
expect(isValid).to.be.equal(false);
expect(ajvs.req.errors.length).to.be.equal(1);
expect(ajvs.req.errors[0].message).to.be.equal('should have required property \'id\'');
});

it('should control request and return error if id is in bad format', async () => {
const req : ReqRes = {
id : 'notAnObjectId',
}

const isValid = ajvs.req.validate(
customSchema,
req
);
expect(isValid).to.be.equal(false);
expect(ajvs.req.errors.length).to.be.equal(1);
expect(ajvs.req.errors[0].message).to.be.equal('should match pattern "^[0-9a-fA-F]{24}$"');
});


it('should control response and serialize object to string', async () => {
const res : ReqRes = {
id : new ObjectID('507f191e810c19729de860ea'),
}

const isValid = ajvs.res.validate(
customSchema,
res
);
expect(res.id).to.be.equal('507f191e810c19729de860ea');
expect(isValid).to.be.equal(true);
expect(ajvs.res.errors).to.be.equal(null);
});

it('should control response and return an error if id is not set', async () => {

const res : ReqRes = {
// No id but it is required
// id : '507f191e810c19729de860ea',
//id : new ObjectID('507f191e810c19729de860ea'),
}

const isValid = ajvs.res.validate(
customSchema,
res
);
expect(isValid).to.be.equal(false);
expect(ajvs.res.errors.length).to.be.equal(1);
expect(ajvs.res.errors[0].message).to.be.equal('should have required property \'id\'');
});
});


Expand Down

0 comments on commit 8fc7226

Please sign in to comment.