Skip to content

Commit 1ae6fcf

Browse files
respect validation logic when consolidating allOf contents for default details form
1 parent effa221 commit 1ae6fcf

File tree

5 files changed

+351
-162
lines changed

5 files changed

+351
-162
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-jsonschema-inspector",
3-
"version": "2.0.0",
3+
"version": "2.0.1",
44
"description": "View component for traversing/searching in a JSON Schema",
55
"homepage": "https://CarstenWickner.github.io/react-jsonschema-inspector",
66
"author": "Carsten Wickner",

src/component/InspectorDetailsContent.js

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import { getColumnDataPropTypeShape } from "./renderDataUtils";
77

88
import JsonSchemaGroup from "../model/JsonSchemaGroup";
99
import { createOptionTargetArrayFromIndexes, getFieldValueFromSchemaGroup } from "../model/schemaUtils";
10-
import { isDefined, listValues } from "../model/utils";
10+
import {
11+
isDefined, listValues, commonValues, minimumValue, maximumValue
12+
} from "../model/utils";
1113

1214
function checkIfIsRequired(columnData, selectionColumnIndex) {
1315
if (!selectionColumnIndex) {
@@ -33,6 +35,17 @@ function checkIfIsRequired(columnData, selectionColumnIndex) {
3335
return checkIfIsRequired(columnData, selectionColumnIndex - 1);
3436
}
3537

38+
function containsTrueOrReduce(allValues, reduceNonBooleans) {
39+
if (!Array.isArray(allValues)) {
40+
// return single value as-is
41+
return allValues;
42+
}
43+
if (allValues.includes(true)) {
44+
return true;
45+
}
46+
return allValues.reduce(reduceNonBooleans);
47+
}
48+
3649
export const collectFormFields = (itemSchemaGroup, columnData, selectionColumnIndex) => {
3750
const formFields = [];
3851
const addFormField = (labelText, rowValue) => {
@@ -42,19 +55,30 @@ export const collectFormFields = (itemSchemaGroup, columnData, selectionColumnIn
4255
};
4356
const { selectedItem } = columnData[selectionColumnIndex];
4457
const optionIndexes = typeof selectedItem === "string" ? undefined : selectedItem;
45-
const getValue = fieldName => getFieldValueFromSchemaGroup(itemSchemaGroup, fieldName, listValues, undefined, undefined, optionIndexes);
58+
const getValue = (fieldName, mergeValues = listValues) => getFieldValueFromSchemaGroup(
59+
itemSchemaGroup, fieldName, mergeValues, undefined, undefined, optionIndexes
60+
);
4661

4762
addFormField("Title", getValue("title"));
4863
addFormField("Description", getValue("description"));
4964

5065
addFormField("Required", checkIfIsRequired(columnData, selectionColumnIndex) ? "Yes" : null);
5166

52-
addFormField("Type", getValue("type"));
53-
addFormField("Constant Value", getValue("const"));
54-
addFormField("Possible Values", getValue("enum"));
67+
addFormField("Type", getValue("type", commonValues));
68+
const enumValues = commonValues(getValue("const", commonValues), getValue("enum", commonValues));
69+
if (isDefined(enumValues)) {
70+
if (!Array.isArray(enumValues)) {
71+
addFormField("Constant Value", enumValues);
72+
} else if (enumValues.length === 1) {
73+
addFormField("Constant Value", enumValues[0]);
74+
} else {
75+
addFormField("Possible Values", enumValues);
76+
}
77+
}
5578

56-
const minimum = getValue("minimum");
57-
const exclusiveMinimum = getValue("exclusiveMinimum");
79+
// if multiple minimums are specified (in allOf parts), the highest minimum applies
80+
const minimum = getValue("minimum", maximumValue);
81+
const exclusiveMinimum = containsTrueOrReduce(getValue("exclusiveMinimum"), maximumValue);
5882
let minValue;
5983
if (isDefined(minimum)) {
6084
// according to JSON Schema Draft 4, "exclusiveMinimum" is a boolean and used in combination with "minimum"
@@ -66,8 +90,9 @@ export const collectFormFields = (itemSchemaGroup, columnData, selectionColumnIn
6690
}
6791
addFormField("Min Value", minValue);
6892

69-
const maximum = getValue("maximum");
70-
const exclusiveMaximum = getValue("exclusiveMaximum");
93+
// if multiple maximums are specified (in allOf parts), the lowest maximum applies
94+
const maximum = getValue("maximum", minimumValue);
95+
const exclusiveMaximum = containsTrueOrReduce(getValue("exclusiveMaximum"), minimumValue);
7196
let maxValue;
7297
if (isDefined(maximum)) {
7398
// according to JSON Schema Draft 4, "exclusiveMaximum" is a boolean and used in combination with "maximum"
@@ -86,12 +111,14 @@ export const collectFormFields = (itemSchemaGroup, columnData, selectionColumnIn
86111
addFormField("Example(s)",
87112
(examples && typeof examples[0] === "object") ? JSON.stringify(examples) : examples);
88113
addFormField("Value Pattern", getValue("pattern"));
89-
addFormField("Value Format", getValue("format"));
90-
addFormField("Min Length", getValue("minLength"));
91-
addFormField("Max Length", getValue("maxLength"));
92-
addFormField("Min Items", getValue("minItems"));
93-
addFormField("Max Items", getValue("maxItems"));
94-
addFormField("Items Unique", getValue("uniqueItems") === true ? "Yes" : null);
114+
addFormField("Value Format", getValue("format", commonValues));
115+
// if multiple minimums are specified (in allOf parts), the highest minimum applies
116+
addFormField("Min Length", getValue("minLength", maximumValue));
117+
// if multiple maximums are specified (in allOf parts), the lowest maximum applies
118+
addFormField("Max Length", getValue("maxLength", minimumValue));
119+
addFormField("Min Items", getValue("minItems", maximumValue));
120+
addFormField("Max Items", getValue("maxItems", minimumValue));
121+
addFormField("Items Unique", containsTrueOrReduce(getValue("uniqueItems")) ? "Yes" : null);
95122

96123
return formFields;
97124
};

src/model/utils.js

Lines changed: 102 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -37,35 +37,110 @@ export function mapObjectValues(original, mappingFunction) {
3737
}
3838

3939
/**
40-
* Generic function to be used in Array.reduce().
40+
* Helper function for building a function to be used in Array.reduce() – ignoring undefined/null values.
4141
*
42-
* @param {*} combined - temporary result of previous reduce steps
43-
* @param {*} nextValue - single value to merge with "combined"
44-
* @returns {*|Array.<*>} either single (defined) value or array of multiple (defined) values
42+
* @param {Function} mergeDefinedValues - reduce function to apply if both the given values are defined and not null
43+
* @param {*} mergeDefinedValues.param0 - temporary result of previous reduce steps (guaranteed to be defined and not null)
44+
* @param {*} mergeDefinedValues.param1 - single value to merge with first parameter (guaranteed to be defined and not null)
45+
* @returns {Function} also expecting two parameters: (1) the temporary result of previous reduce steps and (2) the single value to add/merge with
4546
*/
46-
export function listValues(combined, nextValue) {
47-
let mergeResult;
48-
if (!isDefined(combined)) {
49-
mergeResult = nextValue;
50-
} else if (!isDefined(nextValue)) {
51-
mergeResult = combined;
52-
} else if (combined === nextValue) {
53-
mergeResult = combined;
54-
} else if (Array.isArray(combined)) {
55-
if (Array.isArray(nextValue)) {
56-
// both "combined" and "nextValue" are arrays already
57-
mergeResult = combined.concat(nextValue);
47+
function nullAwareReduce(mergeDefinedValues) {
48+
return (combined, nextValue) => {
49+
let mergeResult;
50+
if (!isDefined(combined)) {
51+
mergeResult = nextValue;
52+
} else if (!isDefined(nextValue)) {
53+
mergeResult = combined;
5854
} else {
59-
// "combined" is an array already but "nextValue" is a single value
60-
mergeResult = combined.slice();
61-
mergeResult.push(nextValue);
55+
mergeResult = mergeDefinedValues(combined, nextValue);
6256
}
63-
} else if (Array.isArray(nextValue)) {
64-
// "combined" is a single value but "nextValue" is an array already
65-
mergeResult = [combined].concat(nextValue);
66-
} else {
67-
// "combined" and "nextValue" are single values, to be combined into an array
68-
mergeResult = [combined, nextValue];
69-
}
70-
return mergeResult;
57+
return mergeResult;
58+
};
7159
}
60+
61+
/**
62+
* Generic function to be used in Array.reduce() – returning the lowest encountered value.
63+
* Undefined/null values are being ignored.
64+
*
65+
* @param {?number} combined - temporary result of previous reduce steps
66+
* @param {?number} nextValue - next value to compare with
67+
* @returns {?number} lowest encountered value
68+
*/
69+
export const minimumValue = nullAwareReduce((a, b) => (a < b ? a : b));
70+
71+
/**
72+
* Generic function to be used in Array.reduce() – returning the highest encountered value.
73+
* Undefined/null values are being ignored.
74+
*
75+
* @param {?number} combined - temporary result of previous reduce steps
76+
* @param {?number} nextValue - next value to compare with
77+
* @returns {?number} highest encountered value
78+
*/
79+
export const maximumValue = nullAwareReduce((a, b) => (a > b ? a : b));
80+
81+
/**
82+
* Generic function to be used in Array.reduce() – returning all encountered values.
83+
* Undefined/null values are being ignored.
84+
*
85+
* @param {?*} combined - temporary result of previous reduce steps
86+
* @param {?*} nextValue - single value to merge with "combined"
87+
* @returns {?*|Array.<*>} either single (defined) value or array of multiple (defined) values
88+
*/
89+
export const listValues = nullAwareReduce(
90+
(combined, nextValue) => {
91+
let mergeResult;
92+
if (combined === nextValue) {
93+
mergeResult = combined;
94+
} else if (Array.isArray(combined)) {
95+
if (Array.isArray(nextValue)) {
96+
// both "combined" and "nextValue" are arrays already
97+
mergeResult = combined.concat(nextValue);
98+
} else {
99+
// "combined" is an array already but "nextValue" is a single value
100+
mergeResult = combined.slice();
101+
mergeResult.push(nextValue);
102+
}
103+
} else if (Array.isArray(nextValue)) {
104+
// "combined" is a single value but "nextValue" is an array already
105+
mergeResult = [combined].concat(nextValue);
106+
} else {
107+
// "combined" and "nextValue" are single values, to be combined into an array
108+
mergeResult = [combined, nextValue];
109+
}
110+
return mergeResult;
111+
}
112+
);
113+
114+
/**
115+
* Generic function to be used in Array.reduce() – returning only those values that occurred in all instances with a value.
116+
* Undefined/null values are being ignored.
117+
*
118+
* @param {?*} combined - temporary result of previous reduce steps
119+
* @param {?*} nextValue - single value to merge with "combined"
120+
* @returns {?*|Array.<*>} either single (defined) value, array of multiple (defined) values, or empty array if encountered values do not intersect
121+
*/
122+
export const commonValues = nullAwareReduce(
123+
(combined, nextValue) => {
124+
let mergeResult;
125+
if (combined === nextValue) {
126+
mergeResult = combined;
127+
} else if (Array.isArray(combined)) {
128+
if (Array.isArray(nextValue)) {
129+
// both "combined" and "nextValue" are arrays already
130+
mergeResult = combined.filter(existingValue => nextValue.includes(existingValue));
131+
// unwrap array containing single value
132+
mergeResult = mergeResult.length === 1 ? mergeResult[0] : mergeResult;
133+
} else {
134+
// "combined" is an array already but "nextValue" is a single value
135+
mergeResult = combined.includes(nextValue) ? nextValue : [];
136+
}
137+
} else if (Array.isArray(nextValue)) {
138+
// "combined" is a single value but "nextValue" is an array already
139+
mergeResult = nextValue.includes(combined) ? combined : [];
140+
} else {
141+
// "combined" and "nextValue" are single values but are not the same
142+
mergeResult = [];
143+
}
144+
return mergeResult;
145+
}
146+
);

0 commit comments

Comments
 (0)