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