typify_impl/
merge.rs

1// Copyright 2024 Oxide Computer Company
2
3use std::{
4    collections::{BTreeMap, BTreeSet},
5    iter::repeat,
6};
7
8use log::debug;
9use schemars::schema::{
10    ArrayValidation, InstanceType, NumberValidation, ObjectValidation, Schema, SchemaObject,
11    SingleOrVec, StringValidation, SubschemaValidation,
12};
13
14use crate::{util::ref_key, validate::schema_value_validate, RefKey};
15
16/// Merge all schemas in array of schemas. If the result is unsatisfiable, this
17/// returns `Schema::Bool(false)`.
18pub(crate) fn merge_all(schemas: &[Schema], defs: &BTreeMap<RefKey, Schema>) -> Schema {
19    try_merge_all(schemas, defs).unwrap_or(Schema::Bool(false))
20}
21
22fn try_merge_all(schemas: &[Schema], defs: &BTreeMap<RefKey, Schema>) -> Result<Schema, ()> {
23    debug!(
24        "merge all {}",
25        serde_json::to_string_pretty(schemas).unwrap(),
26    );
27
28    let merged_schema = match schemas {
29        [] => panic!("we should not be trying to merge an empty array of schemas"),
30        [only] => only.clone(),
31        [first, second, rest @ ..] => {
32            let mut out = try_merge_schema(first, second, defs)?;
33            for schema in rest {
34                out = try_merge_schema(&out, schema, defs)?;
35            }
36            out
37        }
38    };
39
40    Ok(merged_schema)
41}
42
43/// Given two additionalItems schemas that might be None--which is equivalent
44/// to Schema::Bool(true)--this returns the appropriate value. This is only
45/// called in a situation where additionalItems are relevant, so we prefer
46/// `true` to the (equivalent) absence of the schema. In other words, this will
47/// never return None.
48fn merge_additional_items(
49    a: Option<&Schema>,
50    b: Option<&Schema>,
51    defs: &BTreeMap<RefKey, Schema>,
52) -> Option<Schema> {
53    match (a, b) {
54        (None, None) => Some(Schema::Bool(true)),
55        _ => merge_additional_properties(a, b, defs),
56    }
57}
58
59/// Given two additionalProperties schemas that might be None--which is
60/// equivalent to Schema::Bool(true)--this returns the appropriate value. We
61/// prefer None to `true` for objects since the named properties are th main
62/// event.
63fn merge_additional_properties(
64    a: Option<&Schema>,
65    b: Option<&Schema>,
66    defs: &BTreeMap<RefKey, Schema>,
67) -> Option<Schema> {
68    match (a, b) {
69        (None, other) | (other, None) => other.cloned(),
70        (Some(aa), Some(bb)) => Some(try_merge_schema(aa, bb, defs).unwrap_or(Schema::Bool(false))),
71    }
72}
73
74fn merge_schema(a: &Schema, b: &Schema, defs: &BTreeMap<RefKey, Schema>) -> Schema {
75    try_merge_schema(a, b, defs).unwrap_or(Schema::Bool(false))
76}
77
78/// Merge two schemas returning the resulting schema. If the two schemas are
79/// incompatible (i.e. if there is no data that can satisfy them both
80/// simultaneously) then this returns Err.
81fn try_merge_schema(a: &Schema, b: &Schema, defs: &BTreeMap<RefKey, Schema>) -> Result<Schema, ()> {
82    match (a, b) {
83        (Schema::Bool(false), _) | (_, Schema::Bool(false)) => Err(()),
84        (Schema::Bool(true), other) | (other, Schema::Bool(true)) => Ok(other.clone()),
85
86        // If we have two references to the same schema, that's easy!
87        (
88            Schema::Object(SchemaObject {
89                reference: Some(a_ref_name),
90                ..
91            }),
92            Schema::Object(SchemaObject {
93                reference: Some(b_ref_name),
94                ..
95            }),
96        ) if a_ref_name == b_ref_name => Ok(Schema::Object(SchemaObject {
97            reference: Some(a_ref_name.clone()),
98            ..Default::default()
99        })),
100
101        // Resolve references here before we start to merge the objects.
102        //
103        // TODO: need to mitigate circular references so we don't go into a
104        // spin loop. We can do this by wrapping defs in a structure that
105        // remembers what we've already looked up; if we hit a cycle we can
106        // consider the proper handling, but it might be to ignore it--a
107        // circular allOf chain is a bit hard to reason about.
108        (
109            ref_schema @ Schema::Object(SchemaObject {
110                reference: Some(ref_name),
111                ..
112            }),
113            other,
114        )
115        | (
116            other,
117            ref_schema @ Schema::Object(SchemaObject {
118                reference: Some(ref_name),
119                ..
120            }),
121        ) => {
122            let key = ref_key(ref_name);
123            let resolved = defs
124                .get(&key)
125                .unwrap_or_else(|| panic!("unresolved reference: {}", ref_name));
126            let merged_schema = try_merge_schema(resolved, other, defs)?;
127
128            // If we merge a referenced schema with another schema **and**
129            // the resulting schema is equivalent to the referenced schema
130            // (i.e. the other schema is identical or less permissive) then we
131            // just return the reference schema rather than its contents.
132            if merged_schema.roughly(resolved) {
133                Ok(ref_schema.clone())
134            } else {
135                Ok(merged_schema)
136            }
137        }
138
139        (Schema::Object(aa), Schema::Object(bb)) => Ok(merge_schema_object(aa, bb, defs)?.into()),
140    }
141}
142
143fn merge_schema_object(
144    a: &SchemaObject,
145    b: &SchemaObject,
146    defs: &BTreeMap<RefKey, Schema>,
147) -> Result<SchemaObject, ()> {
148    debug!(
149        "merging {}\n{}",
150        serde_json::to_string_pretty(a).unwrap(),
151        serde_json::to_string_pretty(b).unwrap(),
152    );
153
154    assert!(a.reference.is_none());
155    assert!(b.reference.is_none());
156
157    let instance_type = merge_so_instance_type(a.instance_type.as_ref(), b.instance_type.as_ref())?;
158    let format = merge_so_format(a.format.as_ref(), b.format.as_ref())?;
159
160    let number = merge_so_number(a.number.as_deref(), b.number.as_deref())?;
161    let string = merge_so_string(a.string.as_deref(), b.string.as_deref())?;
162    let array = merge_so_array(a.array.as_deref(), b.array.as_deref(), defs)?;
163    let object = merge_so_object(a.object.as_deref(), b.object.as_deref(), defs)?;
164
165    let enum_values = merge_so_enum_values(
166        a.enum_values.as_ref(),
167        a.const_value.as_ref(),
168        b.enum_values.as_ref(),
169        b.const_value.as_ref(),
170    )?;
171
172    // We could clean up this schema to eliminate data irrelevant to the
173    // instance type, but logic in the conversion path should already handle
174    // that.
175    let mut merged_schema = SchemaObject {
176        metadata: None,
177        instance_type,
178        format,
179        enum_values,
180        const_value: None,
181        subschemas: None,
182        number,
183        string,
184        array,
185        object,
186        reference: None,
187        extensions: Default::default(),
188    };
189
190    // TODO if the merged schema is Default::default() then we should probably
191    // take some shortcut here...
192
193    // If we have subschemas for either schema then we merge the body of the
194    // two schemas and then do the appropriate merge with subschemas (i.e.
195    // potentially twice). This is effectively an `allOf` between the merged
196    // "body" schema and the component subschemas.
197    merged_schema = try_merge_with_subschemas(merged_schema, a.subschemas.as_deref(), defs)?;
198    merged_schema = try_merge_with_subschemas(merged_schema, b.subschemas.as_deref(), defs)?;
199
200    assert_ne!(merged_schema, Schema::Bool(false).into_object());
201
202    // Now that we've finalized the schemas, we take a pass through the
203    // enumerated values (if there are any) to weed out any that might be
204    // invalid.
205    if let Some(enum_values) = merged_schema.enum_values.take() {
206        let wrapped_schema = Schema::Object(merged_schema);
207        let enum_values = Some(
208            enum_values
209                .into_iter()
210                .filter(|value| schema_value_validate(&wrapped_schema, value, defs).is_ok())
211                .collect(),
212        );
213        let Schema::Object(new_merged_schema) = wrapped_schema else {
214            unreachable!()
215        };
216        merged_schema = new_merged_schema;
217        merged_schema.enum_values = enum_values;
218    }
219
220    debug!(
221        "merged {}\n{}\n|\nv\n{}",
222        serde_json::to_string_pretty(a).unwrap(),
223        serde_json::to_string_pretty(b).unwrap(),
224        serde_json::to_string_pretty(&merged_schema).unwrap(),
225    );
226
227    Ok(merged_schema)
228}
229
230fn merge_so_enum_values(
231    a_enum: Option<&Vec<serde_json::Value>>,
232    a_const: Option<&serde_json::Value>,
233    b_enum: Option<&Vec<serde_json::Value>>,
234    b_const: Option<&serde_json::Value>,
235) -> Result<Option<Vec<serde_json::Value>>, ()> {
236    let aa = match (a_enum, a_const) {
237        (None, None) => None,
238        (Some(enum_values), None) => Some(enum_values.clone()),
239        (None, Some(value)) => Some(vec![value.clone()]),
240        (Some(_), Some(_)) => unimplemented!(),
241    };
242    let bb = match (b_enum, b_const) {
243        (None, None) => None,
244        (Some(enum_values), None) => Some(enum_values.clone()),
245        (None, Some(value)) => Some(vec![value.clone()]),
246        (Some(_), Some(_)) => unimplemented!(),
247    };
248
249    match (aa, bb) {
250        (None, None) => Ok(None),
251        (None, Some(values)) | (Some(values), None) => Ok(Some(values)),
252        (Some(aa), Some(bb)) => {
253            let values = aa
254                .into_iter()
255                .filter(|value| bb.contains(value))
256                .collect::<Vec<_>>();
257
258            if values.is_empty() {
259                Err(())
260            } else {
261                Ok(Some(values))
262            }
263        }
264    }
265}
266
267/// Merge the schema with a subschema validation object. It's important that
268/// the return value reduces the complexity of the problem so avoid infinite
269/// recursion.
270pub(crate) fn try_merge_with_subschemas(
271    mut schema_object: SchemaObject,
272    maybe_subschemas: Option<&SubschemaValidation>,
273    defs: &BTreeMap<RefKey, Schema>,
274) -> Result<SchemaObject, ()> {
275    let Some(SubschemaValidation {
276        all_of,
277        any_of,
278        one_of,
279        not,
280        if_schema,
281        then_schema,
282        else_schema,
283    }) = maybe_subschemas
284    else {
285        return Ok(schema_object);
286    };
287
288    if if_schema.is_some() || then_schema.is_some() || else_schema.is_some() {
289        println!(
290            "{}",
291            serde_json::to_string_pretty(&maybe_subschemas).unwrap()
292        );
293        unimplemented!("if/then/else schemas are not supported");
294    }
295
296    if let Some(all_of) = all_of {
297        let merged_schema = all_of
298            .iter()
299            .try_fold(schema_object.into(), |schema, other| {
300                try_merge_schema(&schema, other, defs)
301            })?;
302        assert_ne!(merged_schema, Schema::Bool(false));
303        schema_object = merged_schema.into_object();
304    }
305
306    if let Some(not) = not {
307        schema_object = try_merge_schema_not(schema_object, not.as_ref(), defs)?;
308    }
309
310    // TODO: we should be able to handle a combined one_of and any_of... but
311    // I don't want to do that now because that would be a very strange
312    // construction.
313    assert!(any_of.is_none() || one_of.is_none());
314
315    if let Some(any_of) = any_of {
316        let merged_subschemas = try_merge_with_each_subschema(&schema_object, any_of, defs);
317
318        match merged_subschemas.len() {
319            0 => return Err(()),
320            1 => schema_object = merged_subschemas.into_iter().next().unwrap().into_object(),
321            _ => {
322                schema_object = SchemaObject {
323                    metadata: schema_object.metadata,
324                    subschemas: Some(Box::new(SubschemaValidation {
325                        any_of: Some(merged_subschemas),
326                        ..Default::default()
327                    })),
328                    ..Default::default()
329                }
330            }
331        }
332    }
333
334    if let Some(one_of) = one_of {
335        let merged_subschemas = try_merge_with_each_subschema(&schema_object, one_of, defs);
336
337        match merged_subschemas.len() {
338            0 => return Err(()),
339            1 => schema_object = merged_subschemas.into_iter().next().unwrap().into_object(),
340            _ => {
341                schema_object = SchemaObject {
342                    metadata: schema_object.metadata,
343                    subschemas: Some(Box::new(SubschemaValidation {
344                        one_of: Some(merged_subschemas),
345                        ..Default::default()
346                    })),
347                    ..Default::default()
348                }
349            }
350        }
351    }
352
353    Ok(schema_object)
354}
355
356fn try_merge_with_each_subschema(
357    schema_object: &SchemaObject,
358    subschemas: &[Schema],
359    defs: &BTreeMap<RefKey, Schema>,
360) -> Vec<Schema> {
361    let schema = Schema::Object(schema_object.clone());
362    // First we do a pairwise merge the schemas; if the result is invalid /
363    // unresolvable / never / whatever, we exclude it from the list. If it is
364    // valid, *then* we do the join to preserve information (though we probably
365    // only need to to *that* if at least one schema contains a ref). This
366    // could probably be an opportunity for memoization, but this is an
367    // infrequent construction so... whatever for now.
368    let joined_schemas = subschemas
369        .iter()
370        .enumerate()
371        .filter_map(|(ii, other)| {
372            // Skip if the merged schema is unsatisfiable.
373            let merged_schema = try_merge_schema(&schema, other, defs).ok()?;
374            // If the merged schema is equivalent to one or other of the
375            // individual schemas, use that.
376            // TODO is this right? Should we be "subtracting" out other schemas as below?
377            if merged_schema.roughly(&schema) {
378                Some(schema.clone())
379            } else if merged_schema.roughly(other) {
380                Some(other.clone())
381            } else {
382                let not_others = subschemas
383                    .iter()
384                    .enumerate()
385                    .filter(|(jj, _)| *jj != ii)
386                    .map(|(_, not_schema)| {
387                        Schema::Object(SchemaObject {
388                            subschemas: Some(Box::new(SubschemaValidation {
389                                not: Some(Box::new(not_schema.clone())),
390                                ..Default::default()
391                            })),
392                            ..Default::default()
393                        })
394                    });
395                let joined_schema = [schema.clone(), other.clone()]
396                    .into_iter()
397                    .chain(not_others)
398                    .collect::<Vec<_>>();
399                Some(
400                    SchemaObject {
401                        subschemas: Some(Box::new(SubschemaValidation {
402                            all_of: Some(joined_schema),
403                            ..Default::default()
404                        })),
405                        ..Default::default()
406                    }
407                    .into(),
408                )
409            }
410        })
411        .collect::<Vec<_>>();
412
413    joined_schemas
414}
415
416fn merge_schema_not(
417    schema: &Schema,
418    not_schema: &Schema,
419    defs: &BTreeMap<RefKey, Schema>,
420) -> Schema {
421    match (schema, not_schema) {
422        (_, Schema::Bool(true)) | (Schema::Bool(false), _) => Schema::Bool(false),
423
424        (any, Schema::Bool(false)) => any.clone(),
425
426        // TODO I don't know how to subtract something from nothing...
427        (Schema::Bool(true), Schema::Object(_)) => todo!(),
428
429        (Schema::Object(schema_object), any_not) => {
430            match try_merge_schema_not(schema_object.clone(), any_not, defs) {
431                Ok(schema_obj) => Schema::Object(schema_obj),
432                Err(_) => Schema::Bool(false),
433            }
434        }
435    }
436}
437
438/// "Subtract" the "not" schema from the schema object.
439///
440/// TODO Exactly where and how we handle not constructions is... tricky! As we
441/// find and support more and more useful uses of not we will likely move some
442/// of this into the conversion methods.
443fn try_merge_schema_not(
444    schema_object: SchemaObject,
445    not_schema: &Schema,
446    defs: &BTreeMap<RefKey, Schema>,
447) -> Result<SchemaObject, ()> {
448    debug!(
449        "try_merge_schema_not {}\n not:{}",
450        serde_json::to_string_pretty(&schema_object).unwrap(),
451        serde_json::to_string_pretty(not_schema).unwrap(),
452    );
453    match not_schema {
454        // Subtracting everything leaves nothing...
455        Schema::Bool(true) => Err(()),
456        // ... whereas subtracting nothing leaves everything.
457        Schema::Bool(false) => Ok(schema_object),
458        // Do the real work.
459        Schema::Object(not_object) => try_merge_schema_object_not(schema_object, not_object, defs),
460    }
461}
462
463fn try_merge_with_subschemas_not(
464    schema_object: SchemaObject,
465    not_subschemas: &SubschemaValidation,
466    defs: &BTreeMap<RefKey, Schema>,
467) -> Result<SchemaObject, ()> {
468    debug!("try_merge_with_subschemas_not");
469    match not_subschemas {
470        SubschemaValidation {
471            all_of: None,
472            any_of: Some(any_of),
473            one_of: None,
474            not: None,
475            if_schema: None,
476            then_schema: None,
477            else_schema: None,
478        } => {
479            // A not of anyOf is equivalent to an allOf of not... and the
480            // latter is easier to merge with other schemas by subtraction.
481            let all_of = any_of
482                .iter()
483                .map(|ss| {
484                    Schema::Object(SchemaObject {
485                        subschemas: Some(Box::new(SubschemaValidation {
486                            not: Some(Box::new(ss.clone())),
487                            ..Default::default()
488                        })),
489                        ..Default::default()
490                    })
491                })
492                .collect::<Vec<_>>();
493            let new_other = SchemaObject {
494                subschemas: Some(Box::new(SubschemaValidation {
495                    all_of: Some(all_of),
496                    ..Default::default()
497                })),
498                ..Default::default()
499            };
500            merge_schema_object(&schema_object, &new_other, defs)
501        }
502
503        SubschemaValidation {
504            all_of: None,
505            any_of: None,
506            one_of: None,
507            not: Some(not),
508            if_schema: None,
509            then_schema: None,
510            else_schema: None,
511        } => {
512            debug!("not not");
513            Ok(try_merge_schema(&schema_object.into(), not.as_ref(), defs)?.into_object())
514        }
515
516        // TODO this is a kludge
517        SubschemaValidation {
518            all_of: None,
519            any_of: None,
520            one_of: Some(_),
521            not: None,
522            if_schema: None,
523            then_schema: None,
524            else_schema: None,
525        } => Ok(schema_object),
526
527        SubschemaValidation {
528            all_of: None,
529            any_of: None,
530            one_of: None,
531            not: None,
532            if_schema: None,
533            then_schema: None,
534            else_schema: None,
535        } => Ok(schema_object),
536
537        SubschemaValidation {
538            all_of: Some(all_of),
539            any_of: None,
540            one_of: None,
541            not: None,
542            if_schema: None,
543            then_schema: None,
544            else_schema: None,
545        } => match try_merge_all(all_of, defs) {
546            Ok(merged_not_schema) => try_merge_schema_not(schema_object, &merged_not_schema, defs),
547            Err(_) => Ok(schema_object),
548        },
549
550        _ => todo!(
551            "{}\nnot: {}",
552            serde_json::to_string_pretty(&schema_object).unwrap(),
553            serde_json::to_string_pretty(&not_subschemas).unwrap(),
554        ),
555    }
556}
557
558fn try_merge_schema_object_not(
559    mut schema_object: SchemaObject,
560    not_object: &SchemaObject,
561    defs: &BTreeMap<RefKey, Schema>,
562) -> Result<SchemaObject, ()> {
563    // Examine enum values
564    match (&mut schema_object.enum_values, &not_object.enum_values) {
565        // Nothing to do.
566        (_, None) => {}
567        // TODO not sure quite what to do, so we'll ignore for now.
568        (None, Some(_)) => {}
569        (Some(values), Some(not_values)) => {
570            values.retain(|value| !not_values.contains(value));
571            if values.is_empty() {
572                return Err(());
573            }
574        }
575    }
576
577    match (&mut schema_object.object, &not_object.object) {
578        // Nothing to do.
579        (_, None) => {}
580
581        // TODO Not sure how to enforce the inverse here...
582        (None, Some(_)) => {}
583
584        // In the interesting case, we need to "subtract" object attributes.
585        (Some(obj), Some(not_obj)) => {
586            for (prop_name, prop_schema) in &mut obj.properties {
587                if let Some(not_prop_schema) = not_obj.properties.get(prop_name) {
588                    // For properties in both, we merge those schemas. Note
589                    // that if such a merging is unsatisfiable *and* the
590                    // property is required, we'll take the appropriate action
591                    // later.
592                    *prop_schema = merge_schema_not(prop_schema, not_prop_schema, defs);
593                }
594            }
595
596            for prop_name in not_obj.properties.keys() {
597                if !obj.properties.contains_key(prop_name) {
598                    // There's a property in the "not" that isn't in the
599                    // object. Most precisely we would say "this property may
600                    // have any value as long as it doesn't match this schema".
601                    // That's a little tricky right now, so instead we'll say
602                    // "you may not have a property with this name".
603                    let _ = obj
604                        .properties
605                        .insert(prop_name.clone(), Schema::Bool(false));
606                }
607            }
608
609            for not_required in &not_obj.required {
610                if !not_obj.properties.contains_key(not_required) {
611                    // No value is permissible
612                    let _ = obj
613                        .properties
614                        .insert(not_required.clone(), Schema::Bool(false));
615                }
616            }
617
618            // If any of the previous steps resulted in a required property
619            // being invalid, we note that here and identify the full schema as
620            // invalid.
621            for required in &obj.required {
622                if let Some(Schema::Bool(false)) = obj.properties.get(required) {
623                    return Err(());
624                }
625            }
626        }
627    }
628
629    if let Some(not_subschemas) = &not_object.subschemas {
630        schema_object = try_merge_with_subschemas_not(schema_object, not_subschemas, defs)?;
631    }
632
633    Ok(schema_object)
634}
635
636/// Merge instance types which could be None (meaning type is valid), a
637/// singleton type, or an array of types. An error result indicates that the
638/// types were non-overlappin and therefore incompatible.
639fn merge_so_instance_type(
640    a: Option<&SingleOrVec<InstanceType>>,
641    b: Option<&SingleOrVec<InstanceType>>,
642) -> Result<Option<SingleOrVec<InstanceType>>, ()> {
643    match (a, b) {
644        (None, None) => Ok(None),
645        (None, other @ Some(_)) | (other @ Some(_), None) => Ok(other.cloned()),
646
647        // If each has a single type, it must match.
648        (Some(SingleOrVec::Single(aa)), Some(SingleOrVec::Single(bb))) => {
649            if aa == bb {
650                Ok(Some(SingleOrVec::Single(aa.clone())))
651            } else {
652                Err(())
653            }
654        }
655
656        // If one has a single type and the other is an array, the type must
657        // appear in the array (and that's the resulting type).
658        (Some(SingleOrVec::Vec(types)), Some(SingleOrVec::Single(it)))
659        | (Some(SingleOrVec::Single(it)), Some(SingleOrVec::Vec(types))) => {
660            if types.contains(it) {
661                Ok(Some(SingleOrVec::Single(it.clone())))
662            } else {
663                Err(())
664            }
665        }
666
667        // If both are arrays, we take the intersection; if the intersection is
668        // empty, we return an error.
669        (Some(SingleOrVec::Vec(aa)), Some(SingleOrVec::Vec(bb))) => {
670            let types = aa
671                .iter()
672                .collect::<BTreeSet<_>>()
673                .intersection(&bb.iter().collect::<BTreeSet<_>>())
674                .cloned()
675                .cloned()
676                .collect::<Vec<_>>();
677
678            match types.len() {
679                // No intersection
680                0 => Err(()),
681                1 => Ok(Some(types.into_iter().next().unwrap().into())),
682                _ => Ok(Some(types.into())),
683            }
684        }
685    }
686}
687
688/// By and large, formats are pretty free-form and aren't really compatible
689/// with each other. That is to say, if you have two formats at the same time
690/// that's probably unsatisfiable. There are a few notable exceptions to this:
691///
692/// o integer widths -- take the narrowest
693/// o "ip" vs. "ipv4" / "ipv6" -- take the more specific ip flavor
694///
695/// TODO incorporate the instance type / types here to limit what formats we
696/// consider.
697/// TODO We might need to handle this in a very type-specific way in order to
698/// properly handle cases such as
699/// "int8" and "uint8" -> { min: 0, max: 127, format: None }
700fn merge_so_format(a: Option<&String>, b: Option<&String>) -> Result<Option<String>, ()> {
701    match (a.map(String::as_str), b.map(String::as_str)) {
702        (None, other) | (other, None) => Ok(other.map(String::from)),
703
704        (Some("ip"), result @ Some("ipv4"))
705        | (Some("ip"), result @ Some("ipv6"))
706        | (result @ Some("ipv4"), Some("ip"))
707        | (result @ Some("ipv6"), Some("ip")) => Ok(result.map(String::from)),
708
709        // Fine if they're both the same
710        (Some(aa), Some(bb)) if aa == bb => Ok(Some(aa.into())),
711        // ... they're not the same...
712        (Some(_), Some(_)) => Err(()),
713    }
714}
715
716fn merge_so_number(
717    a: Option<&NumberValidation>,
718    b: Option<&NumberValidation>,
719) -> Result<Option<Box<NumberValidation>>, ()> {
720    match (a, b) {
721        (None, other) | (other, None) => Ok(other.cloned().map(Box::new)),
722        (Some(a), Some(b)) if a == b => Ok(Some(Box::new(a.clone()))),
723        (Some(_), Some(_)) => {
724            unimplemented!("this is fairly fussy and I don't want to do it")
725        }
726    }
727}
728
729fn merge_so_string(
730    a: Option<&StringValidation>,
731    b: Option<&StringValidation>,
732) -> Result<Option<Box<StringValidation>>, ()> {
733    match (a, b) {
734        (None, other) | (other, None) => Ok(other.cloned().map(Box::new)),
735        (Some(a), Some(b)) if a == b => Ok(Some(Box::new(a.clone()))),
736        (Some(_), Some(_)) => {
737            unimplemented!("this is fairly fussy and I don't want to do it")
738        }
739    }
740}
741
742fn merge_so_array(
743    a: Option<&ArrayValidation>,
744    b: Option<&ArrayValidation>,
745    defs: &BTreeMap<RefKey, Schema>,
746) -> Result<Option<Box<ArrayValidation>>, ()> {
747    match (a, b) {
748        (None, other) | (other, None) => Ok(other.cloned().map(Box::new)),
749        (Some(aa), Some(bb)) => {
750            let max_items = choose_value(aa.max_items, bb.max_items, Ord::min);
751            let min_items = choose_value(aa.min_items, bb.min_items, Ord::max);
752            let unique_items =
753                choose_value(aa.unique_items, bb.unique_items, std::ops::BitOr::bitor);
754
755            // We can only contain one thing; we can't resolve the need to
756            // contain two different things.
757            let contains = match (aa.contains.as_deref(), bb.contains.as_deref()) {
758                (None, other) | (other, None) => other.cloned().map(Box::new),
759
760                // We could probably do a more complex "equivalency" check e.g.
761                // that would follow references.
762                (Some(aa_contains), Some(bb_contains)) if aa_contains == bb_contains => {
763                    Some(Box::new(aa_contains.clone()))
764                }
765
766                (Some(_), Some(_)) => return Err(()),
767            };
768
769            // If min > max the schema is unsatisfiable.
770            if let (Some(min), Some(max)) = (min_items, max_items) {
771                if min > max {
772                    return Err(());
773                }
774            }
775
776            // The items and additional_items fields need to be considered
777            // together, and the results of merging can affect the max.
778            //
779            // - If items is a singleton, additional_items is ignored and all
780            //   items in the array must obey the items schema.
781            //
782            // - If items is an array of size N, the Ith < N item must conform
783            //   to the Ith schema. Subsequent items must conform to
784            //   additional_items (so can be whatever if it is None =
785            //   Schema::Bool(true))
786            //
787            // - If items is None (i.e. absent) additional_items is ignored and
788            //   any value is permitted in any position of the array.
789            //
790            // Note that if there is a maximum array length specified and the
791            // items schema array is at least that long, additional_items is
792            // irrelevant so we omit it. This case appears several times below.
793
794            let (items, additional_items, max_items) = match (
795                (&aa.items, &aa.additional_items),
796                (&bb.items, &bb.additional_items),
797            ) {
798                // Both items are none; items and additional_items are None.
799                ((None, _), (None, _)) => (None, None, max_items),
800
801                // A None and a single-item; we can use the single item and
802                // additional_items are irrelevant.
803                ((None, _), (Some(SingleOrVec::Single(item)), _))
804                | ((Some(SingleOrVec::Single(item)), _), (None, _)) => {
805                    (Some(SingleOrVec::Single(item.clone())), None, max_items)
806                }
807
808                // A None and a array of schemas; we can take the array,
809                // modifying it only in consideration of the maximum length (if
810                // it is specified).
811                ((None, _), (Some(SingleOrVec::Vec(items)), additional_items))
812                | ((Some(SingleOrVec::Vec(items)), additional_items), (None, _)) => {
813                    match (max_items, items.len()) {
814                        (Some(max), len) if len >= max as usize => (
815                            Some(SingleOrVec::Vec(
816                                items.iter().take(max as usize).cloned().collect(),
817                            )),
818                            None,
819                            max_items,
820                        ),
821                        _ => (
822                            Some(SingleOrVec::Vec(items.clone())),
823                            additional_items.clone(),
824                            max_items,
825                        ),
826                    }
827                }
828
829                // Two single schemas, just merge them; additional_items would
830                // be irrelevant.
831                (
832                    (Some(SingleOrVec::Single(aa_single)), _),
833                    (Some(SingleOrVec::Single(bb_single)), _),
834                ) => (
835                    Some(SingleOrVec::Single(Box::new(try_merge_schema(
836                        aa_single, bb_single, defs,
837                    )?))),
838                    None,
839                    max_items,
840                ),
841
842                // A single item and an array of schemas. We merge the
843                // singleton with the array and additional_items as needed.
844                (
845                    (Some(SingleOrVec::Single(single)), _),
846                    (Some(SingleOrVec::Vec(items)), additional_items),
847                )
848                | (
849                    (Some(SingleOrVec::Vec(items)), additional_items),
850                    (Some(SingleOrVec::Single(single)), _),
851                ) => {
852                    let (items, allow_additional_items) = merge_items_array(
853                        items.iter().zip(repeat(single.as_ref())),
854                        min_items,
855                        max_items,
856                        defs,
857                    )?;
858
859                    if allow_additional_items {
860                        let additional_items = additional_items.as_deref().map_or_else(
861                            || Ok(single.as_ref().clone()),
862                            |additional_schema| try_merge_schema(additional_schema, single, defs),
863                        )?;
864                        (
865                            Some(SingleOrVec::Vec(items)),
866                            Some(Box::new(additional_items)),
867                            max_items,
868                        )
869                    } else {
870                        let len = items.len() as u32;
871                        (Some(SingleOrVec::Vec(items)), None, Some(len))
872                    }
873                }
874
875                // We need to pairwise merge schemas--as many as the longer
876                // of the two items arrays, limited by the max size of the
877                // array if one is specified. To do this we create
878                // iterators over the items followed by a repetition of the
879                // additional_items schema. We zip these together, merge, and
880                // limit them as appropriate.
881                (
882                    (Some(SingleOrVec::Vec(aa_items)), aa_additional_items),
883                    (Some(SingleOrVec::Vec(bb_items)), bb_additional_items),
884                ) => {
885                    let items_len = aa_items.len().max(bb_items.len());
886
887                    // Note that one of these .chain(repeat(..)) statements is
888                    // always irrelevant because we will always .take(..) the
889                    // shorter of the two (and may consume even fewer). We just
890                    // chain them both for simplicity and don't sweat it.
891                    let aa_items_iter = aa_items.iter().chain(repeat(
892                        aa_additional_items
893                            .as_deref()
894                            .unwrap_or(&Schema::Bool(true)),
895                    ));
896                    let bb_items_iter = bb_items.iter().chain(repeat(
897                        bb_additional_items
898                            .as_deref()
899                            .unwrap_or(&Schema::Bool(true)),
900                    ));
901                    let items_iter = aa_items_iter.zip(bb_items_iter).take(items_len);
902                    let (items, allow_additional_items) =
903                        merge_items_array(items_iter, min_items, max_items, defs)?;
904                    if allow_additional_items {
905                        let additional_items = merge_additional_items(
906                            aa_additional_items.as_deref(),
907                            bb_additional_items.as_deref(),
908                            defs,
909                        );
910                        (
911                            Some(SingleOrVec::Vec(items)),
912                            additional_items.map(Box::new),
913                            max_items,
914                        )
915                    } else {
916                        let len = items.len() as u32;
917                        (Some(SingleOrVec::Vec(items)), None, Some(len))
918                    }
919                }
920            };
921
922            Ok(Some(Box::new(ArrayValidation {
923                items,
924                additional_items,
925                max_items,
926                min_items,
927                unique_items,
928                contains,
929            })))
930        }
931    }
932}
933
934fn merge_items_array<'a>(
935    items_iter: impl Iterator<Item = (&'a Schema, &'a Schema)>,
936    min_items: Option<u32>,
937    max_items: Option<u32>,
938    defs: &BTreeMap<RefKey, Schema>,
939) -> Result<(Vec<Schema>, bool), ()> {
940    let mut items = Vec::new();
941    for (a, b) in items_iter {
942        match try_merge_schema(a, b, defs) {
943            Ok(schema) => {
944                items.push(schema);
945                if let Some(max) = max_items {
946                    if items.len() == max as usize {
947                        return Ok((items, false));
948                    }
949                }
950            }
951            Err(_) => {
952                let len = items.len() as u32;
953                if len < min_items.unwrap_or(1) {
954                    return Err(());
955                }
956                return Ok((items, false));
957            }
958        }
959    }
960
961    Ok((items, true))
962}
963
964/// Prefer Some over None and the result of `prefer` if both are Some.
965fn choose_value<T, F>(a: Option<T>, b: Option<T>, prefer: F) -> Option<T>
966where
967    F: FnOnce(T, T) -> T,
968{
969    match (a, b) {
970        (None, other) | (other, None) => other,
971        (Some(aa), Some(bb)) => Some(prefer(aa, bb)),
972    }
973}
974
975fn merge_so_object(
976    a: Option<&ObjectValidation>,
977    b: Option<&ObjectValidation>,
978    defs: &BTreeMap<RefKey, Schema>,
979) -> Result<Option<Box<ObjectValidation>>, ()> {
980    match (a, b) {
981        (None, other) | (other, None) => Ok(other.cloned().map(Box::new)),
982        (Some(aa), Some(bb)) => {
983            let required = aa
984                .required
985                .union(&bb.required)
986                .cloned()
987                .collect::<BTreeSet<_>>();
988            let additional_properties = merge_additional_properties(
989                aa.additional_properties.as_deref(),
990                bb.additional_properties.as_deref(),
991                defs,
992            );
993
994            enum AOrB<'a> {
995                A(&'a Schema),
996                B(&'a Schema),
997                Both(&'a Schema, &'a Schema),
998            }
999
1000            let properties = aa
1001                .properties
1002                .iter()
1003                // First characterize properties as being in a, b, or both.
1004                .map(|(name, a_schema)| {
1005                    if let Some(b_schema) = bb.properties.get(name) {
1006                        (name, AOrB::Both(a_schema, b_schema))
1007                    } else {
1008                        (name, AOrB::A(a_schema))
1009                    }
1010                })
1011                .chain(bb.properties.iter().filter_map(|(name, b_schema)| {
1012                    if aa.properties.contains_key(name) {
1013                        None
1014                    } else {
1015                        Some((name, AOrB::B(b_schema)))
1016                    }
1017                }))
1018                // Then resolve properties against the other full schema or
1019                // against the schemas for the properties if it appears in
1020                // both.
1021                .filter_map(|(name, ab_schema)| {
1022                    let resolved_schema = match ab_schema {
1023                        AOrB::A(a_schema) => filter_prop(name, a_schema, bb),
1024                        AOrB::B(b_schema) => filter_prop(name, b_schema, aa),
1025                        AOrB::Both(a_schema, b_schema) => merge_schema(a_schema, b_schema, defs),
1026                    };
1027                    match resolved_schema {
1028                        // If a required field is incompatible with the
1029                        // other schema, this object is unsatisfiable.
1030                        Schema::Bool(false) if required.contains(name) => Some(Err(())),
1031
1032                        // For incompatible, non-required fields we need to
1033                        // exclude the property from any values. If
1034                        // `additionalProperties` is `false` (i.e. excludes all
1035                        // other properties) then we can simply omit the
1036                        // property knowing that it (like all other unnamed
1037                        // properties) will not be permitted. Otherwise we
1038                        // include the optional property but with the `false`
1039                        // schema that means that no value will satisfy that
1040                        // property--the value would always be None and any
1041                        // serialization that included the named property would
1042                        // fail to deserialize.
1043                        //
1044                        // If we ever make use of `propertyNames`, it's
1045                        // conceivable that we might check it or modify it in
1046                        // this case, but that may be overly complex.
1047                        Schema::Bool(false) => {
1048                            if let Some(Schema::Bool(false)) = additional_properties {
1049                                None
1050                            } else {
1051                                Some(Ok((name.clone(), Schema::Bool(false))))
1052                            }
1053                        }
1054
1055                        // Compatible schema; proceed.
1056                        schema => Some(Ok((name.clone(), schema))),
1057                    }
1058                })
1059                .collect::<Result<schemars::Map<_, _>, _>>()?;
1060
1061            let max_properties = choose_value(aa.max_properties, bb.max_properties, Ord::min);
1062            let min_properties = choose_value(aa.min_properties, bb.min_properties, Ord::max);
1063
1064            if let (Some(min), Some(max)) = (min_properties, max_properties) {
1065                if min > max {
1066                    return Err(());
1067                }
1068            }
1069
1070            let object_validation = ObjectValidation {
1071                required,
1072                properties,
1073                additional_properties: additional_properties.map(Box::new),
1074                max_properties,
1075                min_properties,
1076                pattern_properties: Default::default(), // TODO
1077                property_names: Default::default(),     // TODO
1078            };
1079            Ok(Some(object_validation.into()))
1080        }
1081    }
1082}
1083
1084fn filter_prop(name: &str, prop_schema: &Schema, object_schema: &ObjectValidation) -> Schema {
1085    // We're only considering properties we *know* do not appear in the other
1086    // object's schema.
1087    assert!(!object_schema.properties.contains_key(name));
1088
1089    // TODO We should do a simple check here to validating the name against
1090    // propertyNames if that schema is specified.
1091    assert!(object_schema.property_names.is_none());
1092
1093    // TODO We should first check patternProperties, but that's such a pain in
1094    // the neck and so weird that I can't be bothered right now (until we hit
1095    // some examples in the wild). A match here would exempt the property from
1096    // the check below against additionalProperties.
1097    assert!(object_schema.pattern_properties.is_empty());
1098
1099    merge_additional(object_schema.additional_properties.as_deref(), prop_schema)
1100        .unwrap_or(Schema::Bool(false))
1101}
1102
1103fn merge_additional(additional: Option<&Schema>, prop_schema: &Schema) -> Result<Schema, ()> {
1104    match additional {
1105        // Anything is fine.
1106        Some(Schema::Bool(true)) | None => Ok(prop_schema.clone()),
1107        // Nothing is fine.
1108        Some(Schema::Bool(false)) => Err(()),
1109
1110        // Some things might be fine.
1111        Some(additional_schema) => Ok(SchemaObject {
1112            subschemas: Some(Box::new(SubschemaValidation {
1113                // TODO it would be a good idea to merge these now rather than
1114                // deferring that since the schemas might be unresolvable i.e.
1115                // they might have no intersection. However, a non-true/false/
1116                // absent additionalProperties within an allOf is an uncommon
1117                // pattern so this is likely good enough for the moment.
1118                all_of: Some(vec![additional_schema.clone(), prop_schema.clone()]),
1119                ..Default::default()
1120            })),
1121            ..Default::default()
1122        }
1123        .into()),
1124    }
1125}
1126
1127trait Roughly {
1128    fn roughly(&self, other: &Self) -> bool;
1129}
1130
1131impl Roughly for schemars::schema::Schema {
1132    fn roughly(&self, other: &Self) -> bool {
1133        match (self, other) {
1134            (Schema::Bool(a), Schema::Bool(b)) => a == b,
1135            (Schema::Bool(false), _) | (_, Schema::Bool(false)) => false,
1136
1137            (Schema::Bool(true), Schema::Object(other))
1138            | (Schema::Object(other), Schema::Bool(true)) => matches!(
1139                other,
1140                SchemaObject {
1141                    metadata: _,
1142                    instance_type: None,
1143                    format: None,
1144                    enum_values: None,
1145                    const_value: None,
1146                    subschemas: None,
1147                    number: None,
1148                    string: None,
1149                    array: None,
1150                    object: None,
1151                    reference: None,
1152                    extensions: _,
1153                }
1154            ),
1155
1156            (Schema::Object(a), Schema::Object(b)) => {
1157                a.instance_type == b.instance_type
1158                    && a.format == b.format
1159                    && a.enum_values == b.enum_values
1160                    && a.const_value == b.const_value
1161                    && roughly_subschemas(a.subschemas.as_deref(), b.subschemas.as_deref())
1162                    && a.number == b.number
1163                    && a.string == b.string
1164                    && roughly_array(a.array.as_deref(), b.array.as_deref())
1165                    && roughly_object(a.object.as_deref(), b.object.as_deref())
1166                    && a.reference == b.reference
1167            }
1168        }
1169    }
1170}
1171
1172fn roughly_subschemas(a: Option<&SubschemaValidation>, b: Option<&SubschemaValidation>) -> bool {
1173    match (a, b) {
1174        (None, None) => true,
1175        (None, Some(_)) => false,
1176        (Some(_), None) => false,
1177        (Some(aa), Some(bb)) => {
1178            roughly_schema_array(aa.all_of.as_deref(), bb.all_of.as_deref())
1179                && roughly_schema_array(aa.any_of.as_deref(), bb.any_of.as_deref())
1180                && roughly_schema_array(aa.one_of.as_deref(), bb.one_of.as_deref())
1181                && roughly_schema_option(aa.not.as_deref(), bb.not.as_deref())
1182                && roughly_schema_option(aa.if_schema.as_deref(), bb.if_schema.as_deref())
1183                && roughly_schema_option(aa.then_schema.as_deref(), bb.then_schema.as_deref())
1184                && roughly_schema_option(aa.else_schema.as_deref(), bb.else_schema.as_deref())
1185        }
1186    }
1187}
1188
1189fn roughly_schema_option(a: Option<&Schema>, b: Option<&Schema>) -> bool {
1190    match (a, b) {
1191        (None, None) => true,
1192        (None, Some(_)) => false,
1193        (Some(_), None) => false,
1194        (Some(aa), Some(bb)) => aa.roughly(bb),
1195    }
1196}
1197
1198fn roughly_schema_array(a: Option<&[Schema]>, b: Option<&[Schema]>) -> bool {
1199    match (a, b) {
1200        (None, None) => true,
1201        (None, Some(_)) => false,
1202        (Some(_), None) => false,
1203        (Some(aa), Some(bb)) => {
1204            // TODO We'll do it pairwise, but we should be looser..
1205            aa.len() == bb.len() && aa.iter().zip(bb.iter()).all(|(aaa, bbb)| aaa.roughly(bbb))
1206        }
1207    }
1208}
1209
1210fn roughly_array(a: Option<&ArrayValidation>, b: Option<&ArrayValidation>) -> bool {
1211    match (a, b) {
1212        (None, None) => true,
1213        (None, Some(_)) => false,
1214        (Some(_), None) => false,
1215        (Some(aa), Some(bb)) => match (&aa.items, &bb.items) {
1216            (None, None) => true,
1217            (None, Some(_)) => false,
1218            (Some(_), None) => false,
1219            (Some(SingleOrVec::Single(_)), Some(SingleOrVec::Vec(_))) => false,
1220            (Some(SingleOrVec::Vec(_)), Some(SingleOrVec::Single(_))) => false,
1221
1222            (Some(SingleOrVec::Single(aaa)), Some(SingleOrVec::Single(bbb))) => aaa.roughly(bbb),
1223            (Some(SingleOrVec::Vec(aaa)), Some(SingleOrVec::Vec(bbb))) => {
1224                roughly_schema_array(Some(aaa), Some(bbb))
1225            }
1226        },
1227    }
1228}
1229
1230fn roughly_object(a: Option<&ObjectValidation>, b: Option<&ObjectValidation>) -> bool {
1231    match (a, b) {
1232        (None, None) => true,
1233        (None, Some(_)) => false,
1234        (Some(_), None) => false,
1235        (Some(aa), Some(bb)) => {
1236            aa.max_properties == bb.max_properties
1237                && aa.min_properties == bb.min_properties
1238                && aa.required == bb.required
1239                && roughly_properties(&aa.properties, &bb.properties)
1240                && roughly_properties(&aa.pattern_properties, &bb.pattern_properties)
1241                && roughly_schema_option(
1242                    aa.additional_properties.as_deref(),
1243                    bb.additional_properties.as_deref(),
1244                )
1245                && roughly_schema_option(aa.property_names.as_deref(), bb.property_names.as_deref())
1246        }
1247    }
1248}
1249
1250fn roughly_properties(
1251    a: &schemars::Map<String, Schema>,
1252    b: &schemars::Map<String, Schema>,
1253) -> bool {
1254    a.len() == b.len()
1255        && a.iter()
1256            .zip(b.iter())
1257            .all(|((aa_name, aa_schema), (bb_name, bb_schema))| {
1258                aa_name == bb_name && aa_schema.roughly(bb_schema)
1259            })
1260}
1261
1262#[cfg(test)]
1263mod tests {
1264    use std::collections::BTreeMap;
1265
1266    use schemars::schema::InstanceType;
1267    use serde_json::json;
1268
1269    use crate::{merge::merge_so_instance_type, RefKey};
1270
1271    use super::try_merge_schema;
1272
1273    #[test]
1274    fn test_simple_merge() {
1275        let a = json!({
1276            "type": "object",
1277            "properties": {
1278                "result": {
1279                    "type": "string"
1280                }
1281            }
1282        });
1283        let b = json!({
1284            "required": ["result", "msg"],
1285            "properties": {
1286                "result": {
1287                    "enum": ["success"]
1288                },
1289                "msg": {
1290                    "type": "string"
1291                }
1292            }
1293        });
1294        let ab = json!({
1295            "type": "object",
1296            "required": ["result", "msg"],
1297            "properties": {
1298                "result": {
1299                    "type": "string",
1300                    "enum": ["success"]
1301                },
1302                "msg": {
1303                    "type": "string"
1304                }
1305            }
1306        });
1307
1308        let a = serde_json::from_value(a).unwrap();
1309        let b = serde_json::from_value(b).unwrap();
1310        let ab = serde_json::from_value(ab).unwrap();
1311
1312        let merged = try_merge_schema(&a, &b, &BTreeMap::default()).unwrap();
1313
1314        assert_eq!(merged, ab);
1315    }
1316
1317    #[test]
1318    fn test_nop_merge() {
1319        let a = json!({
1320                "type": "object",
1321                "required": [
1322                  "avatar_url",
1323                  "events_url",
1324                  "followers_url",
1325                  "following_url",
1326                  "gists_url",
1327                  "gravatar_id",
1328                  "html_url",
1329                  "id",
1330                  "login",
1331                  "node_id",
1332                  "organizations_url",
1333                  "received_events_url",
1334                  "repos_url",
1335                  "site_admin",
1336                  "starred_url",
1337                  "subscriptions_url",
1338                  "type",
1339                  "url"
1340                ],
1341                "properties": {
1342                  "avatar_url": {
1343                    "type": "string",
1344                    "format": "uri"
1345                  },
1346                  "email": {
1347                    "type": [
1348                      "string",
1349                      "null"
1350                    ]
1351                  },
1352                  "events_url": {
1353                    "type": "string",
1354                    "format": "uri-template"
1355                  },
1356                  "followers_url": {
1357                    "type": "string",
1358                    "format": "uri"
1359                  },
1360                  "following_url": {
1361                    "type": "string",
1362                    "format": "uri-template"
1363                  },
1364                  "gists_url": {
1365                    "type": "string",
1366                    "format": "uri-template"
1367                  },
1368                  "gravatar_id": {
1369                    "type": "string"
1370                  },
1371                  "html_url": {
1372                    "type": "string",
1373                    "format": "uri"
1374                  },
1375                  "id": {
1376                    "type": "integer"
1377                  },
1378                  "login": {
1379                    "type": "string"
1380                  },
1381                  "name": {
1382                    "type": "string"
1383                  },
1384                  "node_id": {
1385                    "type": "string"
1386                  },
1387                  "organizations_url": {
1388                    "type": "string",
1389                    "format": "uri"
1390                  },
1391                  "received_events_url": {
1392                    "type": "string",
1393                    "format": "uri"
1394                  },
1395                  "repos_url": {
1396                    "type": "string",
1397                    "format": "uri"
1398                  },
1399                  "site_admin": {
1400                    "type": "boolean"
1401                  },
1402                  "starred_url": {
1403                    "type": "string",
1404                    "format": "uri-template"
1405                  },
1406                  "subscriptions_url": {
1407                    "type": "string",
1408                    "format": "uri"
1409                  },
1410                  "type": {
1411                    "type": "string",
1412                    "enum": [
1413                      "Bot",
1414                      "User",
1415                      "Organization"
1416                    ]
1417                  },
1418                  "url": {
1419                    "type": "string",
1420                    "format": "uri"
1421                  }
1422                },
1423                "additionalProperties": false
1424              }
1425
1426        );
1427        let b = json!({});
1428
1429        let a = serde_json::from_value(a).unwrap();
1430        let b = serde_json::from_value(b).unwrap();
1431
1432        let merged = try_merge_schema(&a, &b, &BTreeMap::default()).unwrap();
1433
1434        assert_eq!(merged, a);
1435    }
1436
1437    #[test]
1438    fn test_merge_instance_types() {
1439        // Simple cases
1440        assert_eq!(merge_so_instance_type(None, None), Ok(None));
1441
1442        assert_eq!(
1443            merge_so_instance_type(None, Some(&InstanceType::Integer.into())),
1444            Ok(Some(InstanceType::Integer.into())),
1445        );
1446        assert_eq!(
1447            merge_so_instance_type(Some(&InstanceType::Null.into()), None),
1448            Ok(Some(InstanceType::Null.into())),
1449        );
1450
1451        // Containment
1452        assert_eq!(
1453            merge_so_instance_type(
1454                Some(&vec![InstanceType::Integer, InstanceType::Number].into()),
1455                Some(&InstanceType::Integer.into())
1456            ),
1457            Ok(Some(InstanceType::Integer.into())),
1458        );
1459        assert_eq!(
1460            merge_so_instance_type(
1461                Some(&vec![InstanceType::Integer, InstanceType::Number].into()),
1462                Some(&InstanceType::Null.into())
1463            ),
1464            Err(()),
1465        );
1466        assert_eq!(
1467            merge_so_instance_type(
1468                Some(&vec![InstanceType::Integer, InstanceType::Number].into()),
1469                Some(&vec![InstanceType::Integer, InstanceType::Null].into()),
1470            ),
1471            Ok(Some(InstanceType::Integer.into())),
1472        );
1473        assert_eq!(
1474            merge_so_instance_type(
1475                Some(
1476                    &vec![
1477                        InstanceType::Object,
1478                        InstanceType::Integer,
1479                        InstanceType::Number
1480                    ]
1481                    .into()
1482                ),
1483                Some(
1484                    &vec![
1485                        InstanceType::Object,
1486                        InstanceType::Integer,
1487                        InstanceType::Null
1488                    ]
1489                    .into()
1490                ),
1491            ),
1492            Ok(Some(
1493                vec![InstanceType::Object, InstanceType::Integer,].into()
1494            )),
1495        );
1496        assert_eq!(
1497            merge_so_instance_type(
1498                Some(
1499                    &vec![
1500                        InstanceType::Object,
1501                        InstanceType::Integer,
1502                        InstanceType::Number
1503                    ]
1504                    .into()
1505                ),
1506                Some(
1507                    &vec![
1508                        InstanceType::Array,
1509                        InstanceType::Boolean,
1510                        InstanceType::Null
1511                    ]
1512                    .into()
1513                ),
1514            ),
1515            Err(()),
1516        );
1517    }
1518
1519    #[test]
1520    fn test_array_fail() {
1521        let a = json!({
1522            "type": "array",
1523            "items": { "type": "integer" }
1524        });
1525        let b = json!({
1526            "type": "array",
1527            "items": { "type": "string" }
1528        });
1529        let a = serde_json::from_value(a).unwrap();
1530        let b = serde_json::from_value(b).unwrap();
1531
1532        let ab = try_merge_schema(&a, &b, &Default::default());
1533
1534        assert!(ab.is_err());
1535
1536        let a = json!({
1537            "type": "array",
1538            "items": [{ "type": "integer" }, {"type": "object"}]
1539        });
1540        let b = json!({
1541            "type": "array",
1542            "items": { "type": "string" }
1543        });
1544        let a = serde_json::from_value(a).unwrap();
1545        let b = serde_json::from_value(b).unwrap();
1546
1547        let ab = try_merge_schema(&a, &b, &Default::default());
1548
1549        assert!(ab.is_err());
1550
1551        let a = json!({
1552            "type": "array",
1553            "items": [{ "type": "integer" }, {"type": "object"}]
1554        });
1555        let b = json!({
1556            "type": "array",
1557            "items": [{ "type": "string" }, { "type": "object" }]
1558        });
1559        let a = serde_json::from_value(a).unwrap();
1560        let b = serde_json::from_value(b).unwrap();
1561
1562        let ab = try_merge_schema(&a, &b, &Default::default());
1563
1564        assert!(
1565            ab.is_err(),
1566            "{}",
1567            serde_json::to_string_pretty(&ab).unwrap(),
1568        );
1569
1570        let a = json!({
1571            "type": "array",
1572            "items": [
1573                { "type": "integer" },
1574                { "type": "integer" },
1575                { "type": "integer" },
1576                { "type": "integer" }
1577            ],
1578            "minItems": 3,
1579            "maxItems": 4
1580        });
1581        let b = json!({
1582            "type": "array",
1583            "items": [
1584                { "type": "integer" },
1585                { "type": "integer" }
1586            ],
1587            "additionalItems": { "type": "string" },
1588            "maxItems": 100
1589        });
1590        let a = serde_json::from_value(a).unwrap();
1591        let b = serde_json::from_value(b).unwrap();
1592
1593        let ab = try_merge_schema(&a, &b, &Default::default());
1594
1595        assert!(
1596            ab.is_err(),
1597            "{}",
1598            serde_json::to_string_pretty(&ab).unwrap(),
1599        );
1600    }
1601
1602    #[test]
1603    fn test_array_good1() {
1604        let a = json!({
1605            "type": "array",
1606            "items": [
1607                { "type": "integer" },
1608                { "type": "integer" },
1609                { "type": "integer" },
1610                { "type": "integer" }
1611            ],
1612            "maxItems": 4
1613        });
1614        let b = json!({
1615            "type": "array",
1616            "items": [
1617                { "type": "integer" },
1618                { "type": "integer" }
1619            ],
1620            "additionalItems": { "type": "integer" },
1621            "maxItems": 3
1622        });
1623        let ab = json!({
1624            "type": "array",
1625            "items": [
1626                { "type": "integer" },
1627                { "type": "integer" },
1628                { "type": "integer" }
1629            ],
1630            "maxItems": 3
1631        });
1632
1633        let a = serde_json::from_value(a).unwrap();
1634        let b = serde_json::from_value(b).unwrap();
1635        let ab = serde_json::from_value(ab).unwrap();
1636
1637        let merged = try_merge_schema(&a, &b, &BTreeMap::default()).unwrap();
1638        assert_eq!(
1639            merged,
1640            ab,
1641            "{}",
1642            serde_json::to_string_pretty(&merged).unwrap(),
1643        )
1644    }
1645
1646    #[test]
1647    fn test_array_good2() {
1648        let a = json!({
1649            "type": "array",
1650            "items": [
1651                { "type": "integer" },
1652                { "type": "integer" },
1653                { "type": "integer" }
1654            ],
1655            "maxItems": 4
1656        });
1657        let b = json!({
1658            "type": "array",
1659            "items": [
1660                { "type": "integer" },
1661                { "type": "integer" }
1662            ],
1663            "additionalItems": true,
1664        });
1665        let ab = json!({
1666            "type": "array",
1667            "items": [
1668                { "type": "integer" },
1669                { "type": "integer" },
1670                { "type": "integer" }
1671            ],
1672            "additionalItems": true,
1673            "maxItems": 4
1674        });
1675
1676        let a = serde_json::from_value(a).unwrap();
1677        let b = serde_json::from_value(b).unwrap();
1678        let ab = serde_json::from_value(ab).unwrap();
1679
1680        let merged = try_merge_schema(&a, &b, &BTreeMap::default()).unwrap();
1681        assert_eq!(
1682            merged,
1683            ab,
1684            "{}",
1685            serde_json::to_string_pretty(&merged).unwrap(),
1686        )
1687    }
1688
1689    #[test]
1690    fn test_array_good3() {
1691        let a = json!({
1692            "type": "array",
1693            "items": [
1694                { "type": "integer" },
1695                { "type": "integer" },
1696                { "type": "integer" }
1697            ],
1698            "maxItems": 4
1699        });
1700        let b = json!({
1701            "type": "array",
1702            "items": [
1703                { "type": "integer" },
1704                { "type": "integer" },
1705                { "type": "string" }
1706            ],
1707            "additionalItems": true,
1708        });
1709        let ab = json!({
1710            "type": "array",
1711            "items": [
1712                { "type": "integer" },
1713                { "type": "integer" }
1714            ],
1715            "maxItems": 2
1716        });
1717
1718        let a = serde_json::from_value(a).unwrap();
1719        let b = serde_json::from_value(b).unwrap();
1720        let ab = serde_json::from_value(ab).unwrap();
1721
1722        let merged = try_merge_schema(&a, &b, &BTreeMap::default()).unwrap();
1723        assert_eq!(
1724            merged,
1725            ab,
1726            "{}",
1727            serde_json::to_string_pretty(&merged).unwrap(),
1728        )
1729    }
1730
1731    #[test]
1732    fn test_match_one_of() {
1733        let a = json!({
1734            "$ref": "#/definitions/x"
1735        });
1736        let b = json!({
1737            "oneOf": [
1738                {
1739                    "$ref": "#/definitions/x"
1740                },
1741                {
1742                    "type": "null"
1743                }
1744            ]
1745        });
1746        let x = json!({
1747            "type": "string"
1748        });
1749        let ab = json!({
1750            "$ref": "#/definitions/x"
1751        });
1752
1753        let a = serde_json::from_value(a).unwrap();
1754        let b = serde_json::from_value(b).unwrap();
1755        let x: schemars::schema::Schema = serde_json::from_value(x).unwrap();
1756        let ab = serde_json::from_value(ab).unwrap();
1757
1758        let merged = try_merge_schema(
1759            &a,
1760            &b,
1761            &[(RefKey::Def("x".to_string()), x)].into_iter().collect(),
1762        )
1763        .unwrap();
1764        assert_eq!(
1765            merged,
1766            ab,
1767            "{}",
1768            serde_json::to_string_pretty(&merged).unwrap(),
1769        )
1770    }
1771
1772    #[test]
1773    fn test_all_of_one_of_identity() {
1774        let a = json!({
1775            "oneOf": [
1776                {
1777                    "$ref": "#/definitions/x"
1778                },
1779                {
1780                    "type": "null"
1781                }
1782            ]
1783        });
1784        let b = json!({
1785            "oneOf": [
1786                {
1787                    "$ref": "#/definitions/x"
1788                },
1789                {
1790                    "type": "null"
1791                }
1792            ]
1793        });
1794        let x = json!({
1795            "title": "x",
1796            "type": "string"
1797        });
1798        let ab = json!({
1799            "oneOf": [
1800                {
1801                    "$ref": "#/definitions/x"
1802                },
1803                {
1804                    "type": "null"
1805                }
1806            ]
1807        });
1808
1809        let a = serde_json::from_value(a).unwrap();
1810        let b = serde_json::from_value(b).unwrap();
1811        let x: schemars::schema::Schema = serde_json::from_value(x).unwrap();
1812        let ab = serde_json::from_value(ab).unwrap();
1813
1814        let merged = try_merge_schema(
1815            &a,
1816            &b,
1817            &[(RefKey::Def("x".to_string()), x)].into_iter().collect(),
1818        )
1819        .unwrap();
1820        assert_eq!(
1821            merged,
1822            ab,
1823            "{}",
1824            serde_json::to_string_pretty(&merged).unwrap(),
1825        )
1826    }
1827}