Skip to content

explicit ajv stack error #2634

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

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/core/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -1038,7 +1038,10 @@ export function toPathSchema(schema, name = "", rootSchema, formData = {}) {
return toPathSchema(_schema, name, rootSchema, formData);
}

if (schema.hasOwnProperty("additionalProperties")) {
if (
schema.hasOwnProperty("additionalProperties") &&
schema.additionalProperties !== false
) {
pathSchema.__rjsf_additionalProperties = true;
}

Expand Down
26 changes: 23 additions & 3 deletions packages/core/src/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ function createAjvInstance() {
multipleOfPrecision: 8,
schemaId: "auto",
unknownFormats: "ignore",
verbose: true,
});

// add custom formats
Expand Down Expand Up @@ -145,16 +146,35 @@ function transformAjvErrors(errors = []) {
}

return errors.map(e => {
const { dataPath, keyword, message, params, schemaPath } = e;
const { dataPath, keyword, message, params, schemaPath, parentSchema } = e;
let property = `${dataPath}`;

// Get the latest element that represent the item
let paths = dataPath.split(".");
let currentProperty = paths.pop();
let title = currentProperty;
//If schemaPath termine par currentProperty/name => parentSchema represent the current property
if (
schemaPath.endsWith(`${currentProperty}/${keyword}`) &&
parentSchema.title
) {
title = parentSchema.title;
} else {
// otherwise we are a level upper e.g for required properties
if (
parentSchema.hasOwnProperty("properties") &&
parentSchema.properties.hasOwnProperty(currentProperty) &&
parentSchema.properties[currentProperty].hasOwnProperty("title")
) {
title = parentSchema.properties[currentProperty].title;
}
}
// put data in expected format
return {
name: keyword,
property,
message,
params, // specific to ajv
stack: `${property} ${message}`.trim(),
stack: `${title} ${message}`.trim(),
schemaPath,
};
});
Expand Down
2 changes: 1 addition & 1 deletion packages/core/test/ArrayField_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1026,7 +1026,7 @@ describe("ArrayField", () => {
params: { limit: 3 },
property: ".multipleChoicesList",
schemaPath: "#/properties/multipleChoicesList/minItems",
stack: ".multipleChoicesList should NOT have fewer than 3 items",
stack: "multipleChoicesList should NOT have fewer than 3 items",
},
]);
});
Expand Down
53 changes: 46 additions & 7 deletions packages/core/test/Form_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1730,7 +1730,7 @@ describeRepeated("Form common", createFormComponent => {
params: { limit: 8 },
property: ".level1.level2",
schemaPath: "#/properties/level1/properties/level2/minLength",
stack: ".level1.level2 should NOT be shorter than 8 characters",
stack: "level2 should NOT be shorter than 8 characters",
},
]);
});
Expand Down Expand Up @@ -1832,15 +1832,15 @@ describeRepeated("Form common", createFormComponent => {
params: { limit: 4 },
property: ".level1[1]",
schemaPath: "#/properties/level1/items/minLength",
stack: ".level1[1] should NOT be shorter than 4 characters",
stack: "level1[1] should NOT be shorter than 4 characters",
},
{
message: "should NOT be shorter than 4 characters",
name: "minLength",
params: { limit: 4 },
property: ".level1[3]",
schemaPath: "#/properties/level1/items/minLength",
stack: ".level1[3] should NOT be shorter than 4 characters",
stack: "level1[3] should NOT be shorter than 4 characters",
},
]);
});
Expand Down Expand Up @@ -1894,15 +1894,15 @@ describeRepeated("Form common", createFormComponent => {
params: { limit: 4 },
property: ".outer[0][1]",
schemaPath: "#/properties/outer/items/items/minLength",
stack: ".outer[0][1] should NOT be shorter than 4 characters",
stack: "outer[0][1] should NOT be shorter than 4 characters",
},
{
message: "should NOT be shorter than 4 characters",
name: "minLength",
params: { limit: 4 },
property: ".outer[1][0]",
schemaPath: "#/properties/outer/items/items/minLength",
stack: ".outer[1][0] should NOT be shorter than 4 characters",
stack: "outer[1][0] should NOT be shorter than 4 characters",
},
]);
});
Expand Down Expand Up @@ -1955,7 +1955,7 @@ describeRepeated("Form common", createFormComponent => {
params: { limit: 4 },
property: "[1].foo",
schemaPath: "#/items/properties/foo/minLength",
stack: "[1].foo should NOT be shorter than 4 characters",
stack: "foo should NOT be shorter than 4 characters",
},
]);
});
Expand Down Expand Up @@ -2449,7 +2449,7 @@ describeRepeated("Form common", createFormComponent => {
params: { format: "area-code" },
property: ".areaCode",
schemaPath: "#/properties/areaCode/format",
stack: '.areaCode should match format "area-code"',
stack: 'areaCode should match format "area-code"',
},
]);
});
Expand Down Expand Up @@ -3193,6 +3193,45 @@ describe("Form omitExtraData and liveOmit", () => {
});
});

