Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: observe validateApiSpec and avoid schema re-checks (performance) (…
…#544) * fix: observe validateApiSpec and avoid schema re-checks * update deps * remove unused error * chore: update lockfile and patch version * chore: update npmignore * chore: change history * fix: observe validateApiSpec and avoid schema re-checks * remove unused error * chore: update excludes * add coverage * cover serdes opts * test: cover options branches * fix: optons * remove commented code * doc: update comment
- Loading branch information
Showing
7 changed files
with
237 additions
and
92 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ | |
/assets | ||
/examples | ||
/example | ||
/packages | ||
/docs | ||
node_modules | ||
/src | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import ajv = require('ajv'); | ||
import { | ||
OpenApiValidatorOpts, | ||
Options, | ||
RequestValidatorOptions, | ||
ValidateRequestOpts, | ||
ValidateResponseOpts, | ||
} from '../types'; | ||
|
||
export class AjvOptions { | ||
private options: OpenApiValidatorOpts; | ||
constructor(options: OpenApiValidatorOpts) { | ||
this.options = options; | ||
} | ||
get preprocessor(): ajv.Options { | ||
return this.baseOptions(); | ||
} | ||
|
||
get response(): ajv.Options { | ||
const { coerceTypes, removeAdditional } = <ValidateResponseOpts>( | ||
this.options.validateResponses | ||
); | ||
return { | ||
...this.baseOptions(), | ||
useDefaults: false, | ||
coerceTypes, | ||
removeAdditional, | ||
}; | ||
} | ||
|
||
get request(): RequestValidatorOptions { | ||
const { allowUnknownQueryParameters, coerceTypes, removeAdditional } = < | ||
ValidateRequestOpts | ||
>this.options.validateRequests; | ||
return { | ||
...this.baseOptions(), | ||
allowUnknownQueryParameters, | ||
coerceTypes, | ||
removeAdditional, | ||
}; | ||
} | ||
|
||
get multipart(): Options { | ||
return this.baseOptions(); | ||
} | ||
|
||
private baseOptions(): Options { | ||
const { | ||
coerceTypes, | ||
unknownFormats, | ||
validateFormats, | ||
serDes, | ||
} = this.options; | ||
const serDesMap = {}; | ||
for (const serDesObject of serDes) { | ||
if (!serDesMap[serDesObject.format]) { | ||
serDesMap[serDesObject.format] = serDesObject; | ||
} else { | ||
if (serDesObject.serialize) { | ||
serDesMap[serDesObject.format].serialize = serDesObject.serialize; | ||
} | ||
if (serDesObject.deserialize) { | ||
serDesMap[serDesObject.format].deserialize = serDesObject.deserialize; | ||
} | ||
} | ||
} | ||
|
||
return { | ||
validateSchema: false, // this is true for statup validation, thus it can be bypassed here | ||
nullable: true, | ||
coerceTypes, | ||
useDefaults: true, | ||
removeAdditional: false, | ||
unknownFormats, | ||
format: validateFormats, | ||
formats: this.options.formats.reduce((acc, f) => { | ||
acc[f.name] = { | ||
type: f.type, | ||
validate: f.validate, | ||
}; | ||
return acc; | ||
}, {}), | ||
serDesMap: serDesMap, | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import { expect } from 'chai'; | ||
import { AjvOptions } from '../src/framework/ajv/options'; | ||
|
||
describe('AjvOptions', () => { | ||
// hard code base options | ||
// These are normalized when express-openapi-validator parses options, however | ||
// this test bypasses that, thus we manually set them to expected values | ||
const baseOptions = { | ||
apiSpec: './spec', | ||
validateApiSpec: false, | ||
validateRequests: true, | ||
validateResponses: { | ||
coerceTypes: false, | ||
removeAdditional: true, | ||
}, | ||
serDes: [], | ||
formats: [], | ||
}; | ||
|
||
it('should not validate schema for requests since schema is validated on startup', async () => { | ||
const ajv = new AjvOptions(baseOptions); | ||
const options = ajv.request; | ||
expect(options.validateSchema).to.be.false; | ||
}); | ||
|
||
it('should not validate schema for response since schema is validated on startup', async () => { | ||
const ajv = new AjvOptions(baseOptions); | ||
const options = ajv.response; | ||
expect(options.validateSchema).to.be.false; | ||
}); | ||
|
||
it('should not validate schema for preprocessor since schema is validated on startup', async () => { | ||
const ajv = new AjvOptions(baseOptions); | ||
const options = ajv.preprocessor; | ||
expect(options.validateSchema).to.be.false; | ||
}); | ||
|
||
it('should not validate schema for multipar since schema is validated on startup', async () => { | ||
const ajv = new AjvOptions(baseOptions); | ||
const options = ajv.multipart; | ||
expect(options.validateSchema).to.be.false; | ||
}); | ||
|
||
it('should set serdes deserialize', () => { | ||
const ajv = new AjvOptions({ | ||
...baseOptions, | ||
serDes: [ | ||
{ | ||
format: 'custom-1', | ||
deserialize: () => 'test', | ||
}, | ||
], | ||
}); | ||
const options = ajv.multipart; | ||
expect(options.serDesMap['custom-1']).has.property('deserialize'); | ||
expect(options.serDesMap['custom-1']).does.not.have.property('serialize'); | ||
}); | ||
|
||
it('should set serdes serialize', () => { | ||
const ajv = new AjvOptions({ | ||
...baseOptions, | ||
serDes: [ | ||
{ | ||
format: 'custom-1', | ||
serialize: () => 'test', | ||
}, | ||
], | ||
}); | ||
const options = ajv.multipart; | ||
expect(options.serDesMap).has.property('custom-1'); | ||
expect(options.serDesMap['custom-1']).has.property('serialize'); | ||
expect(options.serDesMap['custom-1']).does.not.have.property('deserialize'); | ||
}); | ||
|
||
it('should set serdes serialize and deserialize', () => { | ||
const ajv = new AjvOptions({ | ||
...baseOptions, | ||
serDes: [ | ||
{ | ||
format: 'custom-1', | ||
serialize: () => 'test', | ||
deserialize: (s) => {}, | ||
}, | ||
], | ||
}); | ||
const options = ajv.multipart; | ||
expect(options.serDesMap).has.property('custom-1'); | ||
expect(options.serDesMap['custom-1']).has.property('serialize'); | ||
expect(options.serDesMap['custom-1']).has.property('deserialize'); | ||
}); | ||
|
||
it('should set serdes serialize and deserialize separately', () => { | ||
const ajv = new AjvOptions({ | ||
...baseOptions, | ||
serDes: [ | ||
{ | ||
format: 'custom-1', | ||
serialize: () => 'test', | ||
}, | ||
{ | ||
format: 'custom-1', | ||
deserialize: () => 'test', | ||
}, | ||
{ | ||
format: 'custom-1', | ||
serialize: () => 'test', | ||
}, | ||
], | ||
}); | ||
const options = ajv.multipart; | ||
expect(options.serDesMap).has.property('custom-1'); | ||
expect(options.serDesMap['custom-1']).has.property('serialize'); | ||
expect(options.serDesMap['custom-1']).has.property('deserialize'); | ||
}); | ||
}); |