ajv-formats
ajv-formats copied to clipboard
“compare is not a function” when using formatMinimum with date format
This is my schema (with unrelated properties removed):
export const employeeSchema = {
items: {
properties: {
launchDate: {
errorMessage: {
format: 'must be a date in YYYY-MM-DD format (e.g. _1973-05-11_)',
type: 'required, and must be a date in YYYY-MM-DD format (e.g. _1973-05-11_)',
},
examples: ['1973-05-11'],
format: 'date',
type: 'string',
},
},
required: [
'launchDate',
],
type: 'object',
},
minItems: 1,
type: 'array',
};
As-is, everything works and validates correctly. But when I add formatMinimum
to launchDate
:
export const employeeSchema = {
items: {
properties: {
launchDate: {
errorMessage: {
format: 'must be a date in YYYY-MM-DD format (e.g. _1973-05-11_)',
type: 'required, and must be a date in YYYY-MM-DD format (e.g. _1973-05-11_)',
},
examples: ['1973-05-11'],
format: 'date',
formatMinimum: '2022-03-30',
type: 'string',
},
},
required: [
'launchDate',
],
type: 'object',
},
minItems: 1,
type: 'array',
};
… I get the following console error:
Uncaught TypeError: {(intermediate value)(intermediate value)(intermediate value)(intermediate value)}.compare is not a function
at validate14 (eval at compileSchema (index.jss:89:1), <anonymous>:3:2858)
at index.jss:149:1
at mountMemo (react-dom.development.jss:15846:1)
at Object.useMemo (react-dom.development.jss:16219:1)
at useMemo (react.development.jss:1532:1)
at UseMemo (whyDidYouRender.jss:1461:1)
at ModalImportReview (index.jss:132:1)
at renderWithHooks (react-dom.development.jss:14985:1)
at mountIndeterminateComponent (react-dom.development.jss:17811:1)
at beginWork (react-dom.development.jss:19049:1)
Update:
RunKit example: https://runkit.com/brandondurham/compare-is-not-a-function-when-using-formatminimum-with-date-format
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 [email protected] runkit B: https://runkit.com/cdxoo/62f7953878d623000706463a for interaction with [email protected]
the issue occurs in both of those
var Ajv = require('[email protected]');
var ajvFormats = require('[email protected]');
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 [email protected]
. Can someone confirm?
It does appear that using [email protected]
works fine - [email protected]
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