Skip to main content

cml/types/
document.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use indexmap::IndexMap;
6use itertools::Itertools;
7
8use crate::types::capability::ContextCapability;
9use crate::types::child::ContextChild;
10use crate::types::collection::ContextCollection;
11use crate::types::common::*;
12use crate::types::environment::ContextEnvironment;
13use crate::types::expose::ContextExpose;
14use crate::types::offer::ContextOffer;
15use crate::types::program::ContextProgram;
16use crate::types::r#use::ContextUse;
17use crate::{
18    CanonicalizeContext, Capability, CapabilityFromRef, Child, Collection, ConfigKey,
19    ConfigValueType, Environment, Error, Expose, Location, Offer, Program, Use, merge_spanned_vec,
20};
21
22pub use cm_types::{
23    Availability, BorrowedName, BoundedName, DeliveryType, DependencyType, HandleType, Name,
24    OnTerminate, ParseError, Path, RelativePath, StartupMode, StorageId, Url,
25};
26use reference_doc::ReferenceDoc;
27use serde::{Deserialize, Serialize};
28use serde_json::{Map, Value};
29
30use std::collections::{BTreeMap, HashMap, HashSet};
31use std::sync::Arc;
32use std::{cmp, path};
33
34/// # Component manifest (`.cml`) reference
35///
36/// A `.cml` file contains a single json5 object literal with the keys below.
37///
38/// Where string values are expected, a list of valid values is generally documented.
39/// The following string value types are reused and must follow specific rules.
40///
41/// The `.cml` file is compiled into a FIDL wire format (`.cm`) file.
42///
43/// ## String types
44///
45/// ### Names {#names}
46///
47/// Both capabilities and a component's children are named. A name string may
48/// consist of one or more of the following characters: `A-Z`, `a-z`, `0-9`,
49/// `_`, `.`, `-`. It must not exceed 255 characters in length and may not start
50/// with `.` or `-`.
51///
52/// ### Paths {#paths}
53///
54/// Paths are sequences of [names](#names) delimited by the `/` character. A path
55/// must not exceed 4095 characters in length. Throughout the document,
56///
57/// - Relative paths cannot start with the `/` character.
58/// - Namespace and outgoing directory paths must start with the `/` character.
59///
60/// ### References {#references}
61///
62/// A reference string takes the form of `#<name>`, where `<name>` refers to the name of a child:
63///
64/// - A [static child instance][doc-static-children] whose name is
65///     `<name>`, or
66/// - A [collection][doc-collections] whose name is `<name>`.
67///
68/// [doc-static-children]: /docs/concepts/components/v2/realms.md#static-children
69/// [doc-collections]: /docs/concepts/components/v2/realms.md#collections
70/// [doc-protocol]: /docs/concepts/components/v2/capabilities/protocol.md
71/// [doc-dictionaries]: /reference/fidl/fuchsia.component.decl#Dictionary
72/// [doc-directory]: /docs/concepts/components/v2/capabilities/directory.md
73/// [doc-storage]: /docs/concepts/components/v2/capabilities/storage.md
74/// [doc-resolvers]: /docs/concepts/components/v2/capabilities/resolver.md
75/// [doc-runners]: /docs/concepts/components/v2/capabilities/runner.md
76/// [doc-event]: /docs/concepts/components/v2/capabilities/event.md
77/// [doc-service]: /docs/concepts/components/v2/capabilities/service.md
78/// [doc-directory-rights]: /docs/concepts/components/v2/capabilities/directory.md#directory-capability-rights
79///
80/// ## Top-level keys {#document}
81#[derive(ReferenceDoc, Deserialize, Debug, Default, PartialEq, Serialize)]
82#[serde(deny_unknown_fields)]
83pub struct Document {
84    /// The optional `include` property describes zero or more other component manifest
85    /// files to be merged into this component manifest. For example:
86    ///
87    /// ```json5
88    /// include: [ "syslog/client.shard.cml" ]
89    /// ```
90    ///
91    /// In the example given above, the component manifest is including contents from a
92    /// manifest shard provided by the `syslog` library, thus ensuring that the
93    /// component functions correctly at runtime if it attempts to write to `syslog`. By
94    /// convention such files are called "manifest shards" and end with `.shard.cml`.
95    ///
96    /// Include paths prepended with `//` are relative to the source root of the Fuchsia
97    /// checkout. However, include paths not prepended with `//`, as in the example
98    /// above, are resolved from Fuchsia SDK libraries (`//sdk/lib`) that export
99    /// component manifest shards.
100    ///
101    /// For reference, inside the Fuchsia checkout these two include paths are
102    /// equivalent:
103    ///
104    /// * `syslog/client.shard.cml`
105    /// * `//sdk/lib/syslog/client.shard.cml`
106    ///
107    /// You can review the outcome of merging any and all includes into a component
108    /// manifest file by invoking the following command:
109    ///
110    /// Note: The `fx` command below is for developers working in a Fuchsia source
111    /// checkout environment.
112    ///
113    /// ```sh
114    /// fx cmc include {{ "<var>" }}cml_file{{ "</var>" }} --includeroot $FUCHSIA_DIR --includepath $FUCHSIA_DIR/sdk/lib
115    /// ```
116    ///
117    /// Includes can cope with duplicate [`use`], [`offer`], [`expose`], or [`capabilities`]
118    /// declarations referencing the same capability, as long as the properties are the same. For
119    /// example:
120    ///
121    /// ```json5
122    /// // my_component.cml
123    /// include: [ "syslog.client.shard.cml" ]
124    /// use: [
125    ///     {
126    ///         protocol: [
127    ///             "fuchsia.posix.socket.Provider",
128    ///             "fuchsia.logger.LogSink",
129    ///         ],
130    ///     },
131    /// ],
132    ///
133    /// // syslog.client.shard.cml
134    /// use: [
135    ///     { protocol: "fuchsia.logger.LogSink" },
136    /// ],
137    /// ```
138    ///
139    /// In this example, the contents of the merged file will be the same as my_component.cml --
140    /// `fuchsia.logger.LogSink` is deduped.
141    ///
142    /// However, this would fail to compile:
143    ///
144    /// ```json5
145    /// // my_component.cml
146    /// include: [ "syslog.client.shard.cml" ]
147    /// use: [
148    ///     {
149    ///         protocol: "fuchsia.logger.LogSink",
150    ///         // properties for fuchsia.logger.LogSink don't match
151    ///         from: "#archivist",
152    ///     },
153    /// ],
154    ///
155    /// // syslog.client.shard.cml
156    /// use: [
157    ///     { protocol: "fuchsia.logger.LogSink" },
158    /// ],
159    /// ```
160    ///
161    /// An exception to this constraint is the `availability` property. If two routing declarations
162    /// are identical, and one availability is stronger than the other, the availability will be
163    /// "promoted" to the stronger value (if `availability` is missing, it defaults to `required`).
164    /// For example:
165    ///
166    /// ```json5
167    /// // my_component.cml
168    /// include: [ "syslog.client.shard.cml" ]
169    /// use: [
170    ///     {
171    ///         protocol: [
172    ///             "fuchsia.posix.socket.Provider",
173    ///             "fuchsia.logger.LogSink",
174    ///         ],
175    ///         availability: "optional",
176    ///     },
177    /// ],
178    ///
179    /// // syslog.client.shard.cml
180    /// use: [
181    ///     {
182    ///         protocol: "fuchsia.logger.LogSink
183    ///         availability: "required",  // This is the default
184    ///     },
185    /// ],
186    /// ```
187    ///
188    /// Becomes:
189    ///
190    /// ```json5
191    /// use: [
192    ///     {
193    ///         protocol: "fuchsia.posix.socket.Provider",
194    ///         availability: "optional",
195    ///     },
196    ///     {
197    ///         protocol: "fuchsia.logger.LogSink",
198    ///         availability: "required",
199    ///     },
200    /// ],
201    /// ```
202    ///
203    /// Includes are transitive, meaning that shards can have their own includes.
204    ///
205    /// Include paths can have diamond dependencies. For instance this is valid:
206    /// A includes B, A includes C, B includes D, C includes D.
207    /// In this case A will transitively include B, C, D.
208    ///
209    /// Include paths cannot have cycles. For instance this is invalid:
210    /// A includes B, B includes A.
211    /// A cycle such as the above will result in a compile-time error.
212    ///
213    /// [`use`]: #use
214    /// [`offer`]: #offer
215    /// [`expose`]: #expose
216    /// [`capabilities`]: #capabilities
217    #[serde(skip_serializing_if = "Option::is_none")]
218    pub include: Option<Vec<String>>,
219
220    /// Components that are executable include a `program` section. The `program`
221    /// section must set the `runner` property to select a [runner][doc-runners] to run
222    /// the component. The format of the rest of the `program` section is determined by
223    /// that particular runner.
224    ///
225    /// # ELF runners {#elf-runners}
226    ///
227    /// If the component uses the ELF runner, `program` must include the following
228    /// properties, at a minimum:
229    ///
230    /// - `runner`: must be set to `"elf"`
231    /// - `binary`: Package-relative path to the executable binary
232    /// - `args` _(optional)_: List of arguments
233    ///
234    /// Example:
235    ///
236    /// ```json5
237    /// program: {
238    ///     runner: "elf",
239    ///     binary: "bin/hippo",
240    ///     args: [ "Hello", "hippos!" ],
241    /// },
242    /// ```
243    ///
244    /// For a complete list of properties, see: [ELF Runner](/docs/concepts/components/v2/elf_runner.md)
245    ///
246    /// # Other runners {#other-runners}
247    ///
248    /// If a component uses a custom runner, values inside the `program` stanza other
249    /// than `runner` are specific to the runner. The runner receives the arguments as a
250    /// dictionary of key and value pairs. Refer to the specific runner being used to
251    /// determine what keys it expects to receive, and how it interprets them.
252    ///
253    /// [doc-runners]: /docs/concepts/components/v2/capabilities/runner.md
254    #[reference_doc(json_type = "object")]
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub program: Option<Program>,
257
258    /// The `children` section declares child component instances as described in
259    /// [Child component instances][doc-children].
260    ///
261    /// [doc-children]: /docs/concepts/components/v2/realms.md#child-component-instances
262    #[reference_doc(recurse)]
263    #[serde(skip_serializing_if = "Option::is_none")]
264    pub children: Option<Vec<Child>>,
265
266    /// The `collections` section declares collections as described in
267    /// [Component collections][doc-collections].
268    #[reference_doc(recurse)]
269    #[serde(skip_serializing_if = "Option::is_none")]
270    pub collections: Option<Vec<Collection>>,
271
272    /// The `environments` section declares environments as described in
273    /// [Environments][doc-environments].
274    ///
275    /// [doc-environments]: /docs/concepts/components/v2/environments.md
276    #[reference_doc(recurse)]
277    #[serde(skip_serializing_if = "Option::is_none")]
278    pub environments: Option<Vec<Environment>>,
279
280    /// The `capabilities` section defines capabilities that are provided by this component.
281    /// Capabilities that are [offered](#offer) or [exposed](#expose) from `self` must be declared
282    /// here.
283    ///
284    /// # Capability fields
285    ///
286    /// This supports the following capability keys. Exactly one of these must be set:
287    ///
288    /// - `protocol`: (_optional `string or array of strings`_)
289    /// - `service`: (_optional `string or array of strings`_)
290    /// - `directory`: (_optional `string`_)
291    /// - `storage`: (_optional `string`_)
292    /// - `runner`: (_optional `string`_)
293    /// - `resolver`: (_optional `string`_)
294    /// - `event_stream`: (_optional `string or array of strings`_)
295    /// - `dictionary`: (_optional `string`_)
296    /// - `config`: (_optional `string`_)
297    ///
298    /// # Additional fields
299    ///
300    /// This supports the following additional fields:
301    /// [glossary.outgoing directory]: /docs/glossary/README.md#outgoing-directory
302    #[reference_doc(recurse)]
303    #[serde(skip_serializing_if = "Option::is_none")]
304    pub capabilities: Option<Vec<Capability>>,
305
306    /// For executable components, declares capabilities that this
307    /// component requires in its [namespace][glossary.namespace] at runtime.
308    /// Capabilities are routed from the `parent` unless otherwise specified,
309    /// and each capability must have a valid route through all components between
310    /// this component and the capability's source.
311    ///
312    /// # Capability fields
313    ///
314    /// This supports the following capability keys. Exactly one of these must be set:
315    ///
316    /// - `service`: (_optional `string or array of strings`_)
317    /// - `directory`: (_optional `string`_)
318    /// - `protocol`: (_optional `string or array of strings`_)
319    /// - `dictionary`: (_optional `string`_)
320    /// - `storage`: (_optional `string`_)
321    /// - `event_stream`: (_optional `string or array of strings`_)
322    /// - `runner`: (_optional `string`_)
323    /// - `config`: (_optional `string`_)
324    ///
325    /// # Additional fields
326    ///
327    /// This supports the following additional fields:
328    /// [glossary.namespace]: /docs/glossary/README.md#namespace
329    #[reference_doc(recurse)]
330    #[serde(skip_serializing_if = "Option::is_none")]
331    pub r#use: Option<Vec<Use>>,
332
333    /// Declares the capabilities that are made available to the parent component or to the
334    /// framework. It is valid to `expose` from `self` or from a child component.
335    ///
336    /// # Capability fields
337    ///
338    /// This supports the following capability keys. Exactly one of these must be set:
339    ///
340    /// - `service`: (_optional `string or array of strings`_)
341    /// - `protocol`: (_optional `string or array of strings`_)
342    /// - `directory`: (_optional `string`_)
343    /// - `runner`: (_optional `string`_)
344    /// - `resolver`: (_optional `string`_)
345    /// - `dictionary`: (_optional `string`_)
346    /// - `config`: (_optional `string`_)
347    ///
348    /// # Additional fields
349    ///
350    /// This supports the following additional fields:
351    #[reference_doc(recurse)]
352    #[serde(skip_serializing_if = "Option::is_none")]
353    pub expose: Option<Vec<Expose>>,
354
355    /// Declares the capabilities that are made available to a [child component][doc-children]
356    /// instance or a [child collection][doc-collections].
357    ///
358    /// # Capability fields
359    ///
360    /// This supports the following capability keys. Exactly one of these must be set:
361    ///
362    /// - `protocol`: (_optional `string or array of strings`_)
363    /// - `service`: (_optional `string or array of strings`_)
364    /// - `directory`: (_optional `string`_)
365    /// - `storage`: (_optional `string`_)
366    /// - `runner`: (_optional `string`_)
367    /// - `resolver`: (_optional `string`_)
368    /// - `event_stream`: (_optional `string or array of strings`_)
369    /// - `dictionary`: (_optional `string`_)
370    /// - `config`: (_optional `string`_)
371    ///
372    /// # Additional fields
373    ///
374    /// This supports the following additional fields:
375    #[reference_doc(recurse)]
376    #[serde(skip_serializing_if = "Option::is_none")]
377    pub offer: Option<Vec<Offer>>,
378
379    /// Contains metadata that components may interpret for their own purposes. The component
380    /// framework enforces no schema for this section, but third parties may expect their facets to
381    /// adhere to a particular schema.
382    #[serde(skip_serializing_if = "Option::is_none")]
383    pub facets: Option<IndexMap<String, Value>>,
384
385    /// The configuration schema as defined by a component. Each key represents a single field
386    /// in the schema.
387    ///
388    /// Configuration fields are JSON objects and must define a `type` which can be one of the
389    /// following strings:
390    /// `bool`, `uint8`, `int8`, `uint16`, `int16`, `uint32`, `int32`, `uint64`, `int64`,
391    /// `string`, `vector`
392    ///
393    /// Example:
394    ///
395    /// ```json5
396    /// config: {
397    ///     debug_mode: {
398    ///         type: "bool"
399    ///     },
400    /// }
401    /// ```
402    ///
403    /// Fields are resolved from a component's package by default. To be able to change the values
404    /// at runtime a `mutability` specifier is required.
405    ///
406    /// Example:
407    ///
408    /// ```json5
409    /// config: {
410    ///     verbose: {
411    ///         type: "bool",
412    ///         mutability: [ "parent" ],
413    ///     },
414    /// },
415    /// ```
416    ///
417    /// Currently `"parent"` is the only mutability specifier supported.
418    ///
419    /// Strings must define the `max_size` property as a non-zero integer.
420    ///
421    /// Example:
422    ///
423    /// ```json5
424    /// config: {
425    ///     verbosity: {
426    ///         type: "string",
427    ///         max_size: 20,
428    ///     }
429    /// }
430    /// ```
431    ///
432    /// Vectors must set the `max_count` property as a non-zero integer. Vectors must also set the
433    /// `element` property as a JSON object which describes the element being contained in the
434    /// vector. Vectors can contain booleans, integers, and strings but cannot contain other
435    /// vectors.
436    ///
437    /// Example:
438    ///
439    /// ```json5
440    /// config: {
441    ///     tags: {
442    ///         type: "vector",
443    ///         max_count: 20,
444    ///         element: {
445    ///             type: "string",
446    ///             max_size: 50,
447    ///         }
448    ///     }
449    /// }
450    /// ```
451    #[reference_doc(json_type = "object")]
452    #[serde(skip_serializing_if = "Option::is_none")]
453    // NB: Unlike other maps the order of these fields matters for the ABI of generated config
454    // libraries. Rather than insertion order, we explicitly sort the fields here to dissuade
455    // developers from taking a dependency on the source ordering in their manifest. In the future
456    // this will hopefully make it easier to pursue layout size optimizations.
457    pub config: Option<BTreeMap<ConfigKey, ConfigValueType>>,
458}
459
460fn merge_from_context_capability_field<T: ContextCapabilityClause>(
461    us: &mut Option<Vec<T>>,
462    other: &mut Option<Vec<T>>,
463) -> Result<(), Error> {
464    // Empty entries are an error, and merging removes empty entries so we first need to check
465    // for them.
466    for entry in us.iter().flatten().chain(other.iter().flatten()) {
467        if entry.names().is_empty() {
468            return Err(Error::Validate {
469                // TODO change error type
470                err: format!("{}: Missing type name: {:#?}", entry.decl_type(), entry),
471                filename: None,
472            });
473        }
474    }
475
476    if let Some(all_ours) = us.as_mut() {
477        if let Some(all_theirs) = other.take() {
478            for mut theirs in all_theirs {
479                for ours in &mut *all_ours {
480                    compute_diff_context(ours, &mut theirs);
481                }
482                all_ours.push(theirs);
483            }
484        }
485        // Post-filter step: remove empty entries.
486        all_ours.retain(|ours| !ours.names().is_empty())
487    } else if let Some(theirs) = other.take() {
488        us.replace(theirs);
489    }
490    Ok(())
491}
492
493/// Subtracts the capabilities in `ours` from `theirs` if the declarations match in their type and
494/// other fields, resulting in the removal of duplicates between `ours` and `theirs`. Stores the
495/// result in `theirs`.
496///
497/// Inexact matches on `availability` are allowed if there is a partial order between them. The
498/// stronger availability is chosen.
499fn compute_diff_context<T: ContextCapabilityClause>(ours: &mut T, theirs: &mut T) {
500    let our_spanned = ours.names();
501    let their_spanned = theirs.names();
502
503    if our_spanned.is_empty() || their_spanned.is_empty() {
504        return;
505    }
506
507    if ours.capability_type(None).unwrap() != theirs.capability_type(None).unwrap() {
508        return;
509    }
510
511    let mut ours_check = ours.clone();
512    let mut theirs_check = theirs.clone();
513
514    ours_check.set_names(Vec::new());
515    theirs_check.set_names(Vec::new());
516    ours_check.set_availability(None);
517    theirs_check.set_availability(None);
518
519    if ours_check != theirs_check {
520        return;
521    }
522
523    let our_avail = ours.availability().map(|a| a.value).unwrap_or_default();
524    let their_avail = theirs.availability().map(|a| a.value).unwrap_or_default();
525
526    let Some(avail_cmp) = our_avail.partial_cmp(&their_avail) else {
527        return;
528    };
529
530    let our_raw_set: HashSet<&Name> = our_spanned.iter().map(|s| &s.value).collect();
531
532    let mut remove_from_ours_raw = HashSet::new();
533    let mut remove_from_theirs_raw = HashSet::new();
534
535    for item in &their_spanned {
536        let name = &item.value;
537        if !our_raw_set.contains(name) {
538            continue;
539        }
540
541        match avail_cmp {
542            cmp::Ordering::Less => {
543                remove_from_ours_raw.insert(name.clone());
544            }
545            cmp::Ordering::Greater => {
546                remove_from_theirs_raw.insert(name.clone());
547            }
548            cmp::Ordering::Equal => {
549                remove_from_theirs_raw.insert(name.clone());
550            }
551        }
552    }
553
554    if !remove_from_ours_raw.is_empty() {
555        let new_ours =
556            our_spanned.into_iter().filter(|s| !remove_from_ours_raw.contains(&s.value)).collect();
557        ours.set_names(new_ours);
558    }
559
560    if !remove_from_theirs_raw.is_empty() {
561        let new_theirs = their_spanned
562            .into_iter()
563            .filter(|s| !remove_from_theirs_raw.contains(&s.value))
564            .collect();
565        theirs.set_names(new_theirs);
566    }
567}
568
569/// Trait that allows us to merge `serde_json::Map`s into `indexmap::IndexMap`s and vice versa.
570trait ValueMap {
571    fn get_mut(&mut self, key: &str) -> Option<&mut Value>;
572    fn insert(&mut self, key: String, val: Value);
573}
574
575impl ValueMap for Map<String, Value> {
576    fn get_mut(&mut self, key: &str) -> Option<&mut Value> {
577        self.get_mut(key)
578    }
579
580    fn insert(&mut self, key: String, val: Value) {
581        self.insert(key, val);
582    }
583}
584
585impl ValueMap for IndexMap<String, Value> {
586    fn get_mut(&mut self, key: &str) -> Option<&mut Value> {
587        self.get_mut(key)
588    }
589
590    fn insert(&mut self, key: String, val: Value) {
591        self.insert(key, val);
592    }
593}
594
595#[derive(Debug, Default, Serialize, PartialEq)]
596pub struct DocumentContext {
597    #[serde(skip_serializing_if = "Option::is_none")]
598    pub include: Option<Vec<ContextSpanned<String>>>,
599    #[serde(skip_serializing_if = "Option::is_none")]
600    pub program: Option<ContextSpanned<ContextProgram>>,
601    #[serde(skip_serializing_if = "Option::is_none")]
602    pub children: Option<Vec<ContextSpanned<ContextChild>>>,
603    #[serde(skip_serializing_if = "Option::is_none")]
604    pub collections: Option<Vec<ContextSpanned<ContextCollection>>>,
605    #[serde(skip_serializing_if = "Option::is_none")]
606    pub environments: Option<Vec<ContextSpanned<ContextEnvironment>>>,
607    #[serde(skip_serializing_if = "Option::is_none")]
608    pub capabilities: Option<Vec<ContextSpanned<ContextCapability>>>,
609    #[serde(skip_serializing_if = "Option::is_none")]
610    pub r#use: Option<Vec<ContextSpanned<ContextUse>>>,
611    #[serde(skip_serializing_if = "Option::is_none")]
612    pub expose: Option<Vec<ContextSpanned<ContextExpose>>>,
613    #[serde(skip_serializing_if = "Option::is_none")]
614    pub offer: Option<Vec<ContextSpanned<ContextOffer>>>,
615    #[serde(skip_serializing_if = "Option::is_none")]
616    pub facets: Option<IndexMap<String, ContextSpanned<Value>>>,
617    #[serde(skip_serializing_if = "Option::is_none")]
618    pub config: Option<BTreeMap<ConfigKey, ContextSpanned<ConfigValueType>>>,
619}
620
621impl DocumentContext {
622    pub fn merge_from(
623        &mut self,
624        mut other: DocumentContext,
625        include_path: &path::Path,
626    ) -> Result<(), Error> {
627        merge_spanned_vec!(self, other, include);
628        self.merge_program(&mut other, include_path)?;
629        merge_spanned_vec!(self, other, children);
630        merge_spanned_vec!(self, other, collections);
631        self.merge_environment(&mut other)?;
632        merge_from_context_capability_field(&mut self.capabilities, &mut other.capabilities)?;
633        merge_from_context_capability_field(&mut self.r#use, &mut other.r#use)?;
634        merge_from_context_capability_field(&mut self.expose, &mut other.expose)?;
635        merge_from_context_capability_field(&mut self.offer, &mut other.offer)?;
636        self.merge_facets(&mut other, include_path)?;
637        self.merge_config(&mut other)?;
638        Ok(())
639    }
640
641    pub fn canonicalize(&mut self) {
642        if let Some(children) = &mut self.children {
643            children.sort_by(|a, b| a.value.name.cmp(&b.value.name));
644        }
645        if let Some(collections) = &mut self.collections {
646            collections.sort_by(|a, b| a.value.name.cmp(&b.value.name));
647        }
648        if let Some(environments) = &mut self.environments {
649            environments.sort_by(|a, b| a.value.name.cmp(&b.value.name));
650        }
651        if let Some(capabilities) = &mut self.capabilities {
652            capabilities.canonicalize_context();
653        }
654        if let Some(offers) = &mut self.offer {
655            offers.canonicalize_context();
656        }
657        if let Some(expose) = &mut self.expose {
658            expose.canonicalize_context();
659        }
660        if let Some(r#use) = &mut self.r#use {
661            r#use.canonicalize_context();
662        }
663    }
664
665    pub fn all_storage_names(&self) -> Vec<&BorrowedName> {
666        if let Some(capabilities) = self.capabilities.as_ref() {
667            capabilities
668                .iter()
669                .filter_map(|c| c.value.storage.as_ref().map(|n| n.value.as_ref()))
670                .collect()
671        } else {
672            vec![]
673        }
674    }
675
676    pub fn all_storage_with_sources<'a>(&'a self) -> HashMap<Name, &'a CapabilityFromRef> {
677        if let Some(capabilities) = self.capabilities.as_ref() {
678            capabilities
679                .iter()
680                .filter_map(|cap_wrapper| {
681                    let c = &cap_wrapper.value;
682
683                    let storage_span_opt = c.storage.as_ref();
684                    let source_span_opt = c.from.as_ref();
685
686                    match (storage_span_opt, source_span_opt) {
687                        (Some(s_span), Some(f_span)) => {
688                            let name_ref: Name = s_span.value.clone();
689                            let source_ref: &CapabilityFromRef = &f_span.value;
690
691                            Some((name_ref, source_ref))
692                        }
693                        _ => None,
694                    }
695                })
696                .collect()
697        } else {
698            HashMap::new()
699        }
700    }
701
702    pub fn all_capability_names(&self) -> HashSet<Name> {
703        self.capabilities
704            .as_ref()
705            .map(|c| {
706                c.iter()
707                    .flat_map(|capability_wrapper| capability_wrapper.value.names())
708                    .map(|spanned_name| spanned_name.value)
709                    .collect()
710            })
711            .unwrap_or_default()
712    }
713
714    pub fn all_collection_names(&self) -> Vec<&BorrowedName> {
715        if let Some(collections) = self.collections.as_ref() {
716            collections.iter().map(|c| c.value.name.value.as_ref()).collect()
717        } else {
718            vec![]
719        }
720    }
721
722    pub fn all_config_names(&self) -> Vec<&BorrowedName> {
723        self.capabilities
724            .as_ref()
725            .map(|caps| {
726                caps.iter()
727                    .filter_map(|cap_wrapper| {
728                        let cap = &cap_wrapper.value;
729
730                        cap.config.as_ref().map(|spanned_key| spanned_key.value.as_ref())
731                    })
732                    .collect()
733            })
734            .unwrap_or_else(|| vec![])
735    }
736
737    pub fn all_children_names(&self) -> Vec<&BorrowedName> {
738        self.children
739            .as_ref()
740            .map(|children| children.iter().map(|c| c.value.name.value.as_ref()).collect())
741            .unwrap_or_default()
742    }
743
744    pub fn all_dictionaries<'a>(&'a self) -> HashMap<Name, &'a ContextCapability> {
745        if let Some(capabilities) = self.capabilities.as_ref() {
746            capabilities
747                .iter()
748                .filter_map(|cap_wrapper| {
749                    let cap = &cap_wrapper.value;
750                    let dict_span_opt = cap.dictionary.as_ref();
751
752                    dict_span_opt.and_then(|dict_span| {
753                        let name_value = &dict_span.value;
754                        let name: Name = name_value.clone();
755                        Some((name, cap))
756                    })
757                })
758                .collect()
759        } else {
760            HashMap::new()
761        }
762    }
763
764    pub fn all_dictionary_names(&self) -> Vec<&BorrowedName> {
765        if let Some(capabilities) = self.capabilities.as_ref() {
766            capabilities
767                .iter()
768                .filter_map(|c| c.value.dictionary.as_ref().map(|d| d.value.as_ref()))
769                .collect()
770        } else {
771            vec![]
772        }
773    }
774
775    pub fn all_environment_names(&self) -> Vec<&BorrowedName> {
776        self.environments
777            .as_ref()
778            .map(|c| c.iter().map(|s| s.value.name.value.as_ref()).collect())
779            .unwrap_or_else(|| vec![])
780    }
781
782    pub fn all_runner_names(&self) -> Vec<&BorrowedName> {
783        self.capabilities
784            .as_ref()
785            .map(|caps| {
786                caps.iter()
787                    .filter_map(|cap_wrapper| {
788                        let cap = &cap_wrapper.value;
789
790                        cap.runner.as_ref().map(|spanned_key| spanned_key.value.as_ref())
791                    })
792                    .collect()
793            })
794            .unwrap_or_else(|| vec![])
795    }
796
797    pub fn all_resolver_names(&self) -> Vec<&BorrowedName> {
798        self.capabilities
799            .as_ref()
800            .map(|caps| {
801                caps.iter()
802                    .filter_map(|cap_wrapper| {
803                        let cap = &cap_wrapper.value;
804
805                        cap.resolver.as_ref().map(|spanned_key| spanned_key.value.as_ref())
806                    })
807                    .collect()
808            })
809            //.map(|c| c.iter().filter_map(|c| c.resolver.as_ref().map(Name::as_ref)).collect())
810            .unwrap_or_else(|| vec![])
811    }
812
813    fn merge_program(
814        &mut self,
815        other: &mut DocumentContext,
816        include_path: &path::Path,
817    ) -> Result<(), Error> {
818        if other.program.is_none() {
819            return Ok(());
820        }
821        if self.program.is_none() {
822            self.program = other.program.clone();
823            return Ok(());
824        }
825
826        let my_program = &mut self.program.as_mut().unwrap().value;
827        let other_wrapper = other.program.as_mut().unwrap();
828
829        let other_origin = other_wrapper.origin.clone();
830        let other_program_val = &mut other_wrapper.value;
831
832        if let Some(other_runner) = other_program_val.runner.take() {
833            if let Some(my_runner) = my_program.runner.as_ref() {
834                if my_runner.value != other_runner.value {
835                    return Err(Error::merge(
836                        format!(
837                            "Manifest include had a conflicting `program.runner`: parent='{}', include='{}'",
838                            my_runner.value, other_runner.value
839                        ),
840                        Some(other_runner.origin),
841                    ));
842                }
843            } else {
844                my_program.runner = Some(other_runner);
845            }
846        }
847
848        Self::merge_maps_unified(
849            &mut my_program.info,
850            &other_program_val.info,
851            "program",
852            include_path,
853            Some(&other_origin),
854            Some(&vec!["environ", "features"]),
855        )
856    }
857
858    fn merge_environment(&mut self, other: &mut DocumentContext) -> Result<(), Error> {
859        if other.environments.is_none() {
860            return Ok(());
861        }
862        if self.environments.is_none() {
863            self.environments = Some(vec![]);
864        }
865
866        let merged_results = {
867            let my_environments = self.environments.as_mut().unwrap();
868            let other_environments = other.environments.as_mut().unwrap();
869
870            my_environments.sort_by(|x, y| x.value.name.value.cmp(&y.value.name.value));
871            other_environments.sort_by(|x, y| x.value.name.value.cmp(&y.value.name.value));
872
873            let all_environments =
874                my_environments.drain(..).merge_by(other_environments.drain(..), |x, y| {
875                    x.value.name.value <= y.value.name.value
876                });
877
878            let groups = all_environments.chunk_by(|e| e.value.name.value.clone());
879
880            let mut results = vec![];
881            for (_name_value, group) in &groups {
882                let mut group_iter = group.into_iter();
883                let first_wrapper = group_iter.next().expect("chunk cannot be empty");
884                let first_origin = first_wrapper.origin.clone();
885                let mut merged_inner = first_wrapper.value;
886
887                for subsequent in group_iter {
888                    merged_inner.merge_from(subsequent.value)?;
889                }
890
891                results.push(ContextSpanned { value: merged_inner, origin: first_origin });
892            }
893            results
894        };
895
896        self.environments = Some(merged_results);
897        Ok(())
898    }
899
900    fn merge_facets(
901        &mut self,
902        other: &mut DocumentContext,
903        include_path: &path::Path,
904    ) -> Result<(), Error> {
905        if let None = other.facets {
906            return Ok(());
907        }
908        if let None = self.facets {
909            self.facets = Some(Default::default());
910        }
911        let other_facets = other.facets.as_ref().unwrap();
912
913        for (key, include_spanned) in other_facets {
914            let entry_origin = Some(&include_spanned.origin);
915            let my_facets = self.facets.as_mut().unwrap();
916
917            if !my_facets.contains_key(key) {
918                my_facets.insert(key.clone(), include_spanned.clone());
919            } else {
920                let self_spanned = my_facets.get_mut(key).unwrap();
921                match (&mut self_spanned.value, &include_spanned.value) {
922                    (
923                        serde_json::Value::Object(self_obj),
924                        serde_json::Value::Object(include_obj),
925                    ) => {
926                        Self::merge_maps_unified(
927                            self_obj,
928                            include_obj,
929                            &format!("facets.{}", key),
930                            include_path,
931                            entry_origin,
932                            None,
933                        )?;
934                    }
935                    (v1, v2) => {
936                        if v1 != v2 {
937                            return Err(Error::merge(
938                                format!(
939                                    "Manifest include '{}' had a conflicting value for field \"facets.{}\"",
940                                    include_path.display(),
941                                    key
942                                ),
943                                entry_origin.cloned(),
944                            ));
945                        }
946                    }
947                }
948            }
949        }
950        Ok(())
951    }
952
953    fn merge_config(&mut self, other: &mut DocumentContext) -> Result<(), Error> {
954        if other.config.is_none() {
955            return Ok(());
956        }
957        if self.config.is_none() {
958            self.config = Some(BTreeMap::new());
959        }
960
961        let my_config = self.config.as_mut().unwrap();
962        let other_config = other.config.as_ref().unwrap();
963
964        for (key, other_spanned) in other_config {
965            if let Some(my_spanned) = my_config.get(key) {
966                if my_spanned.value != other_spanned.value {
967                    return Err(Error::merge(
968                        format!("Conflicting configuration key found: '{}'", key),
969                        Some(other_spanned.origin.clone()),
970                    ));
971                }
972            } else {
973                my_config.insert(key.clone(), other_spanned.clone());
974            }
975        }
976        Ok(())
977    }
978
979    fn merge_maps_unified<'s, Source, Dest>(
980        self_map: &mut Dest,
981        include_map: Source,
982        outer_key: &str,
983        include_path: &path::Path,
984        origin: Option<&Arc<path::Path>>,
985        allow_array_concatenation_keys: Option<&Vec<&str>>,
986    ) -> Result<(), Error>
987    where
988        Source: IntoIterator<Item = (&'s String, &'s serde_json::Value)>,
989        Dest: ValueMap,
990    {
991        for (key, include_val) in include_map {
992            match self_map.get_mut(key) {
993                None => {
994                    self_map.insert(key.clone(), include_val.clone());
995                }
996                Some(self_val) => match (self_val, include_val) {
997                    (serde_json::Value::Object(s_inner), serde_json::Value::Object(i_inner)) => {
998                        let combined_key = format!("{}.{}", outer_key, key);
999                        Self::merge_maps_unified(
1000                            s_inner,
1001                            i_inner,
1002                            &combined_key,
1003                            include_path,
1004                            origin,
1005                            allow_array_concatenation_keys,
1006                        )?;
1007                    }
1008                    (serde_json::Value::Array(s_arr), serde_json::Value::Array(i_arr)) => {
1009                        let is_allowed = allow_array_concatenation_keys
1010                            .map_or(true, |keys| keys.contains(&key.as_str()));
1011
1012                        if is_allowed {
1013                            s_arr.extend(i_arr.clone());
1014                        } else if s_arr != i_arr {
1015                            return Err(Error::merge(
1016                                format!(
1017                                    "Conflicting array values for field \"{}.{}\"",
1018                                    outer_key, key
1019                                ),
1020                                origin.cloned(),
1021                            ));
1022                        }
1023                    }
1024                    (v1, v2) if v1 == v2 => {}
1025                    _ => {
1026                        return Err(Error::merge(
1027                            format!(
1028                                "Manifest include '{}' had a conflicting value for field \"{}.{}\"",
1029                                include_path.display(),
1030                                outer_key,
1031                                key
1032                            ),
1033                            origin.cloned(),
1034                        ));
1035                    }
1036                },
1037            }
1038        }
1039        Ok(())
1040    }
1041
1042    pub fn includes(&self) -> Vec<String> {
1043        self.include
1044            .as_ref()
1045            .map(|includes| includes.iter().map(|s| s.value.clone()).collect())
1046            .unwrap_or_default()
1047    }
1048}
1049
1050pub fn parse_and_hydrate(
1051    file_arc: Arc<std::path::Path>,
1052    buffer: &String,
1053) -> Result<DocumentContext, Error> {
1054    let parsed_doc: Document = serde_json5::from_str(buffer).map_err(|e| {
1055        let serde_json5::Error::Message { location, msg } = e;
1056        let location = location.map(|l| Location { line: l.line, column: l.column });
1057        Error::parse(msg, location, Some(&file_arc.clone()))
1058    })?;
1059
1060    let include = parsed_doc.include.map(|raw_includes| {
1061        raw_includes
1062            .into_iter()
1063            .map(|path| hydrate_simple(path, &file_arc))
1064            .collect::<Vec<ContextSpanned<String>>>()
1065    });
1066
1067    let facets = parsed_doc.facets.map(|raw_facets| {
1068        raw_facets
1069            .into_iter()
1070            .map(|(key, val)| (key, hydrate_simple(val, &file_arc)))
1071            .collect::<IndexMap<String, ContextSpanned<serde_json::Value>>>()
1072    });
1073
1074    let config = parsed_doc.config.map(|raw_config| {
1075        raw_config
1076            .into_iter()
1077            .map(|(key, val)| (key, hydrate_simple(val, &file_arc)))
1078            .collect::<BTreeMap<ConfigKey, ContextSpanned<ConfigValueType>>>()
1079    });
1080
1081    Ok(DocumentContext {
1082        include,
1083        program: hydrate_opt(parsed_doc.program, &file_arc)?,
1084        children: hydrate_list(parsed_doc.children, &file_arc)?,
1085        collections: hydrate_list(parsed_doc.collections, &file_arc)?,
1086        environments: hydrate_list(parsed_doc.environments, &file_arc)?,
1087        capabilities: hydrate_list(parsed_doc.capabilities, &file_arc)?,
1088        r#use: hydrate_list(parsed_doc.r#use, &file_arc)?,
1089        expose: hydrate_list(parsed_doc.expose, &file_arc)?,
1090        offer: hydrate_list(parsed_doc.offer, &file_arc)?,
1091        facets,
1092        config,
1093    })
1094}
1095
1096#[cfg(test)]
1097mod tests {
1098    use super::*;
1099    use crate::OneOrMany;
1100    use difference::Changeset;
1101    use serde_json::{json, to_string_pretty, to_value};
1102    use std::path;
1103    use std::path::Path;
1104    use test_case::test_case;
1105
1106    fn document_context(contents: &str) -> DocumentContext {
1107        let file_arc = Arc::from(std::path::Path::new("test.cml"));
1108        parse_and_hydrate(file_arc, &contents.to_string()).unwrap()
1109    }
1110
1111    macro_rules! assert_json_eq {
1112        ($a:expr, $e:expr) => {{
1113            if $a != $e {
1114                let expected = to_string_pretty(&$e).unwrap();
1115                let actual = to_string_pretty(&$a).unwrap();
1116                assert_eq!(
1117                    $a,
1118                    $e,
1119                    "JSON actual != expected. Diffs:\n\n{}",
1120                    Changeset::new(&actual, &expected, "\n")
1121                );
1122            }
1123        }};
1124    }
1125
1126    #[test]
1127    fn test_includes() {
1128        let buffer = r##"{}"##;
1129        let empty_document = document_context(buffer);
1130        assert_eq!(empty_document.includes(), Vec::<String>::new());
1131
1132        let buffer = r##"{"include": []}"##;
1133        let empty_include = document_context(buffer);
1134        assert_eq!(empty_include.includes(), Vec::<String>::new());
1135
1136        let buffer = r##"{ "include": [ "foo.cml", "bar.cml" ]}"##;
1137        let include_doc = document_context(buffer);
1138
1139        assert_eq!(include_doc.includes(), vec!["foo.cml", "bar.cml"]);
1140    }
1141
1142    #[test]
1143    fn test_merge_same_section() {
1144        let mut some = document_context(r##"{ "use": [{ "protocol": "foo" }] }"##);
1145        let other = document_context(r##"{ "use": [{ "protocol": "bar" }] }"##);
1146        some.merge_from(other, &Path::new("some/path")).unwrap();
1147        let uses = some.r#use.as_ref().unwrap();
1148        assert_eq!(uses.len(), 2);
1149        let get_protocol = |u: &ContextSpanned<ContextUse>| -> String {
1150            let proto_wrapper = u.value.protocol.as_ref().expect("Missing protocol");
1151
1152            match &proto_wrapper.value {
1153                OneOrMany::One(name) => name.to_string(),
1154                OneOrMany::Many(_) => panic!("Expected single protocol, found list"),
1155            }
1156        };
1157
1158        assert_eq!(get_protocol(&uses[0]), "foo");
1159        assert_eq!(get_protocol(&uses[1]), "bar");
1160    }
1161
1162    #[test]
1163    fn test_merge_upgraded_availability() {
1164        let mut some =
1165            document_context(r##"{ "use": [{ "protocol": "foo", "availability": "optional" }] }"##);
1166        let other1 = document_context(r##"{ "use": [{ "protocol": "foo" }] }"##);
1167        let other2 = document_context(
1168            r##"{ "use": [{ "protocol": "foo", "availability": "transitional" }] }"##,
1169        );
1170        let other3 = document_context(
1171            r##"{ "use": [{ "protocol": "foo", "availability": "same_as_target" }] }"##,
1172        );
1173        some.merge_from(other1, &Path::new("some/path")).unwrap();
1174        some.merge_from(other2, &Path::new("some/path")).unwrap();
1175        some.merge_from(other3, &Path::new("some/path")).unwrap();
1176
1177        let uses = some.r#use.as_ref().unwrap();
1178        assert_eq!(uses.len(), 2);
1179        assert_eq!(
1180            uses[0].protocol().as_ref().unwrap().value,
1181            OneOrMany::One("foo".parse::<Name>().unwrap().as_ref())
1182        );
1183        assert!(uses[0].availability().is_none());
1184        assert_eq!(
1185            uses[1].protocol().as_ref().unwrap().value,
1186            OneOrMany::One("foo".parse::<Name>().unwrap().as_ref())
1187        );
1188        assert_eq!(uses[1].availability().as_ref().unwrap().value, Availability::SameAsTarget,);
1189    }
1190
1191    #[test]
1192    fn test_merge_different_sections() {
1193        let mut some = document_context(r##"{ "use": [{ "protocol": "foo" }] }"##);
1194        let other = document_context(r##"{ "expose": [{ "protocol": "bar", "from": "self" }] }"##);
1195        some.merge_from(other, &Path::new("some/path")).unwrap();
1196        let uses = some.r#use.as_ref().unwrap();
1197        let exposes = some.expose.as_ref().unwrap();
1198        assert_eq!(uses.len(), 1);
1199        assert_eq!(exposes.len(), 1);
1200        assert_eq!(
1201            uses[0].protocol().as_ref().unwrap().value,
1202            OneOrMany::One("foo".parse::<Name>().unwrap().as_ref())
1203        );
1204        assert_eq!(
1205            exposes[0].protocol().as_ref().unwrap().value,
1206            OneOrMany::One("bar".parse::<Name>().unwrap().as_ref())
1207        );
1208    }
1209
1210    #[test]
1211    fn test_merge_environments() {
1212        let mut some = document_context(
1213            r##"
1214        { "environments": [
1215            {
1216                "name": "one",
1217                "extends": "realm"
1218            },
1219            {
1220                "name": "two",
1221                "extends": "none",
1222                "runners": [
1223                    {
1224                        "runner": "r1",
1225                        "from": "#c1"
1226                    },
1227                    {
1228                        "runner": "r2",
1229                        "from": "#c2"
1230                    }
1231                ],
1232                "resolvers": [
1233                    {
1234                        "resolver": "res1",
1235                        "from": "#c1",
1236                        "scheme": "foo"
1237                    }
1238                ],
1239                "debug": [
1240                    {
1241                        "protocol": "baz",
1242                        "from": "#c2"
1243                    }
1244                ]
1245            }
1246        ]}"##,
1247        );
1248        let other = document_context(
1249            r##"
1250        { "environments": [
1251            {
1252                "name": "two",
1253                "__stop_timeout_ms": 100,
1254                "runners": [
1255                    {
1256                        "runner": "r3",
1257                        "from": "#c3"
1258                    }
1259                ],
1260                "resolvers": [
1261                    {
1262                        "resolver": "res2",
1263                        "from": "#c1",
1264                        "scheme": "bar"
1265                    }
1266                ],
1267                "debug": [
1268                    {
1269                        "protocol": "faz",
1270                        "from": "#c2"
1271                    }
1272                ]
1273            },
1274            {
1275                "name": "three",
1276                "__stop_timeout_ms": 1000
1277            }
1278        ]}"##,
1279        );
1280        some.merge_from(other, &Path::new("some/path")).unwrap();
1281        assert_eq!(
1282            to_value(some).unwrap(),
1283            json!({"environments": [
1284                {
1285                    "name": "one",
1286                    "extends": "realm",
1287                },
1288                {
1289                    "name": "three",
1290                    "__stop_timeout_ms": 1000,
1291                },
1292                {
1293                    "name": "two",
1294                    "extends": "none",
1295                    "__stop_timeout_ms": 100,
1296                    "runners": [
1297                        {
1298                            "runner": "r1",
1299                            "from": "#c1",
1300                        },
1301                        {
1302                            "runner": "r2",
1303                            "from": "#c2",
1304                        },
1305                        {
1306                            "runner": "r3",
1307                            "from": "#c3",
1308                        },
1309                    ],
1310                    "resolvers": [
1311                        {
1312                            "resolver": "res1",
1313                            "from": "#c1",
1314                            "scheme": "foo",
1315                        },
1316                        {
1317                            "resolver": "res2",
1318                            "from": "#c1",
1319                            "scheme": "bar",
1320                        },
1321                    ],
1322                    "debug": [
1323                        {
1324                            "protocol": "baz",
1325                            "from": "#c2"
1326                        },
1327                        {
1328                            "protocol": "faz",
1329                            "from": "#c2"
1330                        }
1331                    ]
1332                },
1333            ]})
1334        );
1335    }
1336
1337    #[test]
1338    fn test_merge_environments_errors() {
1339        {
1340            let mut some =
1341                document_context(r##"{"environments": [{"name": "one", "extends": "realm"}]}"##);
1342            let other =
1343                document_context(r##"{"environments": [{"name": "one", "extends": "none"}]}"##);
1344            assert!(some.merge_from(other, &Path::new("some/path")).is_err());
1345        }
1346        {
1347            let mut some = document_context(
1348                r##"{"environments": [{"name": "one", "__stop_timeout_ms": 10}]}"##,
1349            );
1350            let other = document_context(
1351                r##"{"environments": [{"name": "one", "__stop_timeout_ms": 20}]}"##,
1352            );
1353            assert!(some.merge_from(other, &Path::new("some/path")).is_err());
1354        }
1355
1356        // It's ok if the values match.
1357        {
1358            let mut some =
1359                document_context(r##"{"environments": [{"name": "one", "extends": "realm"}]}"##);
1360            let other =
1361                document_context(r##"{"environments": [{"name": "one", "extends": "realm"}]}"##);
1362            some.merge_from(other, &Path::new("some/path")).unwrap();
1363            assert_eq!(
1364                to_value(some).unwrap(),
1365                json!({"environments": [{"name": "one", "extends": "realm"}]})
1366            );
1367        }
1368        {
1369            let mut some = document_context(
1370                r##"{"environments": [{"name": "one", "__stop_timeout_ms": 10}]}"##,
1371            );
1372            let other = document_context(
1373                r##"{"environments": [{"name": "one", "__stop_timeout_ms": 10}]}"##,
1374            );
1375            some.merge_from(other, &Path::new("some/path")).unwrap();
1376            assert_eq!(
1377                to_value(some).unwrap(),
1378                json!({"environments": [{"name": "one", "__stop_timeout_ms": 10}]})
1379            );
1380        }
1381    }
1382
1383    #[test]
1384    fn test_merge_from_other_config() {
1385        let mut some = document_context(r##"{}"##);
1386        let other = document_context(r##"{ "config": { "bar": { "type": "bool" } } }"##);
1387
1388        some.merge_from(other, &path::Path::new("some/path")).unwrap();
1389        let expected = document_context(r##"{ "config": { "bar": { "type": "bool" } } }"##);
1390        assert_eq!(some.config, expected.config);
1391    }
1392
1393    #[test]
1394    fn test_merge_from_some_config() {
1395        let mut some = document_context(r##"{ "config": { "bar": { "type": "bool" } } }"##);
1396        let other = document_context(r##"{}"##);
1397
1398        some.merge_from(other, &path::Path::new("some/path")).unwrap();
1399        let expected = document_context(r##"{ "config": { "bar": { "type": "bool" } } }"##);
1400        assert_eq!(some.config, expected.config);
1401    }
1402
1403    #[test]
1404    fn test_merge_from_config() {
1405        let mut some = document_context(r##"{ "config": { "foo": { "type": "bool" } } }"##);
1406        let other = document_context(r##"{ "config": { "bar": { "type": "bool" } } }"##);
1407        some.merge_from(other, &path::Path::new("some/path")).unwrap();
1408
1409        assert_eq!(
1410            to_value(some).unwrap(),
1411            json!({
1412                "config": {
1413                    "foo": { "type": "bool" },
1414                    "bar": { "type": "bool" }
1415                }
1416            }),
1417        );
1418    }
1419
1420    #[test]
1421    fn test_merge_from_config_dedupe_identical_fields() {
1422        let mut some = document_context(r##"{ "config": { "foo": { "type": "bool" } } }"##);
1423        let other = document_context(r##"{ "config": { "foo": { "type": "bool" } } }"##);
1424        some.merge_from(other, &path::Path::new("some/path")).unwrap();
1425
1426        assert_eq!(to_value(some).unwrap(), json!({ "config": { "foo": { "type": "bool" } } }));
1427    }
1428
1429    #[test]
1430    fn test_merge_from_config_conflicting_keys() {
1431        let mut some = document_context(r##"{ "config": { "foo": { "type": "bool" } } }"##);
1432        let other = document_context(r##"{ "config": { "foo": { "type": "uint8" } } }"##);
1433
1434        assert_matches::assert_matches!(
1435            some.merge_from(other, &path::Path::new("some/path")),
1436            Err(Error::Merge { err, .. })
1437                if err == "Conflicting configuration key found: 'foo'"
1438        );
1439    }
1440
1441    #[test]
1442    fn test_canonicalize_context() {
1443        let mut some = document_context(
1444            &json!({
1445                "children": [
1446                    // Will be sorted by name
1447                    { "name": "b_child", "url": "http://foo/b" },
1448                    { "name": "a_child", "url": "http://foo/a" },
1449                ],
1450                "environments": [
1451                    // Will be sorted by name
1452                    { "name": "b_env" },
1453                    { "name": "a_env" },
1454                ],
1455                "collections": [
1456                    // Will be sorted by name
1457                    { "name": "b_coll", "durability": "transient" },
1458                    { "name": "a_coll", "durability": "transient" },
1459                ],
1460                // Will have entries sorted by capability type, then
1461                // by capability name (using the first entry in Many cases).
1462                "capabilities": [
1463                    // Will be merged with "bar"
1464                    { "protocol": ["foo"] },
1465                    { "protocol": "bar" },
1466                    // Will not be merged, but will be sorted before "bar"
1467                    { "protocol": "arg", "path": "/arg" },
1468                    // Will have list of names sorted
1469                    { "service": ["b", "a"] },
1470                    // Will have list of names sorted
1471                    { "event_stream": ["b", "a"] },
1472                    { "runner": "myrunner" },
1473                    // The following two will *not* be merged, because they have a `path`.
1474                    { "runner": "mypathrunner1", "path": "/foo" },
1475                    { "runner": "mypathrunner2", "path": "/foo" },
1476                ],
1477                // Same rules as for "capabilities".
1478                "offer": [
1479                    // Will be sorted after "bar"
1480                    { "protocol": "baz", "from": "#a_child", "to": "#c_child"  },
1481                    // The following two entries will be merged
1482                    { "protocol": ["foo"], "from": "#a_child", "to": "#b_child"  },
1483                    { "protocol": "bar", "from": "#a_child", "to": "#b_child"  },
1484                    // Will have list of names sorted
1485                    { "service": ["b", "a"], "from": "#a_child", "to": "#b_child"  },
1486                    // Will have list of names sorted
1487                    {
1488                        "event_stream": ["b", "a"],
1489                        "from": "#a_child",
1490                        "to": "#b_child",
1491                        "scope": ["#b", "#c", "#a"]  // Also gets sorted
1492                    },
1493                    { "runner": [ "myrunner", "a" ], "from": "#a_child", "to": "#b_child"  },
1494                    { "runner": [ "b" ], "from": "#a_child", "to": "#b_child"  },
1495                    { "directory": [ "b" ], "from": "#a_child", "to": "#b_child"  },
1496                ],
1497                "expose": [
1498                    { "protocol": ["foo"], "from": "#a_child" },
1499                    { "protocol": "bar", "from": "#a_child" },  // Will appear before protocol: foo
1500                    // Will have list of names sorted
1501                    { "service": ["b", "a"], "from": "#a_child" },
1502                    // Will have list of names sorted
1503                    {
1504                        "event_stream": ["b", "a"],
1505                        "from": "#a_child",
1506                        "scope": ["#b", "#c", "#a"]  // Also gets sorted
1507                    },
1508                    { "runner": [ "myrunner", "a" ], "from": "#a_child" },
1509                    { "runner": [ "b" ], "from": "#a_child" },
1510                    { "directory": [ "b" ], "from": "#a_child" },
1511                ],
1512                "use": [
1513                    // Will be sorted after "baz"
1514                    { "protocol": ["zazzle"], "path": "/zazbaz" },
1515                    // These will be merged
1516                    { "protocol": ["foo"] },
1517                    { "protocol": "bar" },
1518                    // Will have list of names sorted
1519                    { "service": ["b", "a"] },
1520                    // Will have list of names sorted
1521                    { "event_stream": ["b", "a"], "scope": ["#b", "#a"] },
1522                ],
1523            })
1524            .to_string(),
1525        );
1526        some.canonicalize();
1527
1528        assert_json_eq!(
1529            some,
1530            document_context(&json!({
1531                "children": [
1532                    { "name": "a_child", "url": "http://foo/a" },
1533                    { "name": "b_child", "url": "http://foo/b" },
1534                ],
1535                "collections": [
1536                    { "name": "a_coll", "durability": "transient" },
1537                    { "name": "b_coll", "durability": "transient" },
1538                ],
1539                "environments": [
1540                    { "name": "a_env" },
1541                    { "name": "b_env" },
1542                ],
1543                "capabilities": [
1544                    { "event_stream": ["a", "b"] },
1545                    { "protocol": "arg", "path": "/arg" },
1546                    { "protocol": ["bar", "foo"] },
1547                    { "runner": "mypathrunner1", "path": "/foo" },
1548                    { "runner": "mypathrunner2", "path": "/foo" },
1549                    { "runner": "myrunner" },
1550                    { "service": ["a", "b"] },
1551                ],
1552                "use": [
1553                    { "event_stream": ["a", "b"], "scope": ["#a", "#b"] },
1554                    { "protocol": ["bar", "foo"] },
1555                    { "protocol": "zazzle", "path": "/zazbaz" },
1556                    { "service": ["a", "b"] },
1557                ],
1558                "offer": [
1559                    { "directory": "b", "from": "#a_child", "to": "#b_child" },
1560                    {
1561                        "event_stream": ["a", "b"],
1562                        "from": "#a_child",
1563                        "to": "#b_child",
1564                        "scope": ["#a", "#b", "#c"],
1565                    },
1566                    { "protocol": ["bar", "foo"], "from": "#a_child", "to": "#b_child" },
1567                    { "protocol": "baz", "from": "#a_child", "to": "#c_child"  },
1568                    { "runner": [ "a", "b", "myrunner" ], "from": "#a_child", "to": "#b_child" },
1569                    { "service": ["a", "b"], "from": "#a_child", "to": "#b_child" },
1570                ],
1571                "expose": [
1572                    { "directory": "b", "from": "#a_child" },
1573                    {
1574                        "event_stream": ["a", "b"],
1575                        "from": "#a_child",
1576                        "scope": ["#a", "#b", "#c"],
1577                    },
1578                    { "protocol": ["bar", "foo"], "from": "#a_child" },
1579                    { "runner": [ "a", "b", "myrunner" ], "from": "#a_child" },
1580                    { "service": ["a", "b"], "from": "#a_child" },
1581                ],
1582            }).to_string())
1583        )
1584    }
1585
1586    #[test]
1587    fn deny_unknown_config_type_fields() {
1588        let contents =
1589            json!({ "config": { "foo": { "type": "bool", "unknown": "should error" } } });
1590        let file_arc = Arc::from(std::path::Path::new("test.cml"));
1591        parse_and_hydrate(file_arc, &contents.to_string())
1592            .expect_err("must reject unknown config field attributes");
1593    }
1594
1595    #[test]
1596    fn deny_unknown_config_nested_type_fields() {
1597        let input = json!({
1598            "config": {
1599                "foo": {
1600                    "type": "vector",
1601                    "max_count": 10,
1602                    "element": {
1603                        "type": "bool",
1604                        "unknown": "should error"
1605                    },
1606
1607                }
1608            }
1609        });
1610
1611        let file_arc = Arc::from(std::path::Path::new("test.cml"));
1612        parse_and_hydrate(file_arc, &input.to_string())
1613            .expect_err("must reject unknown config field attributes");
1614    }
1615
1616    #[test]
1617    fn test_merge_from_program() {
1618        let mut some =
1619            document_context(&json!({ "program": { "binary": "bin/hello_world" } }).to_string());
1620        let other = document_context(&json!({ "program": { "runner": "elf" } }).to_string());
1621        some.merge_from(other, &Path::new("some/path")).unwrap();
1622        let expected = document_context(
1623            &json!({ "program": { "binary": "bin/hello_world", "runner": "elf" } }).to_string(),
1624        );
1625        assert_eq!(some.program, expected.program);
1626    }
1627
1628    #[test]
1629    fn test_merge_from_program_without_runner() {
1630        let mut some = document_context(
1631            &json!({ "program": { "binary": "bin/hello_world", "runner": "elf" } }).to_string(),
1632        );
1633        // https://fxbug.dev/42160240: merging with a document that doesn't have a runner doesn't override the
1634        // runner that we already have assigned.
1635        let other = document_context(&json!({ "program": {} }).to_string());
1636        some.merge_from(other, &Path::new("some/path")).unwrap();
1637        let expected = document_context(
1638            &json!({ "program": { "binary": "bin/hello_world", "runner": "elf" } }).to_string(),
1639        );
1640        assert_eq!(some.program, expected.program);
1641    }
1642
1643    #[test]
1644    fn test_merge_from_program_overlapping_environ() {
1645        // It's ok to merge `program.environ` by concatenating the arrays together.
1646        let mut some = document_context(&json!({ "program": { "environ": ["1"] } }).to_string());
1647        let other = document_context(&json!({ "program": { "environ": ["2"] } }).to_string());
1648        some.merge_from(other, &Path::new("some/path")).unwrap();
1649        let expected =
1650            document_context(&json!({ "program": { "environ": ["1", "2"] } }).to_string());
1651        assert_eq!(some.program, expected.program);
1652    }
1653
1654    #[test]
1655    fn test_merge_from_program_overlapping_runner() {
1656        // It's ok to merge `program.runner = "elf"` with `program.runner = "elf"`.
1657        let mut some = document_context(
1658            &json!({ "program": { "binary": "bin/hello_world", "runner": "elf" } }).to_string(),
1659        );
1660        let other = document_context(&json!({ "program": { "runner": "elf" } }).to_string());
1661        some.merge_from(other, &Path::new("some/path")).unwrap();
1662        let expected = document_context(
1663            &json!({ "program": { "binary": "bin/hello_world", "runner": "elf" } }).to_string(),
1664        );
1665        assert_eq!(some.program, expected.program);
1666    }
1667
1668    #[test]
1669    fn test_merge_from_program_error_runner() {
1670        let mut some = document_context(&json!({ "program": { "runner": "elf" } }).to_string());
1671        let other = document_context(&json!({ "program": { "runner": "fle" } }).to_string());
1672        assert_matches::assert_matches!(
1673            some.merge_from(other, &Path::new("some/path")),
1674            Err(Error::Merge {  err, .. })
1675                if err == format!("Manifest include had a conflicting `program.runner`: parent='elf', include='fle'"));
1676    }
1677
1678    #[test]
1679    fn test_merge_from_program_error_binary() {
1680        let mut some =
1681            document_context(&json!({ "program": { "binary": "bin/hello_world" } }).to_string());
1682        let other =
1683            document_context(&json!({ "program": { "binary": "bin/hola_mundo" } }).to_string());
1684        assert_matches::assert_matches!(
1685            some.merge_from(other, &Path::new("some/path")),
1686            Err(Error::Merge {  err, .. })
1687                if err == format!("Manifest include 'some/path' had a conflicting value for field \"program.binary\""));
1688    }
1689
1690    #[test]
1691    fn test_merge_from_program_error_args() {
1692        let mut some =
1693            document_context(&json!({ "program": { "args": ["a".to_owned()] } }).to_string());
1694        let other =
1695            document_context(&json!({ "program": { "args": ["b".to_owned()] } }).to_string());
1696        assert_matches::assert_matches!(
1697            some.merge_from(other, &Path::new("some/path")),
1698            Err(Error::Merge {  err, .. })
1699                if err == format!("Conflicting array values for field \"program.args\""));
1700    }
1701
1702    #[test_case(
1703        document_context(&json!({ "facets": { "my.key": "my.value" } }).to_string()),
1704        document_context(&json!({ "facets": { "other.key": "other.value" } }).to_string()),
1705        document_context(&json!({ "facets": { "my.key": "my.value",  "other.key": "other.value" } }).to_string())
1706        ; "two separate keys"
1707    )]
1708    #[test_case(
1709        document_context(&json!({ "facets": { "my.key": "my.value" } }).to_string()),
1710        document_context(&json!({ "facets": {} }).to_string()),
1711        document_context(&json!({ "facets": { "my.key": "my.value" } }).to_string())
1712        ; "empty other facet"
1713    )]
1714    #[test_case(
1715        document_context(&json!({ "facets": {} }).to_string()),
1716        document_context(&json!({ "facets": { "other.key": "other.value" } }).to_string()),
1717        document_context(&json!({ "facets": { "other.key": "other.value" } }).to_string())
1718        ; "empty my facet"
1719    )]
1720    #[test_case(
1721        document_context(&json!({ "facets": { "key": { "type": "some_type" } } }).to_string()),
1722        document_context(&json!({ "facets": { "key": { "runner": "some_runner"} } }).to_string()),
1723        document_context(&json!({ "facets": { "key": { "type": "some_type", "runner": "some_runner" } } }).to_string())
1724        ; "nested facet key"
1725    )]
1726    #[test_case(
1727        document_context(&json!({ "facets": { "key": { "type": "some_type", "nested_key": { "type": "new type" }}}}).to_string()),
1728        document_context(&json!({ "facets": { "key": { "nested_key": { "runner": "some_runner" }} } }).to_string()),
1729        document_context(&json!({ "facets": { "key": { "type": "some_type", "nested_key": { "runner": "some_runner", "type": "new type" }}}}).to_string())
1730        ; "double nested facet key"
1731    )]
1732    #[test_case(
1733        document_context(&json!({ "facets": { "key": { "array_key": ["value_1", "value_2"] } } }).to_string()),
1734        document_context(&json!({ "facets": { "key": { "array_key": ["value_3", "value_4"] } } }).to_string()),
1735        document_context(&json!({ "facets": { "key": { "array_key": ["value_1", "value_2", "value_3", "value_4"] } } }).to_string())
1736        ; "merge array values" // failing
1737    )]
1738    fn test_merge_from_facets(
1739        mut my: DocumentContext,
1740        other: DocumentContext,
1741        expected: DocumentContext,
1742    ) {
1743        my.merge_from(other, &Path::new("some/path")).unwrap();
1744        assert_eq!(my.facets, expected.facets);
1745    }
1746
1747    #[test_case(
1748        document_context(&json!({ "facets": { "key": "my.value" }}).to_string()),
1749        document_context(&json!({ "facets": { "key": "other.value" }}).to_string()),
1750        "facets.key"
1751        ; "conflict first level keys" // failing
1752    )]
1753    #[test_case(
1754        document_context(&json!({ "facets": { "key":  {"type": "cts" }}}).to_string()),
1755        document_context(&json!({ "facets": { "key":  {"type": "system" }}}).to_string()),
1756        "facets.key.type"
1757        ; "conflict second level keys"
1758    )]
1759    #[test_case(
1760        document_context(&json!({ "facets": { "key":  {"type": {"key": "value" }}}}).to_string()),
1761        document_context(&json!({ "facets": { "key":  {"type": "system" }}}).to_string()),
1762        "facets.key.type"
1763        ; "incompatible self nested type"
1764    )]
1765    #[test_case(
1766        document_context(&json!({ "facets": { "key":  {"type": "system" }}}).to_string()),
1767        document_context(&json!({ "facets": { "key":  {"type":  {"key": "value" }}}}).to_string()),
1768        "facets.key.type"
1769        ; "incompatible other nested type"
1770    )]
1771    #[test_case(
1772        document_context(&json!({ "facets": { "key":  {"type": {"key": "my.value" }}}}).to_string()),
1773        document_context(&json!({ "facets": { "key":  {"type":  {"key": "some.value" }}}}).to_string()),
1774        "facets.key.type.key"
1775        ; "conflict third level keys"
1776    )]
1777    #[test_case(
1778        document_context(&json!({ "facets": { "key":  {"type": [ "value_1" ]}}}).to_string()),
1779        document_context(&json!({ "facets": { "key":  {"type":  "value_2" }}}).to_string()),
1780        "facets.key.type"
1781        ; "incompatible keys"
1782    )]
1783    fn test_merge_from_facet_error(mut my: DocumentContext, other: DocumentContext, field: &str) {
1784        assert_matches::assert_matches!(
1785            my.merge_from(other, &path::Path::new("some/path")),
1786            Err(Error::Merge {  err, .. })
1787                if err == format!("Manifest include 'some/path' had a conflicting value for field \"{}\"", field)
1788        );
1789    }
1790
1791    #[test_case("protocol")]
1792    #[test_case("service")]
1793    #[test_case("event_stream")]
1794    fn test_merge_from_duplicate_use_array(typename: &str) {
1795        let mut my = document_context(&json!({ "use": [{ typename: "a" }]}).to_string());
1796        let other = document_context(
1797            &json!({ "use": [
1798                { typename: ["a", "b"], "availability": "optional"}
1799            ]})
1800            .to_string(),
1801        );
1802        let result = document_context(
1803            &json!({ "use": [
1804                { typename: "a" },
1805                { typename: "b", "availability": "optional" },
1806            ]})
1807            .to_string(),
1808        );
1809
1810        my.merge_from(other, &path::Path::new("some/path")).unwrap();
1811        assert_eq!(my, result);
1812    }
1813
1814    #[test_case("directory")]
1815    #[test_case("storage")]
1816    fn test_merge_from_duplicate_use_noarray(typename: &str) {
1817        let mut my =
1818            document_context(&json!({ "use": [{ typename: "a", "path": "/a"}]}).to_string());
1819        let other = document_context(
1820            &json!({ "use": [
1821                { typename: "a", "path": "/a", "availability": "optional" },
1822                { typename: "b", "path": "/b", "availability": "optional" },
1823            ]})
1824            .to_string(),
1825        );
1826        let result = document_context(
1827            &json!({ "use": [
1828                { typename: "a", "path": "/a" },
1829                { typename: "b", "path": "/b", "availability": "optional" },
1830            ]})
1831            .to_string(),
1832        );
1833        my.merge_from(other, &path::Path::new("some/path")).unwrap();
1834        assert_eq!(my, result);
1835    }
1836
1837    #[test_case("protocol")]
1838    #[test_case("service")]
1839    #[test_case("event_stream")]
1840    fn test_merge_from_duplicate_capabilities_array(typename: &str) {
1841        let mut my = document_context(&json!({ "capabilities": [{ typename: "a" }]}).to_string());
1842        let other =
1843            document_context(&json!({ "capabilities": [ { typename: ["a", "b"] } ]}).to_string());
1844        let result = document_context(
1845            &json!({ "capabilities": [ { typename: "a" }, { typename: "b" } ]}).to_string(),
1846        );
1847
1848        my.merge_from(other, &path::Path::new("some/path")).unwrap();
1849        assert_eq!(my, result);
1850    }
1851
1852    #[test_case("directory")]
1853    #[test_case("storage")]
1854    #[test_case("runner")]
1855    #[test_case("resolver")]
1856    fn test_merge_from_duplicate_capabilities_noarray(typename: &str) {
1857        let mut my = document_context(
1858            &json!({ "capabilities": [{ typename: "a", "path": "/a"}]}).to_string(),
1859        );
1860        let other = document_context(
1861            &json!({ "capabilities": [
1862                { typename: "a", "path": "/a" },
1863                { typename: "b", "path": "/b" },
1864            ]})
1865            .to_string(),
1866        );
1867        let result = document_context(
1868            &json!({ "capabilities": [
1869                { typename: "a", "path": "/a" },
1870                { typename: "b", "path": "/b" },
1871            ]})
1872            .to_string(),
1873        );
1874        my.merge_from(other, &path::Path::new("some/path")).unwrap();
1875        assert_eq!(my, result);
1876    }
1877
1878    #[test]
1879    fn test_merge_with_empty_names() {
1880        // This document is an error because there is no capability name.
1881        let mut my = document_context(&json!({ "capabilities": [{ "path": "/a"}]}).to_string());
1882
1883        let other = document_context(
1884            &json!({ "capabilities": [
1885                { "directory": "a", "path": "/a" },
1886                { "directory": "b", "path": "/b" },
1887            ]})
1888            .to_string(),
1889        );
1890        my.merge_from(other, &path::Path::new("some/path")).unwrap_err();
1891    }
1892
1893    #[test_case("protocol")]
1894    #[test_case("service")]
1895    #[test_case("event_stream")]
1896    #[test_case("directory")]
1897    #[test_case("storage")]
1898    #[test_case("runner")]
1899    #[test_case("resolver")]
1900    fn test_merge_from_duplicate_offers(typename: &str) {
1901        let mut my = document_context(
1902            &json!({ "offer": [{ typename: "a", "from": "self", "to": "#c" }]}).to_string(),
1903        );
1904        let other = document_context(
1905            &json!({ "offer": [
1906                { typename: ["a", "b"], "from": "self", "to": "#c", "availability": "optional" }
1907            ]})
1908            .to_string(),
1909        );
1910        let result = document_context(
1911            &json!({ "offer": [
1912                { typename: "a", "from": "self", "to": "#c" },
1913                { typename: "b", "from": "self", "to": "#c", "availability": "optional" },
1914            ]})
1915            .to_string(),
1916        );
1917
1918        my.merge_from(other, &path::Path::new("some/path")).unwrap();
1919        assert_eq!(my, result);
1920    }
1921
1922    #[test_case("protocol")]
1923    #[test_case("service")]
1924    #[test_case("event_stream")]
1925    #[test_case("directory")]
1926    #[test_case("runner")]
1927    #[test_case("resolver")]
1928    fn test_merge_from_duplicate_exposes(typename: &str) {
1929        let mut my =
1930            document_context(&json!({ "expose": [{ typename: "a", "from": "self" }]}).to_string());
1931        let other = document_context(
1932            &json!({ "expose": [
1933                { typename: ["a", "b"], "from": "self" }
1934            ]})
1935            .to_string(),
1936        );
1937        let result = document_context(
1938            &json!({ "expose": [
1939                { typename: "a", "from": "self" },
1940                { typename: "b", "from": "self" },
1941            ]})
1942            .to_string(),
1943        );
1944
1945        my.merge_from(other, &path::Path::new("some/path")).unwrap();
1946        assert_eq!(my, result);
1947    }
1948
1949    #[test_case(
1950        document_context(&json!({ "use": [
1951            { "protocol": "a", "availability": "required" },
1952            { "protocol": "b", "availability": "optional" },
1953            { "protocol": "c", "availability": "transitional" },
1954            { "protocol": "d", "availability": "same_as_target" },
1955        ]}).to_string()),
1956        document_context(&json!({ "use": [
1957            { "protocol": ["a"], "availability": "required" },
1958            { "protocol": ["b"], "availability": "optional" },
1959            { "protocol": ["c"], "availability": "transitional" },
1960            { "protocol": ["d"], "availability": "same_as_target" },
1961        ]}).to_string()),
1962        document_context(&json!({ "use": [
1963            { "protocol": "a", "availability": "required" },
1964            { "protocol": "b", "availability": "optional" },
1965            { "protocol": "c", "availability": "transitional" },
1966            { "protocol": "d", "availability": "same_as_target" },
1967        ]}).to_string())
1968        ; "merge both same"
1969    )]
1970    #[test_case(
1971        document_context(&json!({ "use": [
1972            { "protocol": "a", "availability": "optional" },
1973            { "protocol": "b", "availability": "transitional" },
1974            { "protocol": "c", "availability": "transitional" },
1975        ]}).to_string()),
1976        document_context(&json!({ "use": [
1977            { "protocol": ["a", "x"], "availability": "required" },
1978            { "protocol": ["b", "y"], "availability": "optional" },
1979            { "protocol": ["c", "z"], "availability": "required" },
1980        ]}).to_string()),
1981        document_context(&json!({ "use": [
1982            { "protocol": ["a", "x"], "availability": "required" },
1983            { "protocol": ["b", "y"], "availability": "optional" },
1984            { "protocol": ["c", "z"], "availability": "required" },
1985        ]}).to_string())
1986        ; "merge with upgrade"
1987    )]
1988    #[test_case(
1989        document_context(&json!({ "use": [
1990            { "protocol": "a", "availability": "required" },
1991            { "protocol": "b", "availability": "optional" },
1992            { "protocol": "c", "availability": "required" },
1993        ]}).to_string()),
1994        document_context(&json!({ "use": [
1995            { "protocol": ["a", "x"], "availability": "optional" },
1996            { "protocol": ["b", "y"], "availability": "transitional" },
1997            { "protocol": ["c", "z"], "availability": "transitional" },
1998        ]}).to_string()),
1999        document_context(&json!({ "use": [
2000            { "protocol": "a", "availability": "required" },
2001            { "protocol": "b", "availability": "optional" },
2002            { "protocol": "c", "availability": "required" },
2003            { "protocol": "x", "availability": "optional" },
2004            { "protocol": "y", "availability": "transitional" },
2005            { "protocol": "z", "availability": "transitional" },
2006        ]}).to_string())
2007        ; "merge with downgrade"
2008    )]
2009    #[test_case(
2010        document_context(&json!({ "use": [
2011            { "protocol": "a", "availability": "optional" },
2012            { "protocol": "b", "availability": "transitional" },
2013            { "protocol": "c", "availability": "transitional" },
2014        ]}).to_string()),
2015        document_context(&json!({ "use": [
2016            { "protocol": ["a", "x"], "availability": "same_as_target" },
2017            { "protocol": ["b", "y"], "availability": "same_as_target" },
2018            { "protocol": ["c", "z"], "availability": "same_as_target" },
2019        ]}).to_string()),
2020        document_context(&json!({ "use": [
2021            { "protocol": "a", "availability": "optional" },
2022            { "protocol": "b", "availability": "transitional" },
2023            { "protocol": "c", "availability": "transitional" },
2024            { "protocol": ["a", "x"], "availability": "same_as_target" },
2025            { "protocol": ["b", "y"], "availability": "same_as_target" },
2026            { "protocol": ["c", "z"], "availability": "same_as_target" },
2027        ]}).to_string())
2028        ; "merge with no replacement"
2029    )]
2030    #[test_case(
2031        document_context(&json!({ "use": [
2032            { "protocol": ["a", "b", "c"], "availability": "optional" },
2033            { "protocol": "d", "availability": "same_as_target" },
2034            { "protocol": ["e", "f"] },
2035        ]}).to_string()),
2036        document_context(&json!({ "use": [
2037            { "protocol": ["c", "e", "g"] },
2038            { "protocol": ["d", "h"] },
2039            { "protocol": ["f", "i"], "availability": "transitional" },
2040        ]}).to_string()),
2041        document_context(&json!({ "use": [
2042            { "protocol": ["a", "b"], "availability": "optional" },
2043            { "protocol": "d", "availability": "same_as_target" },
2044            { "protocol": ["e", "f"] },
2045            { "protocol": ["c", "g"] },
2046            { "protocol": ["d", "h"] },
2047            { "protocol": "i", "availability": "transitional" },
2048        ]}).to_string())
2049        ; "merge multiple"
2050    )]
2051
2052    fn test_merge_from_duplicate_capability_availability(
2053        mut my: DocumentContext,
2054        other: DocumentContext,
2055        result: DocumentContext,
2056    ) {
2057        my.merge_from(other, &path::Path::new("some/path")).unwrap();
2058        assert_eq!(my, result);
2059    }
2060
2061    #[test_case(
2062        document_context(&json!({ "use": [{ "protocol": ["a", "b"] }]}).to_string()),
2063        document_context(&json!({ "use": [{ "protocol": ["c", "d"] }]}).to_string()),
2064        document_context(&json!({ "use": [
2065            { "protocol": ["a", "b"] }, { "protocol": ["c", "d"] }
2066        ]}).to_string())
2067        ; "merge capabilities with disjoint sets"
2068    )]
2069    #[test_case(
2070        document_context(&json!({ "use": [
2071            { "protocol": ["a"] },
2072            { "protocol": "b" },
2073        ]}).to_string()),
2074        document_context(&json!({ "use": [{ "protocol": ["a", "b"] }]}).to_string()),
2075        document_context(&json!({ "use": [
2076            { "protocol": ["a"] }, { "protocol": "b" },
2077        ]}).to_string())
2078        ; "merge capabilities with equal set"
2079    )]
2080    #[test_case(
2081        document_context(&json!({ "use": [
2082            { "protocol": ["a", "b"] },
2083            { "protocol": "c" },
2084        ]}).to_string()),
2085        document_context(&json!({ "use": [{ "protocol": ["a", "b"] }]}).to_string()),
2086        document_context(&json!({ "use": [
2087            { "protocol": ["a", "b"] }, { "protocol": "c" },
2088        ]}).to_string())
2089        ; "merge capabilities with subset"
2090    )]
2091    #[test_case(
2092        document_context(&json!({ "use": [
2093            { "protocol": ["a", "b"] },
2094        ]}).to_string()),
2095        document_context(&json!({ "use": [{ "protocol": ["a", "b", "c"] }]}).to_string()),
2096        document_context(&json!({ "use": [
2097            { "protocol": ["a", "b"] },
2098            { "protocol": "c" },
2099        ]}).to_string())
2100        ; "merge capabilities with superset"
2101    )]
2102    #[test_case(
2103        document_context(&json!({ "use": [
2104            { "protocol": ["a", "b"] },
2105        ]}).to_string()),
2106        document_context(&json!({ "use": [{ "protocol": ["b", "c", "d"] }]}).to_string()),
2107        document_context(&json!({ "use": [
2108            { "protocol": ["a", "b"] }, { "protocol": ["c", "d"] }
2109        ]}).to_string())
2110        ; "merge capabilities with intersection"
2111    )]
2112    #[test_case(
2113        document_context(&json!({ "use": [{ "protocol": ["a", "b"] }]}).to_string()),
2114        document_context(&json!({ "use": [
2115            { "protocol": ["c", "b", "d"] },
2116            { "protocol": ["e", "d"] },
2117        ]}).to_string()),
2118        document_context(&json!({ "use": [
2119            {"protocol": ["a", "b"] },
2120            {"protocol": ["c", "d"] },
2121            {"protocol": "e" }]}).to_string())
2122        ; "merge capabilities from multiple arrays"
2123    )]
2124    #[test_case(
2125        document_context(&json!({ "use": [{ "protocol": "foo.bar.Baz", "from": "self"}]}).to_string()),
2126        document_context(&json!({ "use": [{ "service": "foo.bar.Baz", "from": "self"}]}).to_string()),
2127        document_context(&json!({ "use": [
2128            {"protocol": "foo.bar.Baz", "from": "self"},
2129            {"service": "foo.bar.Baz", "from": "self"}]}).to_string())
2130        ; "merge capabilities, types don't match"
2131    )]
2132    #[test_case(
2133        document_context(&json!({ "use": [{ "protocol": "foo.bar.Baz", "from": "self"}]}).to_string()),
2134        document_context(&json!({ "use": [{ "protocol": "foo.bar.Baz" }]}).to_string()),
2135        document_context(&json!({ "use": [
2136            {"protocol": "foo.bar.Baz", "from": "self"},
2137            {"protocol": "foo.bar.Baz"}]}).to_string())
2138        ; "merge capabilities, fields don't match"
2139    )]
2140
2141    fn test_merge_from_duplicate_capability(
2142        mut my: DocumentContext,
2143        other: DocumentContext,
2144        result: DocumentContext,
2145    ) {
2146        my.merge_from(other, &path::Path::new("some/path")).unwrap();
2147        assert_eq!(my, result);
2148    }
2149}