cm_rust_testing/
lib.rs

1// Copyright 2021 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 anyhow::{Context, Error};
6use assert_matches::assert_matches;
7use cm_rust::{CapabilityTypeName, ComponentDecl, FidlIntoNative};
8use cm_types::{LongName, Name, Path, RelativePath, Url};
9use derivative::Derivative;
10use std::collections::BTreeMap;
11use {fidl_fuchsia_component_decl as fdecl, fidl_fuchsia_data as fdata, fidl_fuchsia_io as fio};
12
13/// Name of the test runner.
14///
15/// Several functions assume the existence of a runner with this name.
16pub const TEST_RUNNER_NAME: &str = "test_runner";
17
18/// Deserialize `object` into a cml::Document and then translate the result
19/// to ComponentDecl.
20pub fn new_decl_from_json(object: serde_json::Value) -> Result<ComponentDecl, Error> {
21    let doc = serde_json::from_value(object).context("failed to deserialize manifest")?;
22    let cm =
23        cml::compile(&doc, cml::CompileOptions::default()).context("failed to compile manifest")?;
24    Ok(cm.fidl_into_native())
25}
26
27/// Builder for constructing a ComponentDecl.
28#[derive(Debug, Clone)]
29pub struct ComponentDeclBuilder {
30    result: ComponentDecl,
31}
32
33impl ComponentDeclBuilder {
34    /// An empty ComponentDeclBuilder, with no program.
35    pub fn new_empty_component() -> Self {
36        ComponentDeclBuilder { result: Default::default() }
37    }
38
39    /// A ComponentDeclBuilder prefilled with a program and using a runner named "test_runner",
40    /// which we assume is offered to us.
41    pub fn new() -> Self {
42        Self::new_empty_component().program_runner(TEST_RUNNER_NAME)
43    }
44
45    /// Add a child element.
46    pub fn child(mut self, decl: impl Into<cm_rust::ChildDecl>) -> Self {
47        self.result.children.push(decl.into());
48        self
49    }
50
51    /// Add a child with default properties.
52    pub fn child_default(self, name: &str) -> Self {
53        self.child(ChildBuilder::new().name(name))
54    }
55
56    // Add a collection element.
57    pub fn collection(mut self, decl: impl Into<cm_rust::CollectionDecl>) -> Self {
58        self.result.collections.push(decl.into());
59        self
60    }
61
62    /// Add a collection with default properties.
63    pub fn collection_default(self, name: &str) -> Self {
64        self.collection(CollectionBuilder::new().name(name))
65    }
66
67    /// Add a "program" clause, using the given runner.
68    pub fn program_runner(self, runner: &str) -> Self {
69        assert!(self.result.program.is_none(), "tried to add program twice");
70        self.program(cm_rust::ProgramDecl {
71            runner: Some(runner.parse().unwrap()),
72            info: fdata::Dictionary { entries: Some(vec![]), ..Default::default() },
73        })
74    }
75
76    pub fn program(mut self, program: cm_rust::ProgramDecl) -> Self {
77        self.result.program = Some(program);
78        self
79    }
80
81    /// Add an offer decl.
82    pub fn offer(mut self, offer: impl Into<cm_rust::OfferDecl>) -> Self {
83        self.result.offers.push(offer.into());
84        self
85    }
86
87    /// Add an expose decl.
88    pub fn expose(mut self, expose: impl Into<cm_rust::ExposeDecl>) -> Self {
89        self.result.exposes.push(expose.into());
90        self
91    }
92
93    /// Add a use decl.
94    pub fn use_(mut self, use_: impl Into<cm_rust::UseDecl>) -> Self {
95        let use_ = use_.into();
96        if let cm_rust::UseDecl::Runner(_) = &use_ {
97            assert!(
98                self.result.program.as_ref().and_then(|p| p.runner.as_ref()).is_none(),
99                "tried to add a use decl for a runner while program.runner is set"
100            );
101        }
102        self.result.uses.push(use_);
103        self
104    }
105
106    // Add a use decl for fuchsia.component.Realm.
107    pub fn use_realm(self) -> Self {
108        self.use_(
109            UseBuilder::protocol()
110                .name("fuchsia.component.Realm")
111                .source(cm_rust::UseSource::Framework),
112        )
113    }
114
115    /// Add a capability declaration.
116    pub fn capability(mut self, capability: impl Into<cm_rust::CapabilityDecl>) -> Self {
117        self.result.capabilities.push(capability.into());
118        self
119    }
120
121    /// Add a default protocol declaration.
122    pub fn protocol_default(self, name: &str) -> Self {
123        self.capability(CapabilityBuilder::protocol().name(name))
124    }
125
126    /// Add a default dictionary declaration.
127    pub fn dictionary_default(self, name: &str) -> Self {
128        self.capability(CapabilityBuilder::dictionary().name(name))
129    }
130
131    /// Add a default runner declaration.
132    pub fn runner_default(self, name: &str) -> Self {
133        self.capability(CapabilityBuilder::runner().name(name))
134    }
135
136    /// Add a default resolver declaration.
137    pub fn resolver_default(self, name: &str) -> Self {
138        self.capability(CapabilityBuilder::resolver().name(name))
139    }
140
141    /// Add a default service declaration.
142    pub fn service_default(self, name: &str) -> Self {
143        self.capability(CapabilityBuilder::service().name(name))
144    }
145
146    /// Add an environment declaration.
147    pub fn environment(mut self, environment: impl Into<cm_rust::EnvironmentDecl>) -> Self {
148        self.result.environments.push(environment.into());
149        self
150    }
151
152    /// Add a config declaration.
153    pub fn config(mut self, config: cm_rust::ConfigDecl) -> Self {
154        self.result.config = Some(config);
155        self
156    }
157
158    /// Generate the final ComponentDecl.
159    pub fn build(self) -> ComponentDecl {
160        self.result
161    }
162}
163
164/// A convenience builder for constructing ChildDecls.
165#[derive(Debug, Derivative)]
166#[derivative(Default)]
167pub struct ChildBuilder {
168    name: Option<LongName>,
169    url: Option<Url>,
170    #[derivative(Default(value = "fdecl::StartupMode::Lazy"))]
171    startup: fdecl::StartupMode,
172    on_terminate: Option<fdecl::OnTerminate>,
173    environment: Option<Name>,
174}
175
176impl ChildBuilder {
177    pub fn new() -> Self {
178        Self::default()
179    }
180
181    /// Defaults url to `"test:///{name}"`.
182    pub fn name(mut self, name: &str) -> Self {
183        self.name = Some(name.parse().unwrap());
184        if self.url.is_none() {
185            self.url = Some(format!("test:///{name}").parse().unwrap());
186        }
187        self
188    }
189
190    pub fn url(mut self, url: &str) -> Self {
191        self.url = Some(url.parse().unwrap());
192        self
193    }
194
195    pub fn startup(mut self, startup: fdecl::StartupMode) -> Self {
196        self.startup = startup;
197        self
198    }
199
200    pub fn eager(self) -> Self {
201        self.startup(fdecl::StartupMode::Eager)
202    }
203
204    pub fn on_terminate(mut self, on_terminate: fdecl::OnTerminate) -> Self {
205        self.on_terminate = Some(on_terminate);
206        self
207    }
208
209    pub fn environment(mut self, environment: &str) -> Self {
210        self.environment = Some(environment.parse().unwrap());
211        self
212    }
213
214    pub fn build(self) -> cm_rust::ChildDecl {
215        cm_rust::ChildDecl {
216            name: self.name.expect("name not set"),
217            url: self.url.expect("url not set"),
218            startup: self.startup,
219            on_terminate: self.on_terminate,
220            environment: self.environment,
221            config_overrides: None,
222        }
223    }
224}
225
226impl From<ChildBuilder> for cm_rust::ChildDecl {
227    fn from(builder: ChildBuilder) -> Self {
228        builder.build()
229    }
230}
231
232/// A convenience builder for constructing CollectionDecls.
233#[derive(Debug, Derivative)]
234#[derivative(Default)]
235pub struct CollectionBuilder {
236    name: Option<Name>,
237    #[derivative(Default(value = "fdecl::Durability::Transient"))]
238    durability: fdecl::Durability,
239    environment: Option<Name>,
240    #[derivative(Default(value = "cm_types::AllowedOffers::StaticOnly"))]
241    allowed_offers: cm_types::AllowedOffers,
242    allow_long_names: bool,
243    persistent_storage: Option<bool>,
244}
245
246impl CollectionBuilder {
247    pub fn new() -> Self {
248        Self::default()
249    }
250
251    pub fn name(mut self, name: &str) -> Self {
252        self.name = Some(name.parse().unwrap());
253        self
254    }
255
256    pub fn durability(mut self, durability: fdecl::Durability) -> Self {
257        self.durability = durability;
258        self
259    }
260
261    pub fn environment(mut self, environment: &str) -> Self {
262        self.environment = Some(environment.parse().unwrap());
263        self
264    }
265
266    pub fn allowed_offers(mut self, allowed_offers: cm_types::AllowedOffers) -> Self {
267        self.allowed_offers = allowed_offers;
268        self
269    }
270
271    pub fn allow_long_names(mut self) -> Self {
272        self.allow_long_names = true;
273        self
274    }
275
276    pub fn persistent_storage(mut self, persistent_storage: bool) -> Self {
277        self.persistent_storage = Some(persistent_storage);
278        self
279    }
280
281    pub fn build(self) -> cm_rust::CollectionDecl {
282        cm_rust::CollectionDecl {
283            name: self.name.expect("name not set"),
284            durability: self.durability,
285            environment: self.environment,
286            allowed_offers: self.allowed_offers,
287            allow_long_names: self.allow_long_names,
288            persistent_storage: self.persistent_storage,
289        }
290    }
291}
292
293impl From<CollectionBuilder> for cm_rust::CollectionDecl {
294    fn from(builder: CollectionBuilder) -> Self {
295        builder.build()
296    }
297}
298
299/// A convenience builder for constructing EnvironmentDecls.
300#[derive(Debug, Derivative)]
301#[derivative(Default)]
302pub struct EnvironmentBuilder {
303    name: Option<Name>,
304    #[derivative(Default(value = "fdecl::EnvironmentExtends::Realm"))]
305    extends: fdecl::EnvironmentExtends,
306    runners: Vec<cm_rust::RunnerRegistration>,
307    resolvers: Vec<cm_rust::ResolverRegistration>,
308    debug_capabilities: Vec<cm_rust::DebugRegistration>,
309    stop_timeout_ms: Option<u32>,
310}
311
312impl EnvironmentBuilder {
313    pub fn new() -> Self {
314        Self::default()
315    }
316
317    pub fn name(mut self, name: &str) -> Self {
318        self.name = Some(name.parse().unwrap());
319        self
320    }
321
322    pub fn extends(mut self, extends: fdecl::EnvironmentExtends) -> Self {
323        self.extends = extends;
324        self
325    }
326
327    pub fn runner(mut self, runner: cm_rust::RunnerRegistration) -> Self {
328        self.runners.push(runner);
329        self
330    }
331
332    pub fn resolver(mut self, resolver: cm_rust::ResolverRegistration) -> Self {
333        self.resolvers.push(resolver);
334        self
335    }
336
337    pub fn debug(mut self, debug: cm_rust::DebugRegistration) -> Self {
338        self.debug_capabilities.push(debug);
339        self
340    }
341
342    pub fn stop_timeout(mut self, timeout_ms: u32) -> Self {
343        self.stop_timeout_ms = Some(timeout_ms);
344        self
345    }
346
347    pub fn build(self) -> cm_rust::EnvironmentDecl {
348        cm_rust::EnvironmentDecl {
349            name: self.name.expect("name not set"),
350            extends: self.extends,
351            runners: self.runners,
352            resolvers: self.resolvers,
353            debug_capabilities: self.debug_capabilities,
354            stop_timeout_ms: self.stop_timeout_ms,
355        }
356    }
357}
358
359impl From<EnvironmentBuilder> for cm_rust::EnvironmentDecl {
360    fn from(builder: EnvironmentBuilder) -> Self {
361        builder.build()
362    }
363}
364
365/// A convenience builder for constructing [CapabilityDecl]s.
366///
367/// To use, call the constructor matching their capability type ([CapabilityBuilder::protocol],
368/// [CapabilityBuilder::directory], etc., and then call methods to set properties. When done,
369/// call [CapabilityBuilder::build] (or [Into::into]) to generate the [CapabilityDecl].
370#[derive(Debug)]
371pub struct CapabilityBuilder {
372    name: Option<Name>,
373    type_: CapabilityTypeName,
374    path: Option<Path>,
375    dictionary_source: Option<cm_rust::DictionarySource>,
376    source_dictionary: Option<RelativePath>,
377    rights: fio::Operations,
378    subdir: RelativePath,
379    backing_dir: Option<Name>,
380    storage_source: Option<cm_rust::StorageDirectorySource>,
381    storage_id: fdecl::StorageId,
382    value: Option<cm_rust::ConfigValue>,
383    delivery: cm_rust::DeliveryType,
384}
385
386impl CapabilityBuilder {
387    pub fn protocol() -> Self {
388        Self::new(CapabilityTypeName::Protocol)
389    }
390
391    pub fn service() -> Self {
392        Self::new(CapabilityTypeName::Service)
393    }
394
395    pub fn directory() -> Self {
396        Self::new(CapabilityTypeName::Directory)
397    }
398
399    pub fn storage() -> Self {
400        Self::new(CapabilityTypeName::Storage)
401    }
402
403    pub fn runner() -> Self {
404        Self::new(CapabilityTypeName::Runner)
405    }
406
407    pub fn resolver() -> Self {
408        Self::new(CapabilityTypeName::Resolver)
409    }
410
411    pub fn dictionary() -> Self {
412        Self::new(CapabilityTypeName::Dictionary)
413    }
414
415    pub fn config() -> Self {
416        Self::new(CapabilityTypeName::Config)
417    }
418
419    pub fn name(mut self, name: &str) -> Self {
420        self.name = Some(name.parse().unwrap());
421        if self.path.is_some() {
422            return self;
423        }
424        match self.type_ {
425            CapabilityTypeName::Protocol => {
426                self.path = Some(format!("/svc/{name}").parse().unwrap());
427            }
428            CapabilityTypeName::Service => {
429                self.path = Some(format!("/svc/{name}").parse().unwrap());
430            }
431            CapabilityTypeName::Runner => {
432                self.path = Some("/svc/fuchsia.component.runner.ComponentRunner".parse().unwrap());
433            }
434            CapabilityTypeName::Resolver => {
435                self.path = Some("/svc/fuchsia.component.resolution.Resolver".parse().unwrap());
436            }
437            CapabilityTypeName::Dictionary
438            | CapabilityTypeName::Storage
439            | CapabilityTypeName::Config
440            | CapabilityTypeName::Directory => {}
441            CapabilityTypeName::EventStream => unreachable!(),
442        }
443        self
444    }
445
446    fn new(type_: CapabilityTypeName) -> Self {
447        Self {
448            type_,
449            name: None,
450            path: None,
451            dictionary_source: None,
452            source_dictionary: None,
453            rights: fio::R_STAR_DIR,
454            subdir: Default::default(),
455            backing_dir: None,
456            storage_source: None,
457            storage_id: fdecl::StorageId::StaticInstanceIdOrMoniker,
458            value: None,
459            delivery: Default::default(),
460        }
461    }
462
463    pub fn path(mut self, path: &str) -> Self {
464        assert_matches!(
465            self.type_,
466            CapabilityTypeName::Protocol
467                | CapabilityTypeName::Service
468                | CapabilityTypeName::Directory
469                | CapabilityTypeName::Dictionary
470                | CapabilityTypeName::Runner
471                | CapabilityTypeName::Resolver
472        );
473        if self.type_ == CapabilityTypeName::Dictionary {
474            if self.dictionary_source.is_some() || self.source_dictionary.is_some() {
475                panic!("Dictionary path is incompatible with source_dictionary");
476            }
477        }
478        self.path = Some(path.parse().unwrap());
479        self
480    }
481
482    pub fn rights(mut self, rights: fio::Operations) -> Self {
483        assert_matches!(self.type_, CapabilityTypeName::Directory);
484        self.rights = rights;
485        self
486    }
487
488    pub fn backing_dir(mut self, backing_dir: &str) -> Self {
489        assert_matches!(self.type_, CapabilityTypeName::Storage);
490        self.backing_dir = Some(backing_dir.parse().unwrap());
491        self
492    }
493
494    pub fn value(mut self, value: cm_rust::ConfigValue) -> Self {
495        assert_matches!(self.type_, CapabilityTypeName::Config);
496        self.value = Some(value);
497        self
498    }
499
500    pub fn source(mut self, source: cm_rust::StorageDirectorySource) -> Self {
501        assert_matches!(self.type_, CapabilityTypeName::Storage);
502        self.storage_source = Some(source);
503        self
504    }
505
506    pub fn subdir(mut self, subdir: &str) -> Self {
507        assert_matches!(self.type_, CapabilityTypeName::Storage);
508        self.subdir = subdir.parse().unwrap();
509        self
510    }
511
512    pub fn storage_id(mut self, storage_id: fdecl::StorageId) -> Self {
513        assert_matches!(self.type_, CapabilityTypeName::Storage);
514        self.storage_id = storage_id;
515        self
516    }
517
518    pub fn delivery(mut self, delivery: cm_rust::DeliveryType) -> Self {
519        assert_matches!(self.type_, CapabilityTypeName::Protocol);
520        self.delivery = delivery;
521        self
522    }
523
524    pub fn build(self) -> cm_rust::CapabilityDecl {
525        match self.type_ {
526            CapabilityTypeName::Protocol => {
527                cm_rust::CapabilityDecl::Protocol(cm_rust::ProtocolDecl {
528                    name: self.name.expect("name not set"),
529                    source_path: Some(self.path.expect("path not set")),
530                    delivery: self.delivery,
531                })
532            }
533            CapabilityTypeName::Service => cm_rust::CapabilityDecl::Service(cm_rust::ServiceDecl {
534                name: self.name.expect("name not set"),
535                source_path: Some(self.path.expect("path not set")),
536            }),
537            CapabilityTypeName::Runner => cm_rust::CapabilityDecl::Runner(cm_rust::RunnerDecl {
538                name: self.name.expect("name not set"),
539                source_path: Some(self.path.expect("path not set")),
540            }),
541            CapabilityTypeName::Resolver => {
542                cm_rust::CapabilityDecl::Resolver(cm_rust::ResolverDecl {
543                    name: self.name.expect("name not set"),
544                    source_path: Some(self.path.expect("path not set")),
545                })
546            }
547            CapabilityTypeName::Dictionary => {
548                cm_rust::CapabilityDecl::Dictionary(cm_rust::DictionaryDecl {
549                    name: self.name.expect("name not set"),
550                    source_path: self.path,
551                })
552            }
553            CapabilityTypeName::Storage => cm_rust::CapabilityDecl::Storage(cm_rust::StorageDecl {
554                name: self.name.expect("name not set"),
555                backing_dir: self.backing_dir.expect("backing_dir not set"),
556                source: self.storage_source.expect("source not set"),
557                subdir: self.subdir,
558                storage_id: self.storage_id,
559            }),
560            CapabilityTypeName::Directory => {
561                cm_rust::CapabilityDecl::Directory(cm_rust::DirectoryDecl {
562                    name: self.name.expect("name not set"),
563                    source_path: Some(self.path.expect("path not set")),
564                    rights: self.rights,
565                })
566            }
567            CapabilityTypeName::Config => {
568                cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
569                    name: self.name.expect("name not set"),
570                    value: self.value.expect("value not set"),
571                })
572            }
573            CapabilityTypeName::EventStream => unreachable!(),
574        }
575    }
576}
577
578impl From<CapabilityBuilder> for cm_rust::CapabilityDecl {
579    fn from(builder: CapabilityBuilder) -> Self {
580        builder.build()
581    }
582}
583
584/// A convenience builder for constructing [UseDecl]s.
585///
586/// To use, call the constructor matching their capability type ([UseBuilder::protocol],
587/// [UseBuilder::directory], etc.), and then call methods to set properties. When done,
588/// call [UseBuilder::build] (or [Into::into]) to generate the [UseDecl].
589#[derive(Debug)]
590pub struct UseBuilder {
591    source_name: Option<Name>,
592    type_: CapabilityTypeName,
593    source_dictionary: RelativePath,
594    source: cm_rust::UseSource,
595    target_name: Option<Name>,
596    target_path: Option<Path>,
597    dependency_type: cm_rust::DependencyType,
598    availability: cm_rust::Availability,
599    rights: fio::Operations,
600    subdir: RelativePath,
601    scope: Option<Vec<cm_rust::EventScope>>,
602    filter: Option<BTreeMap<String, cm_rust::DictionaryValue>>,
603    config_type: Option<cm_rust::ConfigValueType>,
604}
605
606impl UseBuilder {
607    pub fn protocol() -> Self {
608        Self::new(CapabilityTypeName::Protocol)
609    }
610
611    pub fn service() -> Self {
612        Self::new(CapabilityTypeName::Service)
613    }
614
615    pub fn directory() -> Self {
616        Self::new(CapabilityTypeName::Directory)
617    }
618
619    pub fn storage() -> Self {
620        Self::new(CapabilityTypeName::Storage)
621    }
622
623    pub fn runner() -> Self {
624        Self::new(CapabilityTypeName::Runner)
625    }
626
627    pub fn event_stream() -> Self {
628        Self::new(CapabilityTypeName::EventStream)
629    }
630
631    pub fn config() -> Self {
632        Self::new(CapabilityTypeName::Config)
633    }
634
635    fn new(type_: CapabilityTypeName) -> Self {
636        Self {
637            type_,
638            source: cm_rust::UseSource::Parent,
639            source_name: None,
640            target_name: None,
641            target_path: None,
642            source_dictionary: Default::default(),
643            rights: fio::R_STAR_DIR,
644            subdir: Default::default(),
645            dependency_type: cm_rust::DependencyType::Strong,
646            availability: cm_rust::Availability::Required,
647            scope: None,
648            filter: None,
649            config_type: None,
650        }
651    }
652
653    pub fn config_type(mut self, type_: cm_rust::ConfigValueType) -> Self {
654        self.config_type = Some(type_);
655        self
656    }
657
658    pub fn name(mut self, name: &str) -> Self {
659        self.source_name = Some(name.parse().unwrap());
660        if self.target_path.is_some() || self.target_name.is_some() {
661            return self;
662        }
663        match self.type_ {
664            CapabilityTypeName::Protocol | CapabilityTypeName::Service => {
665                self.target_path = Some(format!("/svc/{name}").parse().unwrap());
666            }
667            CapabilityTypeName::EventStream => {
668                self.target_path = Some("/svc/fuchsia.component.EventStream".parse().unwrap());
669            }
670            CapabilityTypeName::Runner | CapabilityTypeName::Config => {
671                self.target_name = self.source_name.clone();
672            }
673            CapabilityTypeName::Storage | CapabilityTypeName::Directory => {}
674            CapabilityTypeName::Dictionary | CapabilityTypeName::Resolver => unreachable!(),
675        }
676        self
677    }
678
679    pub fn path(mut self, path: &str) -> Self {
680        assert_matches!(
681            self.type_,
682            CapabilityTypeName::Protocol
683                | CapabilityTypeName::Service
684                | CapabilityTypeName::Directory
685                | CapabilityTypeName::EventStream
686                | CapabilityTypeName::Storage
687        );
688        self.target_path = Some(path.parse().unwrap());
689        self
690    }
691
692    pub fn target_name(mut self, name: &str) -> Self {
693        assert_matches!(self.type_, CapabilityTypeName::Runner | CapabilityTypeName::Config);
694        self.target_name = Some(name.parse().unwrap());
695        self
696    }
697
698    pub fn from_dictionary(mut self, dictionary: &str) -> Self {
699        assert_matches!(
700            self.type_,
701            CapabilityTypeName::Service
702                | CapabilityTypeName::Protocol
703                | CapabilityTypeName::Directory
704                | CapabilityTypeName::Runner
705        );
706        self.source_dictionary = dictionary.parse().unwrap();
707        self
708    }
709
710    pub fn source(mut self, source: cm_rust::UseSource) -> Self {
711        assert_matches!(self.type_, t if t != CapabilityTypeName::Storage);
712        self.source = source;
713        self
714    }
715
716    pub fn source_static_child(self, source: &str) -> Self {
717        self.source(cm_rust::UseSource::Child(source.parse().unwrap()))
718    }
719
720    pub fn availability(mut self, availability: cm_rust::Availability) -> Self {
721        assert_matches!(
722            self.type_,
723            CapabilityTypeName::Protocol
724                | CapabilityTypeName::Service
725                | CapabilityTypeName::Directory
726                | CapabilityTypeName::EventStream
727                | CapabilityTypeName::Storage
728                | CapabilityTypeName::Config
729        );
730        self.availability = availability;
731        self
732    }
733
734    pub fn dependency(mut self, dependency: cm_rust::DependencyType) -> Self {
735        assert_matches!(
736            self.type_,
737            CapabilityTypeName::Protocol
738                | CapabilityTypeName::Service
739                | CapabilityTypeName::Directory
740        );
741        self.dependency_type = dependency;
742        self
743    }
744
745    pub fn rights(mut self, rights: fio::Operations) -> Self {
746        assert_matches!(self.type_, CapabilityTypeName::Directory);
747        self.rights = rights;
748        self
749    }
750
751    pub fn subdir(mut self, subdir: &str) -> Self {
752        assert_matches!(self.type_, CapabilityTypeName::Directory);
753        self.subdir = subdir.parse().unwrap();
754        self
755    }
756
757    pub fn scope(mut self, scope: Vec<cm_rust::EventScope>) -> Self {
758        assert_matches!(self.type_, CapabilityTypeName::EventStream);
759        self.scope = Some(scope);
760        self
761    }
762
763    pub fn filter(mut self, filter: BTreeMap<String, cm_rust::DictionaryValue>) -> Self {
764        assert_matches!(self.type_, CapabilityTypeName::EventStream);
765        self.filter = Some(filter);
766        self
767    }
768
769    pub fn build(self) -> cm_rust::UseDecl {
770        match self.type_ {
771            CapabilityTypeName::Protocol => cm_rust::UseDecl::Protocol(cm_rust::UseProtocolDecl {
772                source: self.source,
773                source_name: self.source_name.expect("name not set"),
774                source_dictionary: self.source_dictionary,
775                target_path: self.target_path.expect("path not set"),
776                dependency_type: self.dependency_type,
777                availability: self.availability,
778            }),
779            CapabilityTypeName::Service => cm_rust::UseDecl::Service(cm_rust::UseServiceDecl {
780                source: self.source,
781                source_name: self.source_name.expect("name not set"),
782                source_dictionary: self.source_dictionary,
783                target_path: self.target_path.expect("path not set"),
784                dependency_type: self.dependency_type,
785                availability: self.availability,
786            }),
787            CapabilityTypeName::Directory => {
788                cm_rust::UseDecl::Directory(cm_rust::UseDirectoryDecl {
789                    source: self.source,
790                    source_name: self.source_name.expect("name not set"),
791                    source_dictionary: self.source_dictionary,
792                    target_path: self.target_path.expect("path not set"),
793                    rights: self.rights,
794                    subdir: self.subdir,
795                    dependency_type: self.dependency_type,
796                    availability: self.availability,
797                })
798            }
799            CapabilityTypeName::Storage => cm_rust::UseDecl::Storage(cm_rust::UseStorageDecl {
800                source_name: self.source_name.expect("name not set"),
801                target_path: self.target_path.expect("path not set"),
802                availability: self.availability,
803            }),
804            CapabilityTypeName::EventStream => {
805                cm_rust::UseDecl::EventStream(cm_rust::UseEventStreamDecl {
806                    source: self.source,
807                    source_name: self.source_name.expect("name not set"),
808                    target_path: self.target_path.expect("path not set"),
809                    availability: self.availability,
810                    scope: self.scope,
811                    filter: self.filter,
812                })
813            }
814            CapabilityTypeName::Runner => cm_rust::UseDecl::Runner(cm_rust::UseRunnerDecl {
815                source: self.source,
816                source_name: self.source_name.expect("name not set"),
817                source_dictionary: self.source_dictionary,
818            }),
819            CapabilityTypeName::Config => cm_rust::UseDecl::Config(cm_rust::UseConfigurationDecl {
820                source: self.source,
821                source_name: self.source_name.expect("name not set"),
822                target_name: self.target_name.expect("target name not set"),
823                availability: self.availability,
824                type_: self.config_type.expect("config_type not set"),
825                default: None,
826                source_dictionary: self.source_dictionary,
827            }),
828            CapabilityTypeName::Resolver | CapabilityTypeName::Dictionary => unreachable!(),
829        }
830    }
831}
832
833impl From<UseBuilder> for cm_rust::UseDecl {
834    fn from(builder: UseBuilder) -> Self {
835        builder.build()
836    }
837}
838
839/// A convenience builder for constructing [ExposeDecl]s.
840///
841/// To use, call the constructor matching their capability type ([ExposeBuilder::protocol],
842/// [ExposeBuilder::directory], etc.), and then call methods to set properties. When done,
843/// call [ExposeBuilder::build] (or [Into::into]) to generate the [ExposeDecl].
844#[derive(Debug)]
845pub struct ExposeBuilder {
846    source_name: Option<Name>,
847    type_: CapabilityTypeName,
848    source_dictionary: RelativePath,
849    source: Option<cm_rust::ExposeSource>,
850    target: cm_rust::ExposeTarget,
851    target_name: Option<Name>,
852    availability: cm_rust::Availability,
853    rights: Option<fio::Operations>,
854    subdir: RelativePath,
855}
856
857impl ExposeBuilder {
858    pub fn protocol() -> Self {
859        Self::new(CapabilityTypeName::Protocol)
860    }
861
862    pub fn service() -> Self {
863        Self::new(CapabilityTypeName::Service)
864    }
865
866    pub fn directory() -> Self {
867        Self::new(CapabilityTypeName::Directory)
868    }
869
870    pub fn runner() -> Self {
871        Self::new(CapabilityTypeName::Runner)
872    }
873
874    pub fn resolver() -> Self {
875        Self::new(CapabilityTypeName::Resolver)
876    }
877
878    pub fn dictionary() -> Self {
879        Self::new(CapabilityTypeName::Dictionary)
880    }
881
882    pub fn config() -> Self {
883        Self::new(CapabilityTypeName::Config)
884    }
885
886    fn new(type_: CapabilityTypeName) -> Self {
887        Self {
888            type_,
889            source: None,
890            target: cm_rust::ExposeTarget::Parent,
891            source_name: None,
892            target_name: None,
893            source_dictionary: Default::default(),
894            rights: None,
895            subdir: Default::default(),
896            availability: cm_rust::Availability::Required,
897        }
898    }
899
900    pub fn name(mut self, name: &str) -> Self {
901        self.source_name = Some(name.parse().unwrap());
902        if self.target_name.is_some() {
903            return self;
904        }
905        self.target_name = self.source_name.clone();
906        self
907    }
908
909    pub fn target_name(mut self, name: &str) -> Self {
910        self.target_name = Some(name.parse().unwrap());
911        self
912    }
913
914    pub fn from_dictionary(mut self, dictionary: &str) -> Self {
915        assert_matches!(
916            self.type_,
917            CapabilityTypeName::Service
918                | CapabilityTypeName::Protocol
919                | CapabilityTypeName::Directory
920                | CapabilityTypeName::Dictionary
921                | CapabilityTypeName::Runner
922                | CapabilityTypeName::Resolver
923        );
924        self.source_dictionary = dictionary.parse().unwrap();
925        self
926    }
927
928    pub fn source(mut self, source: cm_rust::ExposeSource) -> Self {
929        self.source = Some(source);
930        self
931    }
932
933    pub fn source_static_child(self, source: &str) -> Self {
934        self.source(cm_rust::ExposeSource::Child(source.parse().unwrap()))
935    }
936
937    pub fn target(mut self, target: cm_rust::ExposeTarget) -> Self {
938        self.target = target;
939        self
940    }
941
942    pub fn availability(mut self, availability: cm_rust::Availability) -> Self {
943        assert_matches!(
944            self.type_,
945            CapabilityTypeName::Protocol
946                | CapabilityTypeName::Service
947                | CapabilityTypeName::Directory
948                | CapabilityTypeName::Config
949                | CapabilityTypeName::Dictionary
950        );
951        self.availability = availability;
952        self
953    }
954
955    pub fn rights(mut self, rights: fio::Operations) -> Self {
956        assert_matches!(self.type_, CapabilityTypeName::Directory);
957        self.rights = Some(rights);
958        self
959    }
960
961    pub fn subdir(mut self, subdir: &str) -> Self {
962        assert_matches!(self.type_, CapabilityTypeName::Directory);
963        self.subdir = subdir.parse().unwrap();
964        self
965    }
966
967    pub fn build(self) -> cm_rust::ExposeDecl {
968        match self.type_ {
969            CapabilityTypeName::Protocol => {
970                cm_rust::ExposeDecl::Protocol(cm_rust::ExposeProtocolDecl {
971                    source: self.source.expect("source not set"),
972                    source_name: self.source_name.expect("name not set"),
973                    source_dictionary: self.source_dictionary,
974                    target: self.target,
975                    target_name: self.target_name.expect("name not set"),
976                    availability: self.availability,
977                })
978            }
979            CapabilityTypeName::Service => {
980                cm_rust::ExposeDecl::Service(cm_rust::ExposeServiceDecl {
981                    source: self.source.expect("source not set"),
982                    source_name: self.source_name.expect("name not set"),
983                    source_dictionary: self.source_dictionary,
984                    target: self.target,
985                    target_name: self.target_name.expect("name not set"),
986                    availability: self.availability,
987                })
988            }
989            CapabilityTypeName::Directory => {
990                cm_rust::ExposeDecl::Directory(cm_rust::ExposeDirectoryDecl {
991                    source: self.source.expect("source not set"),
992                    source_name: self.source_name.expect("name not set"),
993                    source_dictionary: self.source_dictionary,
994                    target: self.target,
995                    target_name: self.target_name.expect("name not set"),
996                    rights: self.rights,
997                    subdir: self.subdir,
998                    availability: self.availability,
999                })
1000            }
1001            CapabilityTypeName::Runner => cm_rust::ExposeDecl::Runner(cm_rust::ExposeRunnerDecl {
1002                source: self.source.expect("source not set"),
1003                source_name: self.source_name.expect("name not set"),
1004                source_dictionary: self.source_dictionary,
1005                target: self.target,
1006                target_name: self.target_name.expect("name not set"),
1007            }),
1008            CapabilityTypeName::Resolver => {
1009                cm_rust::ExposeDecl::Resolver(cm_rust::ExposeResolverDecl {
1010                    source: self.source.expect("source not set"),
1011                    source_name: self.source_name.expect("name not set"),
1012                    source_dictionary: self.source_dictionary,
1013                    target: self.target,
1014                    target_name: self.target_name.expect("name not set"),
1015                })
1016            }
1017            CapabilityTypeName::Config => {
1018                cm_rust::ExposeDecl::Config(cm_rust::ExposeConfigurationDecl {
1019                    source: self.source.expect("source not set"),
1020                    source_name: self.source_name.expect("name not set"),
1021                    source_dictionary: self.source_dictionary,
1022                    target: self.target,
1023                    target_name: self.target_name.expect("name not set"),
1024                    availability: self.availability,
1025                })
1026            }
1027            CapabilityTypeName::Dictionary => {
1028                cm_rust::ExposeDecl::Dictionary(cm_rust::ExposeDictionaryDecl {
1029                    source: self.source.expect("source not set"),
1030                    source_name: self.source_name.expect("name not set"),
1031                    source_dictionary: self.source_dictionary,
1032                    target: self.target,
1033                    target_name: self.target_name.expect("name not set"),
1034                    availability: self.availability,
1035                })
1036            }
1037            CapabilityTypeName::EventStream | CapabilityTypeName::Storage => unreachable!(),
1038        }
1039    }
1040}
1041
1042impl From<ExposeBuilder> for cm_rust::ExposeDecl {
1043    fn from(builder: ExposeBuilder) -> Self {
1044        builder.build()
1045    }
1046}
1047
1048pub fn offer_source_static_child(name: &str) -> cm_rust::OfferSource {
1049    cm_rust::OfferSource::Child(cm_rust::ChildRef { name: name.parse().unwrap(), collection: None })
1050}
1051
1052pub fn offer_target_static_child(name: &str) -> cm_rust::OfferTarget {
1053    cm_rust::OfferTarget::Child(cm_rust::ChildRef { name: name.parse().unwrap(), collection: None })
1054}
1055
1056/// A convenience builder for constructing [OfferDecl]s.
1057///
1058/// To use, call the constructor matching their capability type ([OfferBuilder::protocol],
1059/// [OfferBuilder::directory], etc.), and then call methods to set properties. When done,
1060/// call [OfferBuilder::build] (or [Into::into]) to generate the [OfferDecl].
1061#[derive(Debug)]
1062pub struct OfferBuilder {
1063    source_name: Option<Name>,
1064    type_: CapabilityTypeName,
1065    source_dictionary: RelativePath,
1066    source: Option<cm_rust::OfferSource>,
1067    target: Option<cm_rust::OfferTarget>,
1068    target_name: Option<Name>,
1069    source_instance_filter: Option<Vec<Name>>,
1070    renamed_instances: Option<Vec<cm_rust::NameMapping>>,
1071    rights: Option<fio::Operations>,
1072    subdir: RelativePath,
1073    scope: Option<Vec<cm_rust::EventScope>>,
1074    dependency_type: cm_rust::DependencyType,
1075    availability: cm_rust::Availability,
1076}
1077
1078impl OfferBuilder {
1079    pub fn protocol() -> Self {
1080        Self::new(CapabilityTypeName::Protocol)
1081    }
1082
1083    pub fn service() -> Self {
1084        Self::new(CapabilityTypeName::Service)
1085    }
1086
1087    pub fn directory() -> Self {
1088        Self::new(CapabilityTypeName::Directory)
1089    }
1090
1091    pub fn storage() -> Self {
1092        Self::new(CapabilityTypeName::Storage)
1093    }
1094
1095    pub fn runner() -> Self {
1096        Self::new(CapabilityTypeName::Runner)
1097    }
1098
1099    pub fn resolver() -> Self {
1100        Self::new(CapabilityTypeName::Resolver)
1101    }
1102
1103    pub fn dictionary() -> Self {
1104        Self::new(CapabilityTypeName::Dictionary)
1105    }
1106
1107    pub fn event_stream() -> Self {
1108        Self::new(CapabilityTypeName::EventStream)
1109    }
1110
1111    pub fn config() -> Self {
1112        Self::new(CapabilityTypeName::Config)
1113    }
1114
1115    fn new(type_: CapabilityTypeName) -> Self {
1116        Self {
1117            type_,
1118            source: None,
1119            target: None,
1120            source_name: None,
1121            target_name: None,
1122            source_dictionary: Default::default(),
1123            source_instance_filter: None,
1124            renamed_instances: None,
1125            rights: None,
1126            subdir: Default::default(),
1127            scope: None,
1128            dependency_type: cm_rust::DependencyType::Strong,
1129            availability: cm_rust::Availability::Required,
1130        }
1131    }
1132
1133    pub fn name(mut self, name: &str) -> Self {
1134        self.source_name = Some(name.parse().unwrap());
1135        if self.target_name.is_some() {
1136            return self;
1137        }
1138        self.target_name = self.source_name.clone();
1139        self
1140    }
1141
1142    pub fn target_name(mut self, name: &str) -> Self {
1143        self.target_name = Some(name.parse().unwrap());
1144        self
1145    }
1146
1147    pub fn from_dictionary(mut self, dictionary: &str) -> Self {
1148        assert_matches!(
1149            self.type_,
1150            CapabilityTypeName::Service
1151                | CapabilityTypeName::Protocol
1152                | CapabilityTypeName::Directory
1153                | CapabilityTypeName::Dictionary
1154                | CapabilityTypeName::Runner
1155                | CapabilityTypeName::Resolver
1156        );
1157        self.source_dictionary = dictionary.parse().unwrap();
1158        self
1159    }
1160
1161    pub fn source(mut self, source: cm_rust::OfferSource) -> Self {
1162        self.source = Some(source);
1163        self
1164    }
1165
1166    pub fn source_static_child(self, source: &str) -> Self {
1167        self.source(offer_source_static_child(source))
1168    }
1169
1170    pub fn target(mut self, target: cm_rust::OfferTarget) -> Self {
1171        self.target = Some(target);
1172        self
1173    }
1174
1175    pub fn target_static_child(self, target: &str) -> Self {
1176        self.target(offer_target_static_child(target))
1177    }
1178
1179    pub fn availability(mut self, availability: cm_rust::Availability) -> Self {
1180        assert_matches!(
1181            self.type_,
1182            CapabilityTypeName::Protocol
1183                | CapabilityTypeName::Service
1184                | CapabilityTypeName::Directory
1185                | CapabilityTypeName::Storage
1186                | CapabilityTypeName::EventStream
1187                | CapabilityTypeName::Config
1188                | CapabilityTypeName::Dictionary
1189        );
1190        self.availability = availability;
1191        self
1192    }
1193
1194    pub fn dependency(mut self, dependency: cm_rust::DependencyType) -> Self {
1195        assert_matches!(
1196            self.type_,
1197            CapabilityTypeName::Protocol
1198                | CapabilityTypeName::Directory
1199                | CapabilityTypeName::Dictionary
1200        );
1201        self.dependency_type = dependency;
1202        self
1203    }
1204
1205    pub fn source_instance_filter<'a>(mut self, filter: impl IntoIterator<Item = &'a str>) -> Self {
1206        assert_matches!(self.type_, CapabilityTypeName::Service);
1207        self.source_instance_filter =
1208            Some(filter.into_iter().map(|s| s.parse().unwrap()).collect());
1209        self
1210    }
1211
1212    pub fn renamed_instances<'a, 'b>(
1213        mut self,
1214        mapping: impl IntoIterator<Item = (&'a str, &'b str)>,
1215    ) -> Self {
1216        assert_matches!(self.type_, CapabilityTypeName::Service);
1217        self.renamed_instances = Some(
1218            mapping
1219                .into_iter()
1220                .map(|(s, t)| cm_rust::NameMapping {
1221                    source_name: s.parse().unwrap(),
1222                    target_name: t.parse().unwrap(),
1223                })
1224                .collect(),
1225        );
1226        self
1227    }
1228
1229    pub fn rights(mut self, rights: fio::Operations) -> Self {
1230        assert_matches!(self.type_, CapabilityTypeName::Directory);
1231        self.rights = Some(rights);
1232        self
1233    }
1234
1235    pub fn subdir(mut self, subdir: &str) -> Self {
1236        assert_matches!(self.type_, CapabilityTypeName::Directory);
1237        self.subdir = subdir.parse().unwrap();
1238        self
1239    }
1240
1241    pub fn scope(mut self, scope: Vec<cm_rust::EventScope>) -> Self {
1242        assert_matches!(self.type_, CapabilityTypeName::EventStream);
1243        self.scope = Some(scope);
1244        self
1245    }
1246
1247    pub fn build(self) -> cm_rust::OfferDecl {
1248        match self.type_ {
1249            CapabilityTypeName::Protocol => {
1250                cm_rust::OfferDecl::Protocol(cm_rust::OfferProtocolDecl {
1251                    source: self.source.expect("source not set"),
1252                    source_name: self.source_name.expect("name not set"),
1253                    source_dictionary: self.source_dictionary,
1254                    target: self.target.expect("target not set"),
1255                    target_name: self.target_name.expect("name not set"),
1256                    dependency_type: self.dependency_type,
1257                    availability: self.availability,
1258                })
1259            }
1260            CapabilityTypeName::Service => cm_rust::OfferDecl::Service(cm_rust::OfferServiceDecl {
1261                source: self.source.expect("source not set"),
1262                source_name: self.source_name.expect("name not set"),
1263                source_dictionary: self.source_dictionary,
1264                target: self.target.expect("target is not set"),
1265                target_name: self.target_name.expect("name not set"),
1266                source_instance_filter: self.source_instance_filter,
1267                renamed_instances: self.renamed_instances,
1268                availability: self.availability,
1269                dependency_type: Default::default(),
1270            }),
1271            CapabilityTypeName::Directory => {
1272                cm_rust::OfferDecl::Directory(cm_rust::OfferDirectoryDecl {
1273                    source: self.source.expect("source not set"),
1274                    source_name: self.source_name.expect("name not set"),
1275                    source_dictionary: self.source_dictionary,
1276                    target: self.target.expect("target is not set"),
1277                    target_name: self.target_name.expect("name not set"),
1278                    rights: self.rights,
1279                    subdir: self.subdir,
1280                    dependency_type: self.dependency_type,
1281                    availability: self.availability,
1282                })
1283            }
1284            CapabilityTypeName::Storage => cm_rust::OfferDecl::Storage(cm_rust::OfferStorageDecl {
1285                source: self.source.expect("source not set"),
1286                source_name: self.source_name.expect("name not set"),
1287                target: self.target.expect("target is not set"),
1288                target_name: self.target_name.expect("name not set"),
1289                availability: self.availability,
1290            }),
1291            CapabilityTypeName::EventStream => {
1292                cm_rust::OfferDecl::EventStream(cm_rust::OfferEventStreamDecl {
1293                    source: self.source.expect("source not set"),
1294                    source_name: self.source_name.expect("name not set"),
1295                    target: self.target.expect("target is not set"),
1296                    target_name: self.target_name.expect("name not set"),
1297                    availability: self.availability,
1298                    scope: self.scope,
1299                })
1300            }
1301            CapabilityTypeName::Runner => cm_rust::OfferDecl::Runner(cm_rust::OfferRunnerDecl {
1302                source: self.source.expect("source not set"),
1303                source_name: self.source_name.expect("name not set"),
1304                source_dictionary: self.source_dictionary,
1305                target: self.target.expect("target is not set"),
1306                target_name: self.target_name.expect("name not set"),
1307            }),
1308            CapabilityTypeName::Resolver => {
1309                cm_rust::OfferDecl::Resolver(cm_rust::OfferResolverDecl {
1310                    source: self.source.expect("source not set"),
1311                    source_name: self.source_name.expect("name not set"),
1312                    source_dictionary: self.source_dictionary,
1313                    target: self.target.expect("target is not set"),
1314                    target_name: self.target_name.expect("name not set"),
1315                })
1316            }
1317            CapabilityTypeName::Config => {
1318                cm_rust::OfferDecl::Config(cm_rust::OfferConfigurationDecl {
1319                    source: self.source.expect("source not set"),
1320                    source_name: self.source_name.expect("name not set"),
1321                    target: self.target.expect("target not set"),
1322                    target_name: self.target_name.expect("name not set"),
1323                    availability: self.availability,
1324                    source_dictionary: self.source_dictionary,
1325                })
1326            }
1327            CapabilityTypeName::Dictionary => {
1328                cm_rust::OfferDecl::Dictionary(cm_rust::OfferDictionaryDecl {
1329                    source: self.source.expect("source not set"),
1330                    source_name: self.source_name.expect("name not set"),
1331                    source_dictionary: self.source_dictionary,
1332                    target: self.target.expect("target not set"),
1333                    target_name: self.target_name.expect("name not set"),
1334                    dependency_type: self.dependency_type,
1335                    availability: self.availability,
1336                })
1337            }
1338        }
1339    }
1340}
1341
1342impl From<OfferBuilder> for cm_rust::OfferDecl {
1343    fn from(builder: OfferBuilder) -> Self {
1344        builder.build()
1345    }
1346}