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