Open brandondurham opened 2 years ago
Please add a minimal code sample - ideally to runkit - it's not clear what you are doing wrong otherwise...
Most likely you are not passing the correct options to ajv-formats.
Added a RunKit example above! Thanks so much for looking.
any updates on that one? it also occurs when using the example in your documentation, which i basically copied verbatim into runkit your docs: https://github.com/ajv-validator/ajv-formats#keywords-to-compare-values-formatmaximum--formatminimum-and-formatexclusivemaximum--formatexclusiveminimum
runkit A: https://runkit.com/cdxoo/62f79994b6b4b90009190fe4 for interaction with ajv@8.11.0 runkit B: https://runkit.com/cdxoo/62f7953878d623000706463a for interaction with ajv@7.2.4
the issue occurs in both of those
var Ajv = require('ajv@8.11.0');
var ajvFormats = require('ajv-formats@2.1.1');
var ajv = new Ajv();
ajvFormats(ajv);
var schema = {
type: "string",
format: "date",
formatMinimum: "2016-02-06",
formatExclusiveMaximum: "2016-12-27",
}
ajv.validate(schema, '2016-02-06');
I think the problem comes after ajv-formats@2.0.1
. Can someone confirm?
It does appear that using ajv-formats@2.0.1
works fine - ajv-formats@2.0.2
does not
Here is an example of the (faulty) validation code being generated by the library for a schema with type=string, format=date-time or format=date, and formatMinimum specified (presumably it does the same for formatMaximum too):
(function anonymous(self, scope) {
const schema16 = scope.schema[10];
const formats0 = scope.formats[0];
return function validate14(data, {instancePath="", parentData, parentDataProperty, rootData=data}={}) {
let vErrors = null;
let errors = 0;
if (typeof data === "string") {
if (!(formats0.validate(data))) {
const err0 = {
instancePath,
schemaPath: "#/format",
keyword: "format",
params: {
format: "date-time"
},
message: "must match format \"" + "date-time" + "\""
};
if (vErrors === null) {
vErrors = [err0];
} else {
vErrors.push(err0);
}
errors++;
}
if ({
"str": "formats0",
"prefix": "formats",
"value": {
"key": "date-time",
"ref": {},
"code": {
"_items": ["", "{\"_items\":[\"require(\\\"ajv-formats/dist/formats\\\").\",{\"str\":\"fullFormats\"},\"\"]}", "", "[", "\"date-time\"", "]", ""]
}
},
"scopePath": {
"_items": [".", {
"str": "formats"
}, "[", 0, "]"]
}
}.compare({ // <<<<<<======== this throws because the above ~14 lines didn't get turned into code (and the ~3 below)
"str": "data"
}, {
"_items": ["", "\"1970-01-01T00:00:00.000Z\"", ""]
}) < 0) {
const err1 = {
instancePath,
schemaPath: "#/formatMinimum",
keyword: "formatMinimum",
params: {
"_items": ["{comparison: ", "\">=\"", ", limit: ", "{\"_items\":[\"\",\"\\\"1970-01-01T00:00:00.000Z\\\"\",\"\"]}", "}"]
},
message: {
"_items": ["\"should be >= \"", "+", "{\"_items\":[\"\",\"\\\"1970-01-01T00:00:00.000Z\\\"\",\"\"]}"]
}
};
if (vErrors === null) {
vErrors = [err1];
} else {
vErrors.push(err1);
}
errors++;
}
} else {
const err2 = {
instancePath,
schemaPath: "#/type",
keyword: "type",
params: {
type: "string"
},
message: "must be string"
};
if (vErrors === null) {
vErrors = [err2];
} else {
vErrors.push(err2);
}
errors++;
}
if (errors > 0) {
const emErrs0 = [];
for (const err3 of vErrors) {
if (((((err3.keyword !== "errorMessage") && (!err3.emUsed)) && ((err3.instancePath === instancePath) || ((err3.instancePath.indexOf(instancePath) === 0) && (err3.instancePath[instancePath.length] === "/")))) && (err3.schemaPath.indexOf("#") === 0)) && (err3.schemaPath["#".length] === "/")) {
emErrs0.push(err3);
err3.emUsed = true;
}
}
if (emErrs0.length) {
const err4 = {
instancePath,
schemaPath: "#/errorMessage",
keyword: "errorMessage",
params: {
errors: emErrs0
},
message: "must be an ISO-8601-formatted datetime (min 1970-01-01T00:00:00.000Z)"
};
if (vErrors === null) {
vErrors = [err4];
} else {
vErrors.push(err4);
}
errors++;
}
const emErrs1 = [];
for (const err5 of vErrors) {
if (!err5.emUsed) {
emErrs1.push(err5);
}
}
vErrors = emErrs1;
errors = emErrs1.length;
}
validate14.errors = vErrors;
return errors === 0;
}
}
)
We can see that the code generation is failing to create valid javascript. It is leaving objects as placeholders for what it wants on either side of the .compare(
.
I studied it for hours, but there is a steep learning curve to this library. The magic is being performed in the CodeGen class in node_modules\ajv\lib\compile\codegen\index.ts
, upon being called by topSchemaObjCode()
from within validateFunctionCode()
in node_modules\ajv\lib\compile\validate\index.ts
.
It was a little tricky to find, as it happens when the schema gets compiled for the first time.
I can surmise that the following block of generated validation javascript:
{
"str": "formats0",
"prefix": "formats",
"value": {
"key": "date-time",
"ref": {},
"code": {
"_items": ["", "{\"_items\":[\"require(\\\"ajv-formats/dist/formats\\\").\",{\"str\":\"fullFormats\"},\"\"]}", "", "[", "\"date-time\"", "]", ""]
}
},
"scopePath": {
"_items": [".", {
"str": "formats"
}, "[", 0, "]"]
}
}.compare({
"str": "data"
}, {
"_items": ["", "\"1970-01-01T00:00:00.000Z\"", ""]
}
should instead be something like
formats0.compare("1970-01-01T00:00:00.000Z")
... and it simply didn't get turned into javascript correctly.
Hopefully this will help get if fixed more easily & quicker. In the meantime, I'd love to hear form the authoritative source what is the proposed workaround.
The workaround I used for now was to set the version explicitly in package.json
dependencies ("ajv-formats": "2.0.1"
), and then delete node_modules
folder and the package-lock.json
file, and recreate them with npm install
This is my schema (with unrelated properties removed):
As-is, everything works and validates correctly. But when I add
formatMinimum
tolaunchDate
:… I get the following console error:
Update:
RunKit example: https://runkit.com/brandondurham/compare-is-not-a-function-when-using-formatminimum-with-date-format