This repository has been archived by the owner on Nov 8, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 22
Adds JSON Schema Draft 6/7 support #355
Merged
Merged
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
57f1b05
feat: adds JSON Schema Draft 6/7 support
artem-zakharchenko e82a1e2
refactor: stores JSON Schema Draft versions as "draftV{x}"
artem-zakharchenko 28d94b7
refactor: sets implicit JSON Schema version, if not set
artem-zakharchenko 1930ec5
refactor: parses given data in case of string in JsonSchemaValidator
artem-zakharchenko cb8c01a
refactor: adjusts "validateBody" to new JsonSchema call signature
artem-zakharchenko f9f9357
refactor: adjusts "validateHeaders" to new JsonSchema call signature
artem-zakharchenko e8eadf9
refactor: adjusts "HeadersJsonExample" to new JsonSchema signature
artem-zakharchenko ae746ae
refactor: removes "amanda-to-gavel-shared" test suites
artem-zakharchenko 549bc03
refactor: adjusts call signature of "TextDiff.validate()"
artem-zakharchenko 0304227
refactor: fixes unsupported schema test suite for JsonSchemaValidator
artem-zakharchenko cd142fc
refactor: Simplifies tv4 to headers coercion in HeadersJsonExample
artem-zakharchenko d8a6e56
refactor: fixes the order of expected/actual values in custom chai as…
artem-zakharchenko a74f245
refactor: removes the "json-ajv" test suite
artem-zakharchenko cf4952f
refactor: removes "JsonSchema" dependency from "json-example" test suite
artem-zakharchenko f33466f
test: adjusts JsonSchemaLegacy suite to assert unmatching value again…
artem-zakharchenko 7537859
feat: uses ["path", "array"] structure for "location.property" value
artem-zakharchenko 6223720
chore: installs "@rollup/plugin-json"
artem-zakharchenko 75650c2
chore: uses "@rollup/plugin-json" to import ".json" modules
artem-zakharchenko 9bccf12
refactor: uses raw TV4 error format for header-related error messages
artem-zakharchenko 593ea41
feat: infers JSON Schema Draft 7 when schema is boolean
artem-zakharchenko File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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,31 @@ | ||
const jsonPointer = require('json-pointer'); | ||
|
||
function splitProperty(property) { | ||
return property.split(/\.|\[|\]/).filter(Boolean); | ||
} | ||
|
||
function reduceProperties(acc, property) { | ||
return acc.concat(splitProperty(property)); | ||
} | ||
|
||
/** | ||
* Converts legacy (Amanda/TV4) error messages | ||
* to the Gavel-compliant structure. | ||
*/ | ||
function toGavelResult(legacyErrors) { | ||
return Array.from({ length: legacyErrors.length }, (_, index) => { | ||
const item = legacyErrors[index]; | ||
const propertyPath = item.property.reduce(reduceProperties, []); | ||
const pointer = jsonPointer.compile(propertyPath); | ||
|
||
return { | ||
message: item.message, | ||
location: { | ||
pointer, | ||
property: propertyPath | ||
} | ||
}; | ||
}); | ||
} | ||
|
||
module.exports = toGavelResult; |
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,162 @@ | ||
const amanda = require('amanda'); | ||
const tv4 = require('tv4'); | ||
const jsonPointer = require('json-pointer'); | ||
|
||
const { JsonSchemaValidator, META_SCHEMA } = require('./json-schema-next'); | ||
const { ValidationErrors } = require('./validation-errors'); | ||
const toGavelResult = require('../utils/to-gavel-result'); | ||
|
||
/** | ||
* Returns a proper article for a given string. | ||
* @param {string} str | ||
* @returns {string} | ||
*/ | ||
function getArticle(str) { | ||
return ['a', 'e', 'i', 'o', 'u'].includes(str.toLowerCase()) ? 'an' : 'a'; | ||
} | ||
|
||
const jsonSchemaOptions = { | ||
singleError: false, | ||
messages: { | ||
minLength: (prop, val, validator) => | ||
`The ${prop} property must be at least ${validator} characters long (currently ${val.length} characters long).`, | ||
maxLength: (prop, val, validator) => | ||
`The ${prop} property must not exceed ${validator} characters (currently${val.length} characters long).`, | ||
length: (prop, val, validator) => | ||
`The ${prop} property must be exactly ${validator} characters long (currently ${val.length} characters long).`, | ||
format: (prop, val, validator) => | ||
`The ${prop} property must be ${getArticle( | ||
validator[0] | ||
)} ${validator} (current value is ${JSON.stringify(val)}).`, | ||
type: (prop, val, validator) => | ||
`The ${prop} property must be ${getArticle( | ||
validator[0] | ||
)} ${validator} (current value is ${JSON.stringify(val)})."`, | ||
except: (prop, val) => `The ${prop} property must not be ${val}.`, | ||
minimum: (prop, val, validator) => | ||
`The minimum value of the ${prop} must be ${validator} (current value is ${JSON.stringify( | ||
val | ||
)}).`, | ||
maximum: (prop, val, validator) => | ||
`The maximum value of the ${prop} must be ${validator} (current value is ${JSON.stringify( | ||
val | ||
)}).`, | ||
pattern: (prop, val, validator) => | ||
`The ${prop} value (${val}) does not match the ${validator} pattern.`, | ||
maxItems: (prop, val, validator) => | ||
`The ${prop} property must not contain more than ${validator} items (currently contains ${val.length} items).`, | ||
minItems: (prop, val, validator) => | ||
`The ${prop} property must contain at least ${validator} items (currently contains ${val.length} items).`, | ||
divisibleBy: (prop, val, validator) => | ||
`The ${prop} property is not divisible by ${validator} (current value is ${JSON.stringify( | ||
val | ||
)}).`, | ||
uniqueItems: (prop) => `All items in the ${prop} property must be unique.` | ||
} | ||
}; | ||
|
||
class JsonSchemaLegacy extends JsonSchemaValidator { | ||
validateSchema() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Overriding |
||
const { jsonSchema, jsonMetaSchema } = this; | ||
|
||
// In case schema version is unidentified, | ||
// assume JSON Schema Draft V3. | ||
const metaSchema = jsonMetaSchema || META_SCHEMA.draftV3; | ||
|
||
tv4.reset(); | ||
tv4.addSchema('', metaSchema); | ||
tv4.addSchema(metaSchema.$schema, metaSchema); | ||
const validationResult = tv4.validateResult(jsonSchema, metaSchema); | ||
|
||
return validationResult.valid; | ||
} | ||
|
||
validate(data) { | ||
const parsedData = this.parseData(data); | ||
|
||
switch (this.jsonSchemaVersion) { | ||
case 'draftV3': | ||
return this.validateUsingAmanda(parsedData); | ||
case 'draftV4': | ||
return this.validateUsingTV4(parsedData); | ||
default: | ||
throw new Error( | ||
`Attempted to use JsonSchemaLegacy on non-legacy JSON Schema ${this.jsonSchemaVersion}!` | ||
); | ||
} | ||
} | ||
|
||
validateUsingAmanda(data) { | ||
let errors = { | ||
length: 0, | ||
errorMessages: {} | ||
}; | ||
|
||
try { | ||
amanda.validate(data, this.jsonSchema, jsonSchemaOptions, (error) => { | ||
if (error && error.length > 0) { | ||
for (let i = 0; i < error.length; i++) { | ||
if (error[i].property === '') { | ||
error[i].property = []; | ||
} | ||
} | ||
|
||
errors = new ValidationErrors(error); | ||
} | ||
}); | ||
} catch (internalError) { | ||
errors = new ValidationErrors({ | ||
'0': { | ||
property: [], | ||
attributeValue: true, | ||
message: `Validator internal error: ${internalError.message}`, | ||
validatorName: 'error' | ||
}, | ||
length: 1, | ||
errorMessages: {} | ||
}); | ||
} | ||
|
||
return toGavelResult(errors); | ||
} | ||
|
||
validateUsingTV4(data) { | ||
const result = tv4.validateMultiple(data, this.jsonSchema); | ||
const validationErrors = result.errors.concat(result.missing); | ||
|
||
const amandaCompatibleError = { | ||
length: validationErrors.length, | ||
errorMessages: {} | ||
}; | ||
|
||
for (let index = 0; index < validationErrors.length; index++) { | ||
const validationError = validationErrors[index]; | ||
let error; | ||
|
||
if (validationError instanceof Error) { | ||
error = validationError; | ||
} else { | ||
error = new Error('Missing schema'); | ||
error.params = { key: validationError }; | ||
error.dataPath = ''; | ||
} | ||
|
||
const pathArray = jsonPointer | ||
.parse(error.dataPath) | ||
.concat(error.params.key || []); | ||
const pointer = jsonPointer.compile(pathArray); | ||
|
||
amandaCompatibleError[index] = { | ||
message: `At '${pointer}' ${error.message}`, | ||
property: pathArray, | ||
attributeValue: true, | ||
validatorName: 'error' | ||
}; | ||
} | ||
|
||
const errors = new ValidationErrors(amandaCompatibleError); | ||
return toGavelResult(errors); | ||
} | ||
} | ||
|
||
module.exports = { JsonSchemaLegacy }; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Explicit error type to use in unit tests instead of asserting an exact message.