Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

“compare is not a function” when using formatMinimum with date format #56

Open
brandondurham opened this issue Mar 30, 2022 · 7 comments

Comments

@brandondurham
Copy link

brandondurham commented Mar 30, 2022

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

@epoberezkin
Copy link
Member

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.

@brandondurham
Copy link
Author

Added a RunKit example above! Thanks so much for looking.

@cdxOo
Copy link

cdxOo commented Aug 13, 2022

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');

@ricardogoncalves89
Copy link

I think the problem comes after ajv-formats@2.0.1. Can someone confirm?

@taylorreece
Copy link

It does appear that using ajv-formats@2.0.1 works fine - ajv-formats@2.0.2 does not

@jameswinglmi
Copy link

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.

@jameswinglmi
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

6 participants