it("should remove extra data on change with omitExtraData=true and liveOmit=true if additionalProperties equal false", () => {
const omitExtraData = true;
const liveOmit = true;
const schema = {
type: "object",
properties: {
foo: { type: "string", additionalProperties: false },
bar: { type: "string", additionalProperties: false },
info: {
type: "object",
additionalProperties: false,
properties: {
name: {
type: "string",
additionalProperties: false,
},
},
required: ["name"],
},
},
additionalProperties: false,
};
const formData = {
foo: "foo",
baz: "baz",
info: { majorVersion: "v1123", name: "foofoo" },
};
const { node } = createFormComponent({
schema,
formData,
omitExtraData,
liveOmit,
});

Simulate.submit(node);

expect(node.querySelectorAll(".errors li")).to.have.length.of(0);
});

it("should rename formData key if key input is renamed in a nested object with omitExtraData=true and liveOmit=true", () => {
const { node, onChange } = createFormComponent(
{
Expand Down
59 changes: 45 additions & 14 deletions packages/core/test/validate_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,13 @@ describe("Validation", () => {
pattern: "\\d+",
type: "string",
},
datasetName: {
pattern: "\\d+",
type: "string",
title: "Name of the dataset",
},
},
required: ["datasetId"],
required: ["datasetId", "datasetName"],
type: "object",
},
},
Expand All @@ -182,7 +187,7 @@ describe("Validation", () => {

it("should return a validation error about meta schema when meta schema is not defined", () => {
const errors = validateFormData(
{ datasetId: "some kind of text" },
{ datasetId: "some kind of text", datasetName: "2" },
schema
);
const errMessage =
Expand All @@ -199,28 +204,54 @@ describe("Validation", () => {
});
it("should return a validation error about formData", () => {
const errors = validateFormData(
{ datasetId: "some kind of text" },
{ datasetId: "some kind of text", datasetName: "2" },
schema,
null,
null,
[metaSchemaDraft4]
);
expect(errors.errors).to.have.lengthOf(1);
expect(errors.errors[0].stack).to.equal(
'datasetId should match pattern "\\d+"'
);
});
it("should return a validation error with title attribute about formData", () => {
const errors = validateFormData(
{ datasetId: "2", datasetName: "some kind of text" },
schema,
null,
null,
[metaSchemaDraft4]
);
expect(errors.errors).to.have.lengthOf(1);
expect(errors.errors[0].stack).to.equal(
'Name of the dataset should match pattern "\\d+"'
);
});
it("should return a validation error with title attribute about formData required", () => {
const errors = validateFormData(
{ datasetId: "2" },
schema,
null,
null,
[metaSchemaDraft4]
);
expect(errors.errors).to.have.lengthOf(1);
expect(errors.errors[0].stack).to.equal(
'.datasetId should match pattern "\\d+"'
"Name of the dataset is a required property"
);
});
it("should return a validation error about formData, when used with multiple meta schemas", () => {
const errors = validateFormData(
{ datasetId: "some kind of text" },
{ datasetId: "some kind of text", datasetName: "2" },
schema,
null,
null,
[metaSchemaDraft4, metaSchemaDraft6]
);
expect(errors.errors).to.have.lengthOf(1);
expect(errors.errors[0].stack).to.equal(
'.datasetId should match pattern "\\d+"'
'datasetId should match pattern "\\d+"'
);
});
});
Expand Down Expand Up @@ -253,7 +284,7 @@ describe("Validation", () => {

expect(result.errors).to.have.lengthOf(1);
expect(result.errors[0].stack).to.equal(
'.phone should match format "phone-us"'
'phone should match format "phone-us"'
);
});

Expand All @@ -277,7 +308,7 @@ describe("Validation", () => {

expect(result.errors).to.have.lengthOf(1);
expect(result.errors[0].stack).to.equal(
'.phone should match format "area-code"'
'phone should match format "area-code"'
);
});
});
Expand Down Expand Up @@ -481,15 +512,15 @@ describe("Validation", () => {
params: { missingProperty: "foo" },
property: ".foo",
schemaPath: "#/required",
stack: ".foo is a required property",
stack: "foo is a required property",
},
]);
});

it("should render errors", () => {
expect(node.querySelectorAll(".errors li")).to.have.length.of(1);
expect(node.querySelector(".errors li").textContent).eql(
".foo is a required property"
"foo is a required property"
);
});
});
Expand Down Expand Up @@ -525,7 +556,7 @@ describe("Validation", () => {
it("should render errors", () => {
expect(node.querySelectorAll(".errors li")).to.have.length.of(1);
expect(node.querySelector(".errors li").textContent).eql(
".foo should NOT be shorter than 10 characters"
"foo should NOT be shorter than 10 characters"
);
});

Expand All @@ -537,7 +568,7 @@ describe("Validation", () => {
params: { limit: 10 },
property: ".foo",
schemaPath: "#/properties/foo/minLength",
stack: ".foo should NOT be shorter than 10 characters",
stack: "foo should NOT be shorter than 10 characters",
},
]);
});
Expand Down Expand Up @@ -783,7 +814,7 @@ describe("Validation", () => {
params: { missingProperty: "foo" },
property: ".foo",
schemaPath: "#/required",
stack: ".foo is a required property",
stack: "foo is a required property",
},
]);
});
Expand Down Expand Up @@ -887,7 +918,7 @@ describe("Validation", () => {
params: { pattern: "\\d+" },
property: ".datasetId",
schemaPath: "#/properties/datasetId/pattern",
stack: '.datasetId should match pattern "\\d+"',
stack: 'datasetId should match pattern "\\d+"',
},
]);
onError.resetHistory();
Expand Down