1/**
2 * @param schema
3 * @param rootSchema
4 * @param formData
5 * @returns {{properties: *}|{}|{properties: *}|{}|{properties: *}|{additionalProperties}|*|{}|{allOf}}
6 * 源码来自:react-jsonschema-form
7 * 做了细节和模块调整
8 * 重写了allOf实现逻辑(解决使用allOf必须根节点同时存在,以及对json-schema-merge-allof依赖包过大)
9 * 移除对lodash 、json-schema-merge-allof、jsonpointer 等依赖重新实现
10 * https://github.com/rjsf-team/react-jsonschema-form/blob/master/packages/core/src/utils.js#L621
11 */
12
13import findSchemaDefinition from './findSchemaDefinition';
14import { intersection } from '../arrayUtils';
15
16import {
17    /* guessType,  mergeSchemas, */ isObject, scm
18} from '../utils';
19
20// import { getMatchingOption, isValid } from './validate';
21
22// 自动添加分割线
23
24// export const ADDITIONAL_PROPERTY_FLAG = '__additional_property';
25
26// resolve Schema - dependencies
27// https://json-schema.org/understanding-json-schema/reference/object.html#dependencies
28/*
29export function resolveDependencies(schema, rootSchema, formData) {
30    // 从源模式中删除依赖项。
31    const { dependencies = {} } = schema;
32    let { ...resolvedSchema } = schema;
33    if ('oneOf' in resolvedSchema) {
34        resolvedSchema = resolvedSchema.oneOf[
35            getMatchingOption(formData, resolvedSchema.oneOf, rootSchema)
36        ];
37    } else if ('anyOf' in resolvedSchema) {
38        resolvedSchema = resolvedSchema.anyOf[
39            getMatchingOption(formData, resolvedSchema.anyOf, rootSchema)
40        ];
41    }
42    return processDependencies(
43        dependencies,
44        resolvedSchema,
45        rootSchema,
46        formData
47    );
48}
49*/
50
51// 处理依赖关系 dependencies
52// https://json-schema.org/understanding-json-schema/reference/object.html#dependencies
53/*
54
55function processDependencies(
56    dependencies,
57    resolvedSchema,
58    rootSchema,
59    formData
60) {
61    // Process dependencies updating the local schema properties as appropriate.
62    for (const dependencyKey in dependencies) {
63        // Skip this dependency if its trigger property is not present.
64        if (formData[dependencyKey] === undefined) {
65            // eslint-disable-next-line no-continue
66            continue;
67        }
68        // Skip this dependency if it is not included in the schema (such as when dependencyKey is itself a hidden dependency.)
69        if (
70            resolvedSchema.properties
71            && !(dependencyKey in resolvedSchema.properties)
72        ) {
73            // eslint-disable-next-line no-continue
74            continue;
75        }
76        const {
77            [dependencyKey]: dependencyValue,
78            ...remainingDependencies
79        } = dependencies;
80        if (Array.isArray(dependencyValue)) {
81            resolvedSchema = withDependentProperties(resolvedSchema, dependencyValue);
82        } else if (isObject(dependencyValue)) {
83            resolvedSchema = withDependentSchema(
84                resolvedSchema,
85                rootSchema,
86                formData,
87                dependencyKey,
88                dependencyValue
89            );
90        }
91        return processDependencies(
92            remainingDependencies,
93            resolvedSchema,
94            rootSchema,
95            formData
96        );
97    }
98    return resolvedSchema;
99}
100*/
101
102// 属性依赖
103// https://json-schema.org/understanding-json-schema/reference/object.html#property-dependencies
104
105/*
106function withDependentProperties(schema, additionallyRequired) {
107    if (!additionallyRequired) {
108        return schema;
109    }
110    const required = Array.isArray(schema.required)
111        ? Array.from(new Set([...schema.required, ...additionallyRequired]))
112        : additionallyRequired;
113    return { ...schema, required };
114}
115*/
116
117// schema 依赖
118// https://json-schema.org/understanding-json-schema/reference/object.html#schema-dependencies
119/*
120function withDependentSchema(
121    schema,
122    rootSchema,
123    formData,
124    dependencyKey,
125    dependencyValue
126) {
127    const { oneOf, ...dependentSchema } = retrieveSchema(
128        dependencyValue,
129        rootSchema,
130        formData
131    );
132    schema = mergeSchemas(schema, dependentSchema);
133    // Since it does not contain oneOf, we return the original schema.
134    if (oneOf === undefined) {
135        return schema;
136    } if (!Array.isArray(oneOf)) {
137        throw new Error(`invalid: it is some ${typeof oneOf} instead of an array`);
138    }
139    // Resolve $refs inside oneOf.
140    const resolvedOneOf = oneOf.map(subschema => (subschema.hasOwnProperty('$ref')
141        ? resolveReference(subschema, rootSchema, formData)
142        : subschema));
143    return withExactlyOneSubschema(
144        schema,
145        rootSchema,
146        formData,
147        dependencyKey,
148        resolvedOneOf
149    );
150}
151
152function withExactlyOneSubschema(
153    schema,
154    rootSchema,
155    formData,
156    dependencyKey,
157    oneOf
158) {
159    // eslint-disable-next-line array-callback-return,consistent-return
160    const validSubschemas = oneOf.filter((subschema) => {
161        if (!subschema.properties) {
162            return false;
163        }
164        const { [dependencyKey]: conditionPropertySchema } = subschema.properties;
165        if (conditionPropertySchema) {
166            const conditionSchema = {
167                type: 'object',
168                properties: {
169                    [dependencyKey]: conditionPropertySchema,
170                },
171            };
172
173            return isValid(conditionSchema, formData);
174        }
175    });
176    if (validSubschemas.length !== 1) {
177        console.warn(
178            "ignoring oneOf in dependencies because there isn't exactly one subschema that is valid"
179        );
180        return schema;
181    }
182    const subschema = validSubschemas[0];
183    const {
184        // eslint-disable-next-line no-unused-vars
185        [dependencyKey]: conditionPropertySchema,
186        ...dependentSubschema
187    } = subschema.properties;
188    const dependentSchema = { ...subschema, properties: dependentSubschema };
189    return mergeSchemas(
190        schema,
191        retrieveSchema(dependentSchema, rootSchema, formData)
192    );
193}
194*/
195
196// resolve Schema - $ref
197// https://json-schema.org/understanding-json-schema/structuring.html#using-id-with-ref
198function resolveReference(schema, rootSchema, formData) {
199    // Retrieve the referenced schema definition.
200    const $refSchema = findSchemaDefinition(schema.$ref, rootSchema);
201    // Drop the $ref property of the source schema.
202    // eslint-disable-next-line no-unused-vars
203    const { $ref, ...localSchema } = schema;
204    // Update referenced schema definition with local schema properties.
205    return retrieveSchema(
206        { ...$refSchema, ...localSchema },
207        rootSchema,
208        formData
209    );
210}
211
212
213// 深度递归合并 合并allOf的每2项
214function mergeSchemaAllOf(...args) {
215    if (args.length < 2) return args[0];
216
217    let preVal = {};
218    const copyArgs = [...args];
219    while (copyArgs.length >= 2) {
220        const obj1 = isObject(copyArgs[0]) ? copyArgs[0] : {};
221        const obj2 = isObject(copyArgs[1]) ? copyArgs[1] : {};
222
223        preVal = Object.assign({}, obj1);
224        Object.keys(obj2).reduce((acc, key) => {
225            const left = obj1[key];
226            const right = obj2[key];
227
228            // 左右一边为object
229            if (isObject(left) || isObject(right)) {
230
231                // 两边同时为object
232                if (isObject(left) && isObject(right)) {
233                    acc[key] = mergeSchemaAllOf(left, right);
234                } else {
235                    // 其中一边为 object
236                    const [objTypeData, baseTypeData] = isObject(left) ? [left, right] : [right, left];
237
238                    if (key === 'additionalProperties') {
239                        // 适配类型: 一边配置了对象一边没配置或者true false
240                        // {
241                        //     additionalProperties: {
242                        //         type: 'string',
243                        //     },
244                        //     additionalProperties: false
245                        // }
246                        acc[key] = baseTypeData === true ? objTypeData : false; // default false
247                    } else {
248                        acc[key] = objTypeData;
249                    }
250                }
251                // 一边为array
252            } else if (Array.isArray(left) || Array.isArray(right)) {
253
254                // 同为数组取交集
255                if (Array.isArray(left) && Array.isArray(right)) {
256
257                    // 数组里面嵌套对象不支持 因为我不知道该怎么合并
258                    if (isObject(left[0]) || isObject(right[0])) {
259                        throw new Error('暂不支持如上数组对象元素合并');
260                    }
261
262                    // 交集
263                    const intersectionArray = intersection([].concat(left), [].concat(right));
264
265                    // 没有交集
266                    if (intersectionArray.length <= 0) {
267                        throw new Error('无法合并如上数据');
268                    }
269
270                    if (intersectionArray.length === 0 && key === 'type') {
271                        // 自己取出值
272                        acc[key] = intersectionArray[0];
273                    } else {
274                        acc[key] = intersectionArray;
275                    }
276                } else {
277                    // 其中一边为 Array
278                    // 查找包含关系
279                    const [arrayTypeData, baseTypeData] = Array.isArray(left) ? [left, right] : [right, left];
280                    // 空值直接合并另一边
281                    if (baseTypeData === undefined) {
282                        acc[key] = arrayTypeData;
283                    } else {
284                        if (!arrayTypeData.includes(baseTypeData)) {
285                            throw new Error('无法合并如下数据');
286                        }
287                        acc[key] = baseTypeData;
288                    }
289                }
290            } else if (left !== undefined && right !== undefined) {
291                // 两边都不是 undefined - 基础数据类型 string number boolean...
292                if (key === 'maxLength' || key === 'maximum' || key === 'maxItems' || key === 'exclusiveMaximum' || key === 'maxProperties') {
293                    acc[key] = Math.min(left, right);
294                } else if (key === 'minLength' || key === 'minimum' || key === 'minItems' || key === 'exclusiveMinimum' || key === 'minProperties') {
295                    acc[key] = Math.max(left, right);
296                } else if (key === 'multipleOf') {
297                    // 获取最小公倍数
298                    acc[key] = scm(left, right);
299                } else {
300                    // if (left !== right) {
301                    //     throw new Error('无法合并如下数据');
302                    // }
303                    acc[key] = left;
304                }
305            } else {
306                // 一边为undefined
307                acc[key] = left === undefined ? right : left;
308            }
309            return acc;
310        }, preVal);
311
312        // 先进先出
313        copyArgs.splice(0, 2, preVal);
314    }
315
316    return preVal;
317}
318
319// resolve Schema - allOf
320export function resolveAllOf(schema, rootSchema, formData) {
321    // allOf item中可能存在 $ref
322    const resolvedAllOfRefSchema = {
323        ...schema,
324        allOf: schema.allOf.map(allOfItem => retrieveSchema(allOfItem, rootSchema, formData)),
325    };
326
327    try {
328        const { allOf, ...originProperties } = resolvedAllOfRefSchema;
329        return mergeSchemaAllOf(originProperties, ...allOf);
330    } catch (e) {
331        console.error(`无法合并allOf,丢弃allOf配置继续渲染: \n${e}`);
332        // eslint-disable-next-line no-unused-vars
333        const { allOf: errAllOf, ...resolvedSchemaWithoutAllOf } = resolvedAllOfRefSchema;
334        return resolvedSchemaWithoutAllOf;
335    }
336}
337
338// resolve Schema
339function resolveSchema(schema, rootSchema = {}, formData = {}) {
340    // allOf 、$ref、dependencies 可能被同时配置
341
342    // allOf
343    if (schema.hasOwnProperty('allOf')) {
344        schema = resolveAllOf(schema, rootSchema, formData);
345    }
346
347    // $ref
348    if (schema.hasOwnProperty('$ref')) {
349        schema = resolveReference(schema, rootSchema, formData);
350    }
351
352    // dependencies
353    /*
354    if (schema.hasOwnProperty('dependencies')) {
355        const resolvedSchema = resolveDependencies(schema, rootSchema, formData);
356        schema = retrieveSchema(resolvedSchema, rootSchema, formData);
357    }
358    */
359
360    // additionalProperties
361    /*
362    const hasAdditionalProperties = schema.hasOwnProperty('additionalProperties') && schema.additionalProperties !== false;
363    if (hasAdditionalProperties) {
364        return stubExistingAdditionalProperties(
365            schema,
366            rootSchema,
367            formData
368        );
369    }
370    */
371
372    return schema;
373}
374
375// 这个函数将为formData中的每个键创建新的“属性”项
376// 查找到附加属性统一到properties[key]格式 并且打上标准
377/* function stubExistingAdditionalProperties(
378    schema,
379    rootSchema = {},
380    formData = {}
381) {
382    // clone the schema so we don't ruin the consumer's original
383    schema = {
384        ...schema,
385        properties: { ...schema.properties },
386    };
387
388    Object.keys(formData).forEach((key) => {
389        if (schema.properties.hasOwnProperty(key)) {
390            // No need to stub, our schema already has the property
391            return;
392        }
393
394        let additionalProperties;
395        if (schema.additionalProperties.hasOwnProperty('$ref')) {
396            additionalProperties = retrieveSchema(
397                { $ref: schema.additionalProperties.$ref },
398                rootSchema,
399                formData
400            );
401        } else if (schema.additionalProperties.hasOwnProperty('type')) {
402            additionalProperties = { ...schema.additionalProperties };
403        } else {
404            additionalProperties = { type: guessType(formData[key]) };
405        }
406
407        // The type of our new key should match the additionalProperties value;
408        // 把追加进去的属性设置为标准 schema格式,同时打上标志
409        schema.properties[key] = additionalProperties;
410        // Set our additional property flag so we know it was dynamically added
411        schema.properties[key][ADDITIONAL_PROPERTY_FLAG] = true;
412    });
413
414    return schema;
415} */
416
417// 索引当前节点
418export default function retrieveSchema(schema, rootSchema = {}, formData = {}) {
419    if (!isObject(schema)) {
420        return {};
421    }
422
423    return resolveSchema(schema, rootSchema, formData);
424}
425