Description
What version of Ajv are you using? Does the issue happen if you use the latest version?
7.0.1
Ajv options object
{
allErrors: true,
code: {
es5: false, // use es6
lines: true,
optimize: false, // we'll let rollup do this
source: true,
},
inlineRefs: false,
}
JSON Schema
file:///User.json:
{
type: 'object',
properties: {
name: {
type: 'string',
},
},
required: ['name'],
}
file:///B.json:
{
type: 'object',
properties: {
author: {
$ref: 'file:///User.json',
},
contributors: {
type: 'array',
items: {
$ref: 'file:///User.json',
},
},
},
required: ['author', 'contributors'],
}
Sample data
Not a data validation issue.
Your code
//@ts-check
'use strict';
const Ajv = require('ajv').default;
const standaloneCode = require('ajv/dist/standalone').default;
const ajv = new Ajv({
allErrors: true,
code: {
es5: false,
lines: true,
optimize: false,
source: true,
},
inlineRefs: false,
schemas: {
'file:///User.json': {
type: 'object',
properties: {
name: {
type: 'string',
},
},
required: ['name'],
},
'file:///B.json': {
type: 'object',
properties: {
author: {
$ref: 'file:///User.json',
},
contributors: {
type: 'array',
items: {
$ref: 'file:///User.json',
},
},
},
required: ['author', 'contributors'],
},
}
});
const code = standaloneCode(ajv);
console.log(code);
Note that function validate21
is included twice in the resulting generated code. This breaks some tools while others are unable to remove the duplicate function.
Resulting code (prettified)
"use strict";
exports["file:///User.json"] = validate21;
const schema6 = {
type: "object",
properties: { name: { type: "string" } },
required: ["name"],
};
function validate21(
data,
{ dataPath = "", parentData, parentDataProperty, rootData = data } = {}
) {
let vErrors = null;
let errors = 0;
if (data && typeof data == "object" && !Array.isArray(data)) {
if (data.name === undefined) {
const err0 = {
keyword: "required",
dataPath,
schemaPath: "#/required",
params: { missingProperty: "name" },
message: "should have required property '" + "name" + "'",
};
if (vErrors === null) {
vErrors = [err0];
} else {
vErrors.push(err0);
}
errors++;
}
if (data.name !== undefined) {
let data0 = data.name;
const _errs0 = errors;
if (typeof data0 !== "string") {
const err1 = {
keyword: "type",
dataPath: dataPath + "/name",
schemaPath: "#/properties/name/type",
params: { type: "string" },
message: "should be string",
};
if (vErrors === null) {
vErrors = [err1];
} else {
vErrors.push(err1);
}
errors++;
}
var valid0 = _errs0 === errors;
}
} else {
const err2 = {
keyword: "type",
dataPath,
schemaPath: "#/type",
params: { type: "object" },
message: "should be object",
};
if (vErrors === null) {
vErrors = [err2];
} else {
vErrors.push(err2);
}
errors++;
}
validate21.errors = vErrors;
return errors === 0;
}
exports["file:///B.json"] = validate22;
const schema7 = {
type: "object",
properties: {
author: { $ref: "file:///User.json" },
contributors: { type: "array", items: { $ref: "file:///User.json" } },
},
required: ["author", "contributors"],
};
function validate21(
data,
{ dataPath = "", parentData, parentDataProperty, rootData = data } = {}
) {
let vErrors = null;
let errors = 0;
if (data && typeof data == "object" && !Array.isArray(data)) {
if (data.name === undefined) {
const err0 = {
keyword: "required",
dataPath,
schemaPath: "#/required",
params: { missingProperty: "name" },
message: "should have required property '" + "name" + "'",
};
if (vErrors === null) {
vErrors = [err0];
} else {
vErrors.push(err0);
}
errors++;
}
if (data.name !== undefined) {
let data0 = data.name;
const _errs0 = errors;
if (typeof data0 !== "string") {
const err1 = {
keyword: "type",
dataPath: dataPath + "/name",
schemaPath: "#/properties/name/type",
params: { type: "string" },
message: "should be string",
};
if (vErrors === null) {
vErrors = [err1];
} else {
vErrors.push(err1);
}
errors++;
}
var valid0 = _errs0 === errors;
}
} else {
const err2 = {
keyword: "type",
dataPath,
schemaPath: "#/type",
params: { type: "object" },
message: "should be object",
};
if (vErrors === null) {
vErrors = [err2];
} else {
vErrors.push(err2);
}
errors++;
}
validate21.errors = vErrors;
return errors === 0;
}
function validate22(
data,
{ dataPath = "", parentData, parentDataProperty, rootData = data } = {}
) {
let vErrors = null;
let errors = 0;
if (data && typeof data == "object" && !Array.isArray(data)) {
if (data.author === undefined) {
const err0 = {
keyword: "required",
dataPath,
schemaPath: "#/required",
params: { missingProperty: "author" },
message: "should have required property '" + "author" + "'",
};
if (vErrors === null) {
vErrors = [err0];
} else {
vErrors.push(err0);
}
errors++;
}
if (data.contributors === undefined) {
const err1 = {
keyword: "required",
dataPath,
schemaPath: "#/required",
params: { missingProperty: "contributors" },
message: "should have required property '" + "contributors" + "'",
};
if (vErrors === null) {
vErrors = [err1];
} else {
vErrors.push(err1);
}
errors++;
}
if (data.author !== undefined) {
let data0 = data.author;
const _errs0 = errors;
if (
!validate21(data0, {
dataPath: dataPath + "/author",
parentData: data,
parentDataProperty: "author",
rootData,
})
) {
vErrors =
vErrors === null
? validate21.errors
: vErrors.concat(validate21.errors);
errors = vErrors.length;
} else {
}
var valid0 = _errs0 === errors;
}
if (data.contributors !== undefined) {
let data1 = data.contributors;
const _errs1 = errors;
if (Array.isArray(data1)) {
const len0 = data1.length;
for (let i0 = 0; i0 < len0; i0++) {
let data2 = data1[i0];
const _errs2 = errors;
if (
!validate21(data2, {
dataPath: dataPath + "/contributors/" + i0,
parentData: data1,
parentDataProperty: i0,
rootData,
})
) {
vErrors =
vErrors === null
? validate21.errors
: vErrors.concat(validate21.errors);
errors = vErrors.length;
} else {
}
var valid1 = _errs2 === errors;
}
} else {
const err2 = {
keyword: "type",
dataPath: dataPath + "/contributors",
schemaPath: "#/properties/contributors/type",
params: { type: "array" },
message: "should be array",
};
if (vErrors === null) {
vErrors = [err2];
} else {
vErrors.push(err2);
}
errors++;
}
var valid0 = _errs1 === errors;
}
} else {
const err3 = {
keyword: "type",
dataPath,
schemaPath: "#/type",
params: { type: "object" },
message: "should be object",
};
if (vErrors === null) {
vErrors = [err3];
} else {
vErrors.push(err3);
}
errors++;
}
validate22.errors = vErrors;
return errors === 0;
}
Validation result, data AFTER validation, error messages
N/A
What results did you expect?
I expected each validation function to be included exactly once.
Are you going to resolve the issue?
I'm trying to wrap my head around the codegen but am unlikely to be able to handle this.
Activity
epoberezkin commentedon Dec 19, 2020
good catch. I think I know why it might be happening - and the normal tests wouldn’t have caught it because JS allows redefining functions, probably even in strict mode...
test: duplicate function in standalone code, it should fail but it do…
test: failing test for standalone code (duplicate functions, #1361)
fix: standalone code generation creating duplicate functions (closes #…
epoberezkin commentedon Dec 19, 2020
@ggoodman it would be great if you could test with the full code you have before I release. I have a suspicion there may be some edge cases when this is not sufficient, but it should be fixed for the majority of cases.
ggoodman commentedon Dec 19, 2020
@epoberezkin I've given it a try and in my limited testing it works as advertised! 🎉
Great work! I love the first-class codegen in v7 ❤️
epoberezkin commentedon Dec 19, 2020
Great - thank you :)
jafaircl commentedon Jan 21, 2021
@epoberezkin this still happens in 7.0.3 if the user schema references back to the info schema like so:
is there any way around this or is this something the library just can't do?
epoberezkin commentedon Jan 21, 2021
Need to have a look - mutual recursion is always tricky to compile - but should be possible to fix. I’ll try to reproduce. Just to confirm - it works, but duplicates functions in the module, correct?
28 remaining items