1use crate::types::common::*;
6use crate::{
7 AnyRef, AsClauseContext, CanonicalizeContext, CapabilityId, DictionaryRef, Error, EventScope,
8 FromClauseContext, SourceAvailability,
9};
10
11use crate::one_or_many::{OneOrMany, one_or_many_from_context};
12use crate::types::right::Rights;
13pub use cm_types::{
14 Availability, BorrowedName, BoundedName, DependencyType, HandleType, Name, OnTerminate,
15 ParseError, Path, RelativePath, StartupMode, Url,
16};
17use cml_macro::{OneOrMany, Reference};
18use itertools::Either;
19use reference_doc::ReferenceDoc;
20use serde::{Deserialize, Serialize};
21
22use std::fmt;
23use std::fmt::Write;
24#[allow(unused)] use std::str::FromStr;
26use std::sync::Arc;
27
28#[derive(Deserialize, Debug, PartialEq, Clone, ReferenceDoc, Serialize)]
81#[serde(deny_unknown_fields)]
82#[reference_doc(fields_as = "list", top_level_doc_after_fields)]
83pub struct Offer {
84 #[serde(skip_serializing_if = "Option::is_none")]
86 pub service: Option<OneOrMany<Name>>,
87
88 #[serde(skip_serializing_if = "Option::is_none")]
90 pub protocol: Option<OneOrMany<Name>>,
91
92 #[serde(skip_serializing_if = "Option::is_none")]
94 pub directory: Option<OneOrMany<Name>>,
95
96 #[serde(skip_serializing_if = "Option::is_none")]
98 pub runner: Option<OneOrMany<Name>>,
99
100 #[serde(skip_serializing_if = "Option::is_none")]
102 pub resolver: Option<OneOrMany<Name>>,
103
104 #[serde(skip_serializing_if = "Option::is_none")]
106 pub storage: Option<OneOrMany<Name>>,
107
108 #[serde(skip_serializing_if = "Option::is_none")]
110 pub dictionary: Option<OneOrMany<Name>>,
111
112 #[serde(skip_serializing_if = "Option::is_none")]
114 pub config: Option<OneOrMany<Name>>,
115
116 pub from: OneOrMany<OfferFromRef>,
128
129 pub to: OneOrMany<OfferToRef>,
134
135 #[serde(skip_serializing_if = "Option::is_none")]
139 pub r#as: Option<Name>,
140
141 #[serde(skip_serializing_if = "Option::is_none")]
151 pub dependency: Option<DependencyType>,
152
153 #[serde(skip_serializing_if = "Option::is_none")]
156 #[reference_doc(json_type = "array of string")]
157 pub rights: Option<Rights>,
158
159 #[serde(skip_serializing_if = "Option::is_none")]
162 pub subdir: Option<RelativePath>,
163
164 #[serde(skip_serializing_if = "Option::is_none")]
166 pub event_stream: Option<OneOrMany<Name>>,
167
168 #[serde(skip_serializing_if = "Option::is_none")]
171 pub scope: Option<OneOrMany<EventScope>>,
172
173 #[serde(skip_serializing_if = "Option::is_none")]
190 pub availability: Option<Availability>,
191
192 #[serde(skip_serializing_if = "Option::is_none")]
197 pub source_availability: Option<SourceAvailability>,
198
199 #[serde(skip_serializing_if = "Option::is_none")]
205 pub target_availability: Option<TargetAvailability>,
206}
207
208impl Offer {
209 pub fn empty(from: OneOrMany<OfferFromRef>, to: OneOrMany<OfferToRef>) -> Offer {
212 Self {
213 protocol: None,
214 from,
215 to,
216 r#as: None,
217 service: None,
218 directory: None,
219 config: None,
220 runner: None,
221 resolver: None,
222 storage: None,
223 dictionary: None,
224 dependency: None,
225 rights: None,
226 subdir: None,
227 event_stream: None,
228 scope: None,
229 availability: None,
230 source_availability: None,
231 target_availability: None,
232 }
233 }
234}
235
236impl Default for Offer {
237 fn default() -> Self {
238 Self {
239 from: OneOrMany::One(OfferFromRef::Self_),
240 to: OneOrMany::Many(vec![]),
241 service: None,
242 protocol: None,
243 directory: None,
244 storage: None,
245 runner: None,
246 resolver: None,
247 dictionary: None,
248 config: None,
249 r#as: None,
250 rights: None,
251 subdir: None,
252 dependency: None,
253 event_stream: None,
254 scope: None,
255 availability: None,
256 source_availability: None,
257 target_availability: None,
258 }
259 }
260}
261
262#[derive(Debug, Deserialize, PartialEq, Eq, Hash, Clone, Serialize)]
264#[serde(rename_all = "snake_case")]
265pub enum TargetAvailability {
266 Required,
267 Unknown,
268}
269
270#[derive(PartialEq, Clone)]
271pub enum OfferToAllCapability<'a> {
272 Dictionary(&'a str),
273 Protocol(&'a str),
274}
275
276impl<'a> OfferToAllCapability<'a> {
277 pub fn name(&self) -> &'a str {
278 match self {
279 OfferToAllCapability::Dictionary(name) => name,
280 OfferToAllCapability::Protocol(name) => name,
281 }
282 }
283
284 pub fn offer_type(&self) -> &'static str {
285 match self {
286 OfferToAllCapability::Dictionary(_) => "Dictionary",
287 OfferToAllCapability::Protocol(_) => "Protocol",
288 }
289 }
290
291 pub fn offer_type_plural(&self) -> &'static str {
292 match self {
293 OfferToAllCapability::Dictionary(_) => "dictionaries",
294 OfferToAllCapability::Protocol(_) => "protocols",
295 }
296 }
297}
298
299pub fn offer_to_all_and_component_diff_sources_message<'a>(
300 capability: impl Iterator<Item = OfferToAllCapability<'a>>,
301 component: &str,
302) -> String {
303 let mut output = String::new();
304 let mut capability = capability.peekable();
305 write!(&mut output, "{} ", capability.peek().unwrap().offer_type()).unwrap();
306 for (i, capability) in capability.enumerate() {
307 if i > 0 {
308 write!(&mut output, ", ").unwrap();
309 }
310 write!(&mut output, "{}", capability.name()).unwrap();
311 }
312 write!(
313 &mut output,
314 r#" is offered to both "all" and child component "{}" with different sources"#,
315 component
316 )
317 .unwrap();
318 output
319}
320
321pub fn offer_to_all_and_component_diff_capabilities_message<'a>(
322 capability: impl Iterator<Item = OfferToAllCapability<'a>>,
323 component: &str,
324) -> String {
325 let mut output = String::new();
326 let mut capability_peek = capability.peekable();
327
328 let first_offer_to_all = capability_peek.peek().unwrap().clone();
332 write!(&mut output, "{} ", first_offer_to_all.offer_type()).unwrap();
333 for (i, capability) in capability_peek.enumerate() {
334 if i > 0 {
335 write!(&mut output, ", ").unwrap();
336 }
337 write!(&mut output, "{}", capability.name()).unwrap();
338 }
339 write!(&mut output, r#" is aliased to "{}" with the same name as an offer to "all", but from different source {}"#, component, first_offer_to_all.offer_type_plural()).unwrap();
340 output
341}
342
343#[derive(Debug, PartialEq, Eq, Hash, Clone, Reference)]
345#[reference(
346 expected = "\"parent\", \"framework\", \"self\", \"void\", \"#<child-name>\", or a dictionary path"
347)]
348pub enum OfferFromRef {
349 Named(Name),
351 Parent,
353 Framework,
355 Self_,
357 Void,
359 Dictionary(DictionaryRef),
361}
362
363impl OfferFromRef {
364 pub fn is_named(&self) -> bool {
365 match self {
366 OfferFromRef::Named(_) => true,
367 _ => false,
368 }
369 }
370}
371
372#[derive(Debug, PartialEq, Eq, Hash, Clone, Reference)]
374#[reference(expected = "\"#<child-name>\", \"#<collection-name>\", or \"self/<dictionary>\"")]
375pub enum OfferToRef {
376 Named(Name),
378
379 All,
381
382 OwnDictionary(Name),
384}
385
386#[derive(OneOrMany, Debug, Clone)]
388#[one_or_many(
389 expected = "one or an array of \"#<child-name>\", \"#<collection-name>\", or \"self/<dictionary>\", with unique elements",
390 inner_type = "OfferToRef",
391 min_length = 1,
392 unique_items = true
393)]
394pub struct OneOrManyOfferToRefs;
395
396#[derive(OneOrMany, Debug, Clone)]
398#[one_or_many(
399 expected = "one or an array of \"parent\", \"framework\", \"self\", \"#<child-name>\", \"#<collection-name>\", or a dictionary path",
400 inner_type = "OfferFromRef",
401 min_length = 1,
402 unique_items = true
403)]
404pub struct OneOrManyOfferFromRefs;
405
406#[derive(Debug, Clone, Serialize)]
407pub struct ContextOffer {
408 #[serde(skip)]
409 pub origin: Arc<std::path::Path>,
410 #[serde(skip_serializing_if = "Option::is_none")]
411 pub service: Option<ContextSpanned<OneOrMany<Name>>>,
412 #[serde(skip_serializing_if = "Option::is_none")]
413 pub protocol: Option<ContextSpanned<OneOrMany<Name>>>,
414 #[serde(skip_serializing_if = "Option::is_none")]
415 pub directory: Option<ContextSpanned<OneOrMany<Name>>>,
416 #[serde(skip_serializing_if = "Option::is_none")]
417 pub runner: Option<ContextSpanned<OneOrMany<Name>>>,
418 #[serde(skip_serializing_if = "Option::is_none")]
419 pub resolver: Option<ContextSpanned<OneOrMany<Name>>>,
420 #[serde(skip_serializing_if = "Option::is_none")]
421 pub storage: Option<ContextSpanned<OneOrMany<Name>>>,
422 #[serde(skip_serializing_if = "Option::is_none")]
423 pub dictionary: Option<ContextSpanned<OneOrMany<Name>>>,
424 #[serde(skip_serializing_if = "Option::is_none")]
425 pub config: Option<ContextSpanned<OneOrMany<Name>>>,
426 pub from: ContextSpanned<OneOrMany<OfferFromRef>>,
427 pub to: ContextSpanned<OneOrMany<OfferToRef>>,
428 #[serde(skip_serializing_if = "Option::is_none")]
429 pub r#as: Option<ContextSpanned<Name>>,
430 #[serde(skip_serializing_if = "Option::is_none")]
431 pub dependency: Option<ContextSpanned<DependencyType>>,
432 #[serde(skip_serializing_if = "Option::is_none")]
433 pub rights: Option<ContextSpanned<Rights>>,
434 #[serde(skip_serializing_if = "Option::is_none")]
435 pub subdir: Option<ContextSpanned<RelativePath>>,
436 #[serde(skip_serializing_if = "Option::is_none")]
437 pub event_stream: Option<ContextSpanned<OneOrMany<Name>>>,
438 #[serde(skip_serializing_if = "Option::is_none")]
439 pub scope: Option<ContextSpanned<OneOrMany<EventScope>>>,
440 #[serde(skip_serializing_if = "Option::is_none")]
441 pub availability: Option<ContextSpanned<Availability>>,
442 #[serde(skip_serializing_if = "Option::is_none")]
443 pub source_availability: Option<ContextSpanned<SourceAvailability>>,
444 #[serde(skip_serializing_if = "Option::is_none")]
445 pub target_availability: Option<ContextSpanned<TargetAvailability>>,
446}
447
448impl ContextCapabilityClause for ContextOffer {
449 fn service(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
450 option_one_or_many_as_ref_context(&self.service)
451 }
452 fn protocol(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
453 option_one_or_many_as_ref_context(&self.protocol)
454 }
455 fn directory(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
456 option_one_or_many_as_ref_context(&self.directory)
457 }
458 fn storage(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
459 option_one_or_many_as_ref_context(&self.storage)
460 }
461 fn runner(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
462 option_one_or_many_as_ref_context(&self.runner)
463 }
464 fn resolver(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
465 option_one_or_many_as_ref_context(&self.resolver)
466 }
467 fn event_stream(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
468 option_one_or_many_as_ref_context(&self.event_stream)
469 }
470 fn dictionary(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
471 option_one_or_many_as_ref_context(&self.dictionary)
472 }
473 fn config(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
474 option_one_or_many_as_ref_context(&self.config)
475 }
476
477 fn decl_type(&self) -> &'static str {
478 "offer"
479 }
480 fn supported(&self) -> &[&'static str] {
481 &[
482 "service",
483 "protocol",
484 "directory",
485 "storage",
486 "event_stream",
487 "runner",
488 "resolver",
489 "config",
490 ]
491 }
492 fn are_many_names_allowed(&self) -> bool {
493 [
494 "service",
495 "protocol",
496 "directory",
497 "storage",
498 "runner",
499 "resolver",
500 "event_stream",
501 "config",
502 ]
503 .contains(&self.capability_type(None).unwrap())
504 }
505
506 fn set_service(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
507 self.service = o;
508 }
509
510 fn set_protocol(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
511 self.protocol = o;
512 }
513
514 fn set_directory(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
515 self.directory = o;
516 }
517
518 fn set_storage(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
519 self.storage = o;
520 }
521
522 fn set_runner(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
523 self.runner = o;
524 }
525 fn set_resolver(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
526 self.resolver = o;
527 }
528 fn set_event_stream(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
529 self.event_stream = o;
530 }
531 fn set_dictionary(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
532 self.dictionary = o;
533 }
534 fn set_config(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
535 self.config = o;
536 }
537
538 fn origin(&self) -> &Arc<std::path::Path> {
539 &self.origin
540 }
541
542 fn availability(&self) -> Option<ContextSpanned<Availability>> {
543 self.availability.clone()
544 }
545 fn set_availability(&mut self, a: Option<ContextSpanned<Availability>>) {
546 self.availability = a;
547 }
548}
549
550impl CanonicalizeContext for ContextOffer {
551 fn canonicalize_context(&mut self) {
552 if let Some(service) = &mut self.service {
554 service.value.canonicalize_context();
555 } else if let Some(protocol) = &mut self.protocol {
556 protocol.value.canonicalize_context();
557 } else if let Some(directory) = &mut self.directory {
558 directory.value.canonicalize_context();
559 } else if let Some(runner) = &mut self.runner {
560 runner.value.canonicalize_context();
561 } else if let Some(resolver) = &mut self.resolver {
562 resolver.value.canonicalize_context();
563 } else if let Some(storage) = &mut self.storage {
564 storage.value.canonicalize_context();
565 } else if let Some(event_stream) = &mut self.event_stream {
566 event_stream.value.canonicalize_context();
567 if let Some(scope) = &mut self.scope {
568 scope.value.canonicalize_context();
569 }
570 }
571 }
572}
573
574impl PartialEq for ContextOffer {
575 fn eq(&self, other: &Self) -> bool {
576 macro_rules! cmp {
577 ($field:ident) => {
578 match (&self.$field, &other.$field) {
579 (Some(a), Some(b)) => a.value == b.value,
580 (None, None) => true,
581 _ => false,
582 }
583 };
584 }
585
586 cmp!(service)
587 && cmp!(protocol)
588 && cmp!(directory)
589 && cmp!(runner)
590 && cmp!(resolver)
591 && cmp!(storage)
592 && cmp!(dictionary)
593 && cmp!(config)
594 && self.from.value == other.from.value
595 && self.to.value == other.to.value
596 && cmp!(r#as)
597 && cmp!(dependency)
598 && cmp!(rights)
599 && cmp!(subdir)
600 && cmp!(event_stream)
601 && cmp!(scope)
602 && cmp!(availability)
603 && cmp!(source_availability)
604 && cmp!(target_availability)
605 }
606}
607
608impl Eq for ContextOffer {}
609
610impl Default for ContextOffer {
611 fn default() -> Self {
612 let synthetic_origin: Arc<std::path::Path> = Arc::from(std::path::Path::new("synthetic"));
613
614 Self {
615 from: ContextSpanned {
616 value: OneOrMany::One(OfferFromRef::Self_),
617 origin: synthetic_origin.clone(),
618 },
619 to: ContextSpanned { value: OneOrMany::Many(vec![]), origin: synthetic_origin.clone() },
620 origin: synthetic_origin,
621 service: None,
622 protocol: None,
623 directory: None,
624 storage: None,
625 runner: None,
626 resolver: None,
627 dictionary: None,
628 config: None,
629 r#as: None,
630 rights: None,
631 subdir: None,
632 dependency: None,
633 event_stream: None,
634 scope: None,
635 availability: None,
636 source_availability: None,
637 target_availability: None,
638 }
639 }
640}
641
642impl ContextPathClause for ContextOffer {
643 fn path(&self) -> Option<&ContextSpanned<Path>> {
644 None
645 }
646}
647
648impl AsClauseContext for ContextOffer {
649 fn r#as(&self) -> Option<ContextSpanned<&BorrowedName>> {
650 self.r#as.as_ref().map(|spanned_name| ContextSpanned {
651 value: spanned_name.value.as_ref(),
652 origin: spanned_name.origin.clone(),
653 })
654 }
655}
656
657impl FromClauseContext for ContextOffer {
658 fn from_(&self) -> ContextSpanned<OneOrMany<AnyRef<'_>>> {
659 one_or_many_from_context(&self.from)
660 }
661}
662
663impl Hydrate for Offer {
664 type Output = ContextOffer;
665
666 fn hydrate(self, file: &Arc<std::path::Path>) -> Result<Self::Output, Error> {
667 Ok(ContextOffer {
668 origin: file.clone(),
669 service: hydrate_opt_simple(self.service, file),
670 protocol: hydrate_opt_simple(self.protocol, file),
671 directory: hydrate_opt_simple(self.directory, file),
672 runner: hydrate_opt_simple(self.runner, file),
673 resolver: hydrate_opt_simple(self.resolver, file),
674 storage: hydrate_opt_simple(self.storage, file),
675 dictionary: hydrate_opt_simple(self.dictionary, file),
676 config: hydrate_opt_simple(self.config, file),
677 from: hydrate_simple(self.from, file),
678 to: hydrate_simple(self.to, file),
679 r#as: hydrate_opt_simple(self.r#as, file),
680 dependency: hydrate_opt_simple(self.dependency, file),
681 rights: hydrate_opt_simple(self.rights, file),
682 subdir: hydrate_opt_simple(self.subdir, file),
683 event_stream: hydrate_opt_simple(self.event_stream, file),
684 scope: hydrate_opt_simple(self.scope, file),
685 availability: hydrate_opt_simple(self.availability, file),
686 source_availability: hydrate_opt_simple(self.source_availability, file),
687 target_availability: hydrate_opt_simple(self.target_availability, file),
688 })
689 }
690}
691
692pub fn offer_to_all_from_context_offer(
693 value: &ContextOffer,
694) -> impl Iterator<Item = OfferToAllCapability<'_>> {
695 if let Some(protocol) = &value.protocol {
696 Either::Left(
697 protocol.value.iter().map(|protocol| OfferToAllCapability::Protocol(protocol.as_str())),
698 )
699 } else if let Some(dictionary) = &value.dictionary {
700 Either::Right(
701 dictionary
702 .value
703 .iter()
704 .map(|dictionary| OfferToAllCapability::Dictionary(dictionary.as_str())),
705 )
706 } else {
707 panic!("Expected a dictionary or a protocol");
708 }
709}
710
711pub fn offer_to_all_would_duplicate_context(
716 offer_to_all: &ContextSpanned<ContextOffer>,
717 specific_offer: &ContextSpanned<ContextOffer>,
718 target: &cm_types::BorrowedName,
719) -> Result<bool, Error> {
720 assert!(offer_to_all.value.protocol.is_some() || offer_to_all.value.dictionary.is_some());
722
723 if CapabilityId::from_context_offer_expose(specific_offer).iter().flatten().all(
726 |specific_offer_cap_id| {
727 CapabilityId::from_context_offer_expose(offer_to_all)
728 .iter()
729 .flatten()
730 .all(|offer_to_all_cap_id| offer_to_all_cap_id.0 != specific_offer_cap_id.0)
731 },
732 ) {
733 return Ok(false);
734 }
735
736 let to_field_matches = specific_offer.value.to.value.iter().any(
737 |specific_offer_to| matches!(specific_offer_to, OfferToRef::Named(c) if *c == *target),
738 );
739
740 if !to_field_matches {
741 return Ok(false);
742 }
743
744 if offer_to_all.value.from != specific_offer.value.from {
745 return Err(Error::validate_contexts(
746 offer_to_all_and_component_diff_sources_message(
747 offer_to_all_from_context_offer(&offer_to_all.value),
748 target.as_str(),
749 ),
750 vec![offer_to_all.origin.clone(), specific_offer.origin.clone()],
751 ));
752 }
753
754 if offer_to_all_from_context_offer(&offer_to_all.value).all(|to_all_protocol| {
756 offer_to_all_from_context_offer(&specific_offer.value)
757 .all(|to_specific_protocol| to_all_protocol != to_specific_protocol)
758 }) {
759 return Err(Error::validate_contexts(
760 offer_to_all_and_component_diff_capabilities_message(
761 offer_to_all_from_context_offer(&offer_to_all.value),
762 target.as_str(),
763 ),
764 vec![offer_to_all.origin.clone(), specific_offer.origin.clone()],
765 ));
766 }
767
768 Ok(true)
769}
770
771impl ContextOffer {
772 pub fn empty(from: OneOrMany<OfferFromRef>, to: OneOrMany<OfferToRef>) -> Self {
773 Self {
774 origin: std::sync::Arc::from(std::path::Path::new("programmatic_manifest.cml")),
775 from: synthetic_span(from),
776 to: synthetic_span(to),
777 protocol: None,
778 r#as: None,
779 service: None,
780 directory: None,
781 config: None,
782 runner: None,
783 resolver: None,
784 storage: None,
785 dictionary: None,
786 dependency: None,
787 rights: None,
788 subdir: None,
789 event_stream: None,
790 scope: None,
791 availability: None,
792 source_availability: None,
793 target_availability: None,
794 }
795 }
796}
797
798#[cfg(test)]
799pub fn create_offer(
800 protocol_name: &str,
801 from: OneOrMany<OfferFromRef>,
802 to: OneOrMany<OfferToRef>,
803) -> ContextSpanned<ContextOffer> {
804 let protocol = Some(OneOrMany::One(Name::from_str(protocol_name).unwrap())).map(synthetic_span);
805
806 let offer = ContextOffer { protocol, ..ContextOffer::empty(from, to) };
807
808 synthetic_span(offer)
809}
810
811#[cfg(test)]
812mod tests {
813 use super::*;
814
815 #[test]
816 fn test_offer_would_duplicate() {
817 let offer = create_offer(
818 "fuchsia.logger.LegacyLog",
819 OneOrMany::One(OfferFromRef::Parent {}),
820 OneOrMany::One(OfferToRef::Named(Name::from_str("something").unwrap())),
821 );
822
823 let offer_to_all = create_offer(
824 "fuchsia.logger.LogSink",
825 OneOrMany::One(OfferFromRef::Parent {}),
826 OneOrMany::One(OfferToRef::All),
827 );
828
829 assert!(
831 !offer_to_all_would_duplicate_context(
832 &offer_to_all,
833 &offer,
834 &Name::from_str("something").unwrap()
835 )
836 .unwrap()
837 );
838
839 let offer = create_offer(
840 "fuchsia.logger.LogSink",
841 OneOrMany::One(OfferFromRef::Parent {}),
842 OneOrMany::One(OfferToRef::Named(Name::from_str("not-something").unwrap())),
843 );
844
845 assert!(
847 !offer_to_all_would_duplicate_context(
848 &offer_to_all,
849 &offer,
850 &Name::from_str("something").unwrap()
851 )
852 .unwrap()
853 );
854
855 let mut offer = create_offer(
856 "fuchsia.logger.LogSink",
857 OneOrMany::One(OfferFromRef::Parent {}),
858 OneOrMany::One(OfferToRef::Named(Name::from_str("something").unwrap())),
859 );
860
861 offer.value.r#as = Some(synthetic_span(Name::from_str("FakeLog").unwrap()));
862
863 assert!(
865 !offer_to_all_would_duplicate_context(
866 &offer_to_all,
867 &offer,
868 &Name::from_str("something").unwrap()
869 )
870 .unwrap()
871 );
872
873 let offer = create_offer(
874 "fuchsia.logger.LogSink",
875 OneOrMany::One(OfferFromRef::Parent {}),
876 OneOrMany::One(OfferToRef::Named(Name::from_str("something").unwrap())),
877 );
878
879 assert!(
880 offer_to_all_would_duplicate_context(
881 &offer_to_all,
882 &offer,
883 &Name::from_str("something").unwrap()
884 )
885 .unwrap()
886 );
887
888 let offer = create_offer(
889 "fuchsia.logger.LogSink",
890 OneOrMany::One(OfferFromRef::Named(Name::from_str("other").unwrap())),
891 OneOrMany::One(OfferToRef::Named(Name::from_str("something").unwrap())),
892 );
893
894 assert!(
895 offer_to_all_would_duplicate_context(
896 &offer_to_all,
897 &offer,
898 &Name::from_str("something").unwrap()
899 )
900 .is_err()
901 );
902 }
903}