1use crate::types::common::*;
6use crate::{
7 AnyRef, AsClause, AsClauseContext, Canonicalize, CanonicalizeContext, CapabilityClause,
8 CapabilityId, DictionaryRef, Error, EventScope, FromClause, FromClauseContext, PathClause,
9 SourceAvailability,
10};
11
12use crate::one_or_many::{
13 OneOrMany, one_or_many_from_context, one_or_many_from_impl, option_one_or_many_as_ref,
14};
15use crate::types::right::{Rights, RightsClause};
16pub use cm_types::{
17 Availability, BorrowedName, BoundedName, DependencyType, HandleType, Name, OnTerminate,
18 ParseError, Path, RelativePath, StartupMode, Url,
19};
20use cml_macro::{OneOrMany, Reference};
21use itertools::Either;
22use reference_doc::ReferenceDoc;
23use serde::{Deserialize, Serialize};
24
25use std::fmt;
26use std::fmt::Write;
27use std::path::PathBuf;
28#[allow(unused)] use std::str::FromStr;
30use std::sync::Arc;
31
32#[derive(Deserialize, Debug, PartialEq, Clone, ReferenceDoc, Serialize)]
85#[serde(deny_unknown_fields)]
86#[reference_doc(fields_as = "list", top_level_doc_after_fields)]
87pub struct Offer {
88 #[serde(skip_serializing_if = "Option::is_none")]
90 pub service: Option<OneOrMany<Name>>,
91
92 #[serde(skip_serializing_if = "Option::is_none")]
94 pub protocol: Option<OneOrMany<Name>>,
95
96 #[serde(skip_serializing_if = "Option::is_none")]
98 pub directory: Option<OneOrMany<Name>>,
99
100 #[serde(skip_serializing_if = "Option::is_none")]
102 pub runner: Option<OneOrMany<Name>>,
103
104 #[serde(skip_serializing_if = "Option::is_none")]
106 pub resolver: Option<OneOrMany<Name>>,
107
108 #[serde(skip_serializing_if = "Option::is_none")]
110 pub storage: Option<OneOrMany<Name>>,
111
112 #[serde(skip_serializing_if = "Option::is_none")]
114 pub dictionary: Option<OneOrMany<Name>>,
115
116 #[serde(skip_serializing_if = "Option::is_none")]
118 pub config: Option<OneOrMany<Name>>,
119
120 pub from: OneOrMany<OfferFromRef>,
132
133 pub to: OneOrMany<OfferToRef>,
138
139 #[serde(skip_serializing_if = "Option::is_none")]
143 pub r#as: Option<Name>,
144
145 #[serde(skip_serializing_if = "Option::is_none")]
155 pub dependency: Option<DependencyType>,
156
157 #[serde(skip_serializing_if = "Option::is_none")]
160 #[reference_doc(json_type = "array of string")]
161 pub rights: Option<Rights>,
162
163 #[serde(skip_serializing_if = "Option::is_none")]
166 pub subdir: Option<RelativePath>,
167
168 #[serde(skip_serializing_if = "Option::is_none")]
170 pub event_stream: Option<OneOrMany<Name>>,
171
172 #[serde(skip_serializing_if = "Option::is_none")]
175 pub scope: Option<OneOrMany<EventScope>>,
176
177 #[serde(skip_serializing_if = "Option::is_none")]
194 pub availability: Option<Availability>,
195
196 #[serde(skip_serializing_if = "Option::is_none")]
201 pub source_availability: Option<SourceAvailability>,
202
203 #[serde(skip_serializing_if = "Option::is_none")]
209 pub target_availability: Option<TargetAvailability>,
210}
211
212impl Offer {
213 pub fn empty(from: OneOrMany<OfferFromRef>, to: OneOrMany<OfferToRef>) -> Offer {
216 Self {
217 protocol: None,
218 from,
219 to,
220 r#as: None,
221 service: None,
222 directory: None,
223 config: None,
224 runner: None,
225 resolver: None,
226 storage: None,
227 dictionary: None,
228 dependency: None,
229 rights: None,
230 subdir: None,
231 event_stream: None,
232 scope: None,
233 availability: None,
234 source_availability: None,
235 target_availability: None,
236 }
237 }
238}
239
240impl Default for Offer {
241 fn default() -> Self {
242 Self {
243 from: OneOrMany::One(OfferFromRef::Self_),
244 to: OneOrMany::Many(vec![]),
245 service: None,
246 protocol: None,
247 directory: None,
248 storage: None,
249 runner: None,
250 resolver: None,
251 dictionary: None,
252 config: None,
253 r#as: None,
254 rights: None,
255 subdir: None,
256 dependency: None,
257 event_stream: None,
258 scope: None,
259 availability: None,
260 source_availability: None,
261 target_availability: None,
262 }
263 }
264}
265
266impl FromClause for Offer {
267 fn from_(&self) -> OneOrMany<AnyRef<'_>> {
268 one_or_many_from_impl(&self.from)
269 }
270}
271
272impl Canonicalize for Offer {
273 fn canonicalize(&mut self) {
274 if let Some(service) = &mut self.service {
276 service.canonicalize();
277 } else if let Some(protocol) = &mut self.protocol {
278 protocol.canonicalize();
279 } else if let Some(directory) = &mut self.directory {
280 directory.canonicalize();
281 } else if let Some(runner) = &mut self.runner {
282 runner.canonicalize();
283 } else if let Some(resolver) = &mut self.resolver {
284 resolver.canonicalize();
285 } else if let Some(storage) = &mut self.storage {
286 storage.canonicalize();
287 } else if let Some(event_stream) = &mut self.event_stream {
288 event_stream.canonicalize();
289 if let Some(scope) = &mut self.scope {
290 scope.canonicalize();
291 }
292 }
293 }
294}
295
296impl CapabilityClause for Offer {
297 fn service(&self) -> Option<OneOrMany<&BorrowedName>> {
298 option_one_or_many_as_ref(&self.service)
299 }
300 fn protocol(&self) -> Option<OneOrMany<&BorrowedName>> {
301 option_one_or_many_as_ref(&self.protocol)
302 }
303 fn directory(&self) -> Option<OneOrMany<&BorrowedName>> {
304 option_one_or_many_as_ref(&self.directory)
305 }
306 fn storage(&self) -> Option<OneOrMany<&BorrowedName>> {
307 option_one_or_many_as_ref(&self.storage)
308 }
309 fn runner(&self) -> Option<OneOrMany<&BorrowedName>> {
310 option_one_or_many_as_ref(&self.runner)
311 }
312 fn resolver(&self) -> Option<OneOrMany<&BorrowedName>> {
313 option_one_or_many_as_ref(&self.resolver)
314 }
315 fn event_stream(&self) -> Option<OneOrMany<&BorrowedName>> {
316 option_one_or_many_as_ref(&self.event_stream)
317 }
318 fn dictionary(&self) -> Option<OneOrMany<&BorrowedName>> {
319 option_one_or_many_as_ref(&self.dictionary)
320 }
321 fn config(&self) -> Option<OneOrMany<&BorrowedName>> {
322 option_one_or_many_as_ref(&self.config)
323 }
324
325 fn set_service(&mut self, o: Option<OneOrMany<Name>>) {
326 self.service = o;
327 }
328 fn set_protocol(&mut self, o: Option<OneOrMany<Name>>) {
329 self.protocol = o;
330 }
331 fn set_directory(&mut self, o: Option<OneOrMany<Name>>) {
332 self.directory = o;
333 }
334 fn set_storage(&mut self, o: Option<OneOrMany<Name>>) {
335 self.storage = o;
336 }
337 fn set_runner(&mut self, o: Option<OneOrMany<Name>>) {
338 self.runner = o;
339 }
340 fn set_resolver(&mut self, o: Option<OneOrMany<Name>>) {
341 self.resolver = o;
342 }
343 fn set_event_stream(&mut self, o: Option<OneOrMany<Name>>) {
344 self.event_stream = o;
345 }
346 fn set_dictionary(&mut self, o: Option<OneOrMany<Name>>) {
347 self.dictionary = o;
348 }
349 fn set_config(&mut self, o: Option<OneOrMany<Name>>) {
350 self.config = o
351 }
352
353 fn availability(&self) -> Option<Availability> {
354 self.availability
355 }
356 fn set_availability(&mut self, a: Option<Availability>) {
357 self.availability = a;
358 }
359
360 fn decl_type(&self) -> &'static str {
361 "offer"
362 }
363 fn supported(&self) -> &[&'static str] {
364 &[
365 "service",
366 "protocol",
367 "directory",
368 "storage",
369 "runner",
370 "resolver",
371 "event_stream",
372 "config",
373 ]
374 }
375 fn are_many_names_allowed(&self) -> bool {
376 [
377 "service",
378 "protocol",
379 "directory",
380 "storage",
381 "runner",
382 "resolver",
383 "event_stream",
384 "config",
385 ]
386 .contains(&self.capability_type().unwrap())
387 }
388}
389
390impl PathClause for Offer {
391 fn path(&self) -> Option<&Path> {
392 None
393 }
394}
395
396impl RightsClause for Offer {
397 fn rights(&self) -> Option<&Rights> {
398 self.rights.as_ref()
399 }
400}
401
402impl AsClause for Offer {
403 fn r#as(&self) -> Option<&BorrowedName> {
404 self.r#as.as_ref().map(Name::as_ref)
405 }
406}
407
408#[derive(Debug, Deserialize, PartialEq, Eq, Hash, Clone, Serialize)]
410#[serde(rename_all = "snake_case")]
411pub enum TargetAvailability {
412 Required,
413 Unknown,
414}
415
416#[derive(PartialEq, Clone)]
417pub enum OfferToAllCapability<'a> {
418 Dictionary(&'a str),
419 Protocol(&'a str),
420}
421
422impl<'a> OfferToAllCapability<'a> {
423 pub fn name(&self) -> &'a str {
424 match self {
425 OfferToAllCapability::Dictionary(name) => name,
426 OfferToAllCapability::Protocol(name) => name,
427 }
428 }
429
430 pub fn offer_type(&self) -> &'static str {
431 match self {
432 OfferToAllCapability::Dictionary(_) => "Dictionary",
433 OfferToAllCapability::Protocol(_) => "Protocol",
434 }
435 }
436
437 pub fn offer_type_plural(&self) -> &'static str {
438 match self {
439 OfferToAllCapability::Dictionary(_) => "dictionaries",
440 OfferToAllCapability::Protocol(_) => "protocols",
441 }
442 }
443}
444
445pub fn offer_to_all_from_offer(value: &Offer) -> impl Iterator<Item = OfferToAllCapability<'_>> {
446 if let Some(protocol) = &value.protocol {
447 Either::Left(
448 protocol.iter().map(|protocol| OfferToAllCapability::Protocol(protocol.as_str())),
449 )
450 } else if let Some(dictionary) = &value.dictionary {
451 Either::Right(
452 dictionary
453 .iter()
454 .map(|dictionary| OfferToAllCapability::Dictionary(dictionary.as_str())),
455 )
456 } else {
457 panic!("Expected a dictionary or a protocol");
458 }
459}
460pub fn offer_to_all_and_component_diff_sources_message<'a>(
461 capability: impl Iterator<Item = OfferToAllCapability<'a>>,
462 component: &str,
463) -> String {
464 let mut output = String::new();
465 let mut capability = capability.peekable();
466 write!(&mut output, "{} ", capability.peek().unwrap().offer_type()).unwrap();
467 for (i, capability) in capability.enumerate() {
468 if i > 0 {
469 write!(&mut output, ", ").unwrap();
470 }
471 write!(&mut output, "{}", capability.name()).unwrap();
472 }
473 write!(
474 &mut output,
475 r#" is offered to both "all" and child component "{}" with different sources"#,
476 component
477 )
478 .unwrap();
479 output
480}
481
482pub fn offer_to_all_and_component_diff_capabilities_message<'a>(
483 capability: impl Iterator<Item = OfferToAllCapability<'a>>,
484 component: &str,
485) -> String {
486 let mut output = String::new();
487 let mut capability_peek = capability.peekable();
488
489 let first_offer_to_all = capability_peek.peek().unwrap().clone();
493 write!(&mut output, "{} ", first_offer_to_all.offer_type()).unwrap();
494 for (i, capability) in capability_peek.enumerate() {
495 if i > 0 {
496 write!(&mut output, ", ").unwrap();
497 }
498 write!(&mut output, "{}", capability.name()).unwrap();
499 }
500 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();
501 output
502}
503
504pub fn offer_to_all_would_duplicate(
509 offer_to_all: &Offer,
510 specific_offer: &Offer,
511 target: &cm_types::BorrowedName,
512) -> Result<bool, Error> {
513 assert!(offer_to_all.protocol.is_some() || offer_to_all.dictionary.is_some());
515
516 if CapabilityId::from_offer_expose(specific_offer).iter().flatten().all(
519 |specific_offer_cap_id| {
520 CapabilityId::from_offer_expose(offer_to_all)
521 .iter()
522 .flatten()
523 .all(|offer_to_all_cap_id| offer_to_all_cap_id != specific_offer_cap_id)
524 },
525 ) {
526 return Ok(false);
527 }
528
529 let to_field_matches = specific_offer.to.iter().any(
530 |specific_offer_to| matches!(specific_offer_to, OfferToRef::Named(c) if **c == *target),
531 );
532
533 if !to_field_matches {
534 return Ok(false);
535 }
536
537 if offer_to_all.from != specific_offer.from {
538 return Err(Error::validate(offer_to_all_and_component_diff_sources_message(
539 offer_to_all_from_offer(offer_to_all),
540 target.as_str(),
541 )));
542 }
543
544 if offer_to_all_from_offer(offer_to_all).all(|to_all_protocol| {
546 offer_to_all_from_offer(specific_offer)
547 .all(|to_specific_protocol| to_all_protocol != to_specific_protocol)
548 }) {
549 return Err(Error::validate(offer_to_all_and_component_diff_capabilities_message(
550 offer_to_all_from_offer(offer_to_all),
551 target.as_str(),
552 )));
553 }
554
555 Ok(true)
556}
557
558#[derive(Debug, PartialEq, Eq, Hash, Clone, Reference)]
560#[reference(
561 expected = "\"parent\", \"framework\", \"self\", \"void\", \"#<child-name>\", or a dictionary path"
562)]
563pub enum OfferFromRef {
564 Named(Name),
566 Parent,
568 Framework,
570 Self_,
572 Void,
574 Dictionary(DictionaryRef),
576}
577
578impl OfferFromRef {
579 pub fn is_named(&self) -> bool {
580 match self {
581 OfferFromRef::Named(_) => true,
582 _ => false,
583 }
584 }
585}
586
587#[derive(Debug, PartialEq, Eq, Hash, Clone, Reference)]
589#[reference(expected = "\"#<child-name>\", \"#<collection-name>\", or \"self/<dictionary>\"")]
590pub enum OfferToRef {
591 Named(Name),
593
594 All,
596
597 OwnDictionary(Name),
599}
600
601#[derive(OneOrMany, Debug, Clone)]
603#[one_or_many(
604 expected = "one or an array of \"#<child-name>\", \"#<collection-name>\", or \"self/<dictionary>\", with unique elements",
605 inner_type = "OfferToRef",
606 min_length = 1,
607 unique_items = true
608)]
609pub struct OneOrManyOfferToRefs;
610
611#[derive(OneOrMany, Debug, Clone)]
613#[one_or_many(
614 expected = "one or an array of \"parent\", \"framework\", \"self\", \"#<child-name>\", \"#<collection-name>\", or a dictionary path",
615 inner_type = "OfferFromRef",
616 min_length = 1,
617 unique_items = true
618)]
619pub struct OneOrManyOfferFromRefs;
620
621#[derive(Debug, Clone, Serialize)]
622pub struct ContextOffer {
623 #[serde(skip)]
624 pub origin: Arc<PathBuf>,
625 #[serde(skip_serializing_if = "Option::is_none")]
626 pub service: Option<ContextSpanned<OneOrMany<Name>>>,
627 #[serde(skip_serializing_if = "Option::is_none")]
628 pub protocol: Option<ContextSpanned<OneOrMany<Name>>>,
629 #[serde(skip_serializing_if = "Option::is_none")]
630 pub directory: Option<ContextSpanned<OneOrMany<Name>>>,
631 #[serde(skip_serializing_if = "Option::is_none")]
632 pub runner: Option<ContextSpanned<OneOrMany<Name>>>,
633 #[serde(skip_serializing_if = "Option::is_none")]
634 pub resolver: Option<ContextSpanned<OneOrMany<Name>>>,
635 #[serde(skip_serializing_if = "Option::is_none")]
636 pub storage: Option<ContextSpanned<OneOrMany<Name>>>,
637 #[serde(skip_serializing_if = "Option::is_none")]
638 pub dictionary: Option<ContextSpanned<OneOrMany<Name>>>,
639 #[serde(skip_serializing_if = "Option::is_none")]
640 pub config: Option<ContextSpanned<OneOrMany<Name>>>,
641 pub from: ContextSpanned<OneOrMany<OfferFromRef>>,
642 pub to: ContextSpanned<OneOrMany<OfferToRef>>,
643 #[serde(skip_serializing_if = "Option::is_none")]
644 pub r#as: Option<ContextSpanned<Name>>,
645 #[serde(skip_serializing_if = "Option::is_none")]
646 pub dependency: Option<ContextSpanned<DependencyType>>,
647 #[serde(skip_serializing_if = "Option::is_none")]
648 pub rights: Option<ContextSpanned<Rights>>,
649 #[serde(skip_serializing_if = "Option::is_none")]
650 pub subdir: Option<ContextSpanned<RelativePath>>,
651 #[serde(skip_serializing_if = "Option::is_none")]
652 pub event_stream: Option<ContextSpanned<OneOrMany<Name>>>,
653 #[serde(skip_serializing_if = "Option::is_none")]
654 pub scope: Option<ContextSpanned<OneOrMany<EventScope>>>,
655 #[serde(skip_serializing_if = "Option::is_none")]
656 pub availability: Option<ContextSpanned<Availability>>,
657 #[serde(skip_serializing_if = "Option::is_none")]
658 pub source_availability: Option<ContextSpanned<SourceAvailability>>,
659 #[serde(skip_serializing_if = "Option::is_none")]
660 pub target_availability: Option<ContextSpanned<TargetAvailability>>,
661}
662
663impl ContextCapabilityClause for ContextOffer {
664 fn service(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
665 option_one_or_many_as_ref_context(&self.service)
666 }
667 fn protocol(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
668 option_one_or_many_as_ref_context(&self.protocol)
669 }
670 fn directory(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
671 option_one_or_many_as_ref_context(&self.directory)
672 }
673 fn storage(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
674 option_one_or_many_as_ref_context(&self.storage)
675 }
676 fn runner(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
677 option_one_or_many_as_ref_context(&self.runner)
678 }
679 fn resolver(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
680 option_one_or_many_as_ref_context(&self.resolver)
681 }
682 fn event_stream(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
683 option_one_or_many_as_ref_context(&self.event_stream)
684 }
685 fn dictionary(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
686 option_one_or_many_as_ref_context(&self.dictionary)
687 }
688 fn config(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
689 option_one_or_many_as_ref_context(&self.config)
690 }
691
692 fn decl_type(&self) -> &'static str {
693 "offer"
694 }
695 fn supported(&self) -> &[&'static str] {
696 &[
697 "service",
698 "protocol",
699 "directory",
700 "storage",
701 "event_stream",
702 "runner",
703 "resolver",
704 "config",
705 ]
706 }
707 fn are_many_names_allowed(&self) -> bool {
708 [
709 "service",
710 "protocol",
711 "directory",
712 "storage",
713 "runner",
714 "resolver",
715 "event_stream",
716 "config",
717 ]
718 .contains(&self.capability_type(None).unwrap())
719 }
720
721 fn set_service(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
722 self.service = o;
723 }
724
725 fn set_protocol(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
726 self.protocol = o;
727 }
728
729 fn set_directory(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
730 self.directory = o;
731 }
732
733 fn set_storage(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
734 self.storage = o;
735 }
736
737 fn set_runner(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
738 self.runner = o;
739 }
740 fn set_resolver(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
741 self.resolver = o;
742 }
743 fn set_event_stream(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
744 self.event_stream = o;
745 }
746 fn set_dictionary(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
747 self.dictionary = o;
748 }
749 fn set_config(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
750 self.config = o;
751 }
752
753 fn origin(&self) -> &Arc<PathBuf> {
754 &self.origin
755 }
756
757 fn availability(&self) -> Option<ContextSpanned<Availability>> {
758 self.availability.clone()
759 }
760 fn set_availability(&mut self, a: Option<ContextSpanned<Availability>>) {
761 self.availability = a;
762 }
763}
764
765impl CanonicalizeContext for ContextOffer {
766 fn canonicalize_context(&mut self) {
767 if let Some(service) = &mut self.service {
769 service.value.canonicalize_context();
770 } else if let Some(protocol) = &mut self.protocol {
771 protocol.value.canonicalize_context();
772 } else if let Some(directory) = &mut self.directory {
773 directory.value.canonicalize_context();
774 } else if let Some(runner) = &mut self.runner {
775 runner.value.canonicalize_context();
776 } else if let Some(resolver) = &mut self.resolver {
777 resolver.value.canonicalize_context();
778 } else if let Some(storage) = &mut self.storage {
779 storage.value.canonicalize_context();
780 } else if let Some(event_stream) = &mut self.event_stream {
781 event_stream.value.canonicalize_context();
782 if let Some(scope) = &mut self.scope {
783 scope.value.canonicalize_context();
784 }
785 }
786 }
787}
788
789impl PartialEq for ContextOffer {
790 fn eq(&self, other: &Self) -> bool {
791 macro_rules! cmp {
792 ($field:ident) => {
793 match (&self.$field, &other.$field) {
794 (Some(a), Some(b)) => a.value == b.value,
795 (None, None) => true,
796 _ => false,
797 }
798 };
799 }
800
801 cmp!(service)
802 && cmp!(protocol)
803 && cmp!(directory)
804 && cmp!(runner)
805 && cmp!(resolver)
806 && cmp!(storage)
807 && cmp!(dictionary)
808 && cmp!(config)
809 && self.from.value == other.from.value
810 && self.to.value == other.to.value
811 && cmp!(r#as)
812 && cmp!(dependency)
813 && cmp!(rights)
814 && cmp!(subdir)
815 && cmp!(event_stream)
816 && cmp!(scope)
817 && cmp!(availability)
818 && cmp!(source_availability)
819 && cmp!(target_availability)
820 }
821}
822
823impl Eq for ContextOffer {}
824
825impl Default for ContextOffer {
826 fn default() -> Self {
827 let synthetic_origin = Arc::new(PathBuf::from("synthetic"));
828
829 Self {
830 from: ContextSpanned {
831 value: OneOrMany::One(OfferFromRef::Self_),
832 origin: synthetic_origin.clone(),
833 },
834 to: ContextSpanned { value: OneOrMany::Many(vec![]), origin: synthetic_origin.clone() },
835 origin: synthetic_origin,
836 service: None,
837 protocol: None,
838 directory: None,
839 storage: None,
840 runner: None,
841 resolver: None,
842 dictionary: None,
843 config: None,
844 r#as: None,
845 rights: None,
846 subdir: None,
847 dependency: None,
848 event_stream: None,
849 scope: None,
850 availability: None,
851 source_availability: None,
852 target_availability: None,
853 }
854 }
855}
856
857impl ContextPathClause for ContextOffer {
858 fn path(&self) -> Option<&ContextSpanned<Path>> {
859 None
860 }
861}
862
863impl AsClauseContext for ContextOffer {
864 fn r#as(&self) -> Option<ContextSpanned<&BorrowedName>> {
865 self.r#as.as_ref().map(|spanned_name| ContextSpanned {
866 value: spanned_name.value.as_ref(),
867 origin: spanned_name.origin.clone(),
868 })
869 }
870}
871
872impl FromClauseContext for ContextOffer {
873 fn from_(&self) -> ContextSpanned<OneOrMany<AnyRef<'_>>> {
874 one_or_many_from_context(&self.from)
875 }
876}
877
878impl Hydrate for Offer {
879 type Output = ContextOffer;
880
881 fn hydrate(self, file: &Arc<PathBuf>) -> Result<Self::Output, Error> {
882 Ok(ContextOffer {
883 origin: file.clone(),
884 service: hydrate_opt_simple(self.service, file),
885 protocol: hydrate_opt_simple(self.protocol, file),
886 directory: hydrate_opt_simple(self.directory, file),
887 runner: hydrate_opt_simple(self.runner, file),
888 resolver: hydrate_opt_simple(self.resolver, file),
889 storage: hydrate_opt_simple(self.storage, file),
890 dictionary: hydrate_opt_simple(self.dictionary, file),
891 config: hydrate_opt_simple(self.config, file),
892 from: hydrate_simple(self.from, file),
893 to: hydrate_simple(self.to, file),
894 r#as: hydrate_opt_simple(self.r#as, file),
895 dependency: hydrate_opt_simple(self.dependency, file),
896 rights: hydrate_opt_simple(self.rights, file),
897 subdir: hydrate_opt_simple(self.subdir, file),
898 event_stream: hydrate_opt_simple(self.event_stream, file),
899 scope: hydrate_opt_simple(self.scope, file),
900 availability: hydrate_opt_simple(self.availability, file),
901 source_availability: hydrate_opt_simple(self.source_availability, file),
902 target_availability: hydrate_opt_simple(self.target_availability, file),
903 })
904 }
905}
906
907pub fn offer_to_all_from_context_offer(
908 value: &ContextOffer,
909) -> impl Iterator<Item = OfferToAllCapability<'_>> {
910 if let Some(protocol) = &value.protocol {
911 Either::Left(
912 protocol.value.iter().map(|protocol| OfferToAllCapability::Protocol(protocol.as_str())),
913 )
914 } else if let Some(dictionary) = &value.dictionary {
915 Either::Right(
916 dictionary
917 .value
918 .iter()
919 .map(|dictionary| OfferToAllCapability::Dictionary(dictionary.as_str())),
920 )
921 } else {
922 panic!("Expected a dictionary or a protocol");
923 }
924}
925
926pub fn offer_to_all_would_duplicate_context(
931 offer_to_all: &ContextSpanned<ContextOffer>,
932 specific_offer: &ContextSpanned<ContextOffer>,
933 target: &cm_types::BorrowedName,
934) -> Result<bool, Error> {
935 assert!(offer_to_all.value.protocol.is_some() || offer_to_all.value.dictionary.is_some());
937
938 if CapabilityId::from_context_offer(specific_offer).iter().flatten().all(
941 |specific_offer_cap_id| {
942 CapabilityId::from_context_offer(offer_to_all)
943 .iter()
944 .flatten()
945 .all(|offer_to_all_cap_id| offer_to_all_cap_id.0 != specific_offer_cap_id.0)
946 },
947 ) {
948 return Ok(false);
949 }
950
951 let to_field_matches = specific_offer.value.to.value.iter().any(
952 |specific_offer_to| matches!(specific_offer_to, OfferToRef::Named(c) if *c == *target),
953 );
954
955 if !to_field_matches {
956 return Ok(false);
957 }
958
959 if offer_to_all.value.from != specific_offer.value.from {
960 return Err(Error::validate_contexts(
961 offer_to_all_and_component_diff_sources_message(
962 offer_to_all_from_context_offer(&offer_to_all.value),
963 target.as_str(),
964 ),
965 vec![offer_to_all.origin.clone(), specific_offer.origin.clone()],
966 ));
967 }
968
969 if offer_to_all_from_context_offer(&offer_to_all.value).all(|to_all_protocol| {
971 offer_to_all_from_context_offer(&specific_offer.value)
972 .all(|to_specific_protocol| to_all_protocol != to_specific_protocol)
973 }) {
974 return Err(Error::validate_contexts(
975 offer_to_all_and_component_diff_capabilities_message(
976 offer_to_all_from_context_offer(&offer_to_all.value),
977 target.as_str(),
978 ),
979 vec![offer_to_all.origin.clone(), specific_offer.origin.clone()],
980 ));
981 }
982
983 Ok(true)
984}
985
986#[cfg(test)]
987pub fn create_offer(
988 protocol_name: &str,
989 from: OneOrMany<OfferFromRef>,
990 to: OneOrMany<OfferToRef>,
991) -> Offer {
992 Offer {
993 protocol: Some(OneOrMany::One(Name::from_str(protocol_name).unwrap())),
994 ..Offer::empty(from, to)
995 }
996}
997
998#[cfg(test)]
999mod tests {
1000 use super::*;
1001
1002 #[test]
1003 fn test_offer_would_duplicate() {
1004 let offer = create_offer(
1005 "fuchsia.logger.LegacyLog",
1006 OneOrMany::One(OfferFromRef::Parent {}),
1007 OneOrMany::One(OfferToRef::Named(Name::from_str("something").unwrap())),
1008 );
1009
1010 let offer_to_all = create_offer(
1011 "fuchsia.logger.LogSink",
1012 OneOrMany::One(OfferFromRef::Parent {}),
1013 OneOrMany::One(OfferToRef::All),
1014 );
1015
1016 assert!(
1018 !offer_to_all_would_duplicate(
1019 &offer_to_all,
1020 &offer,
1021 &Name::from_str("something").unwrap()
1022 )
1023 .unwrap()
1024 );
1025
1026 let offer = create_offer(
1027 "fuchsia.logger.LogSink",
1028 OneOrMany::One(OfferFromRef::Parent {}),
1029 OneOrMany::One(OfferToRef::Named(Name::from_str("not-something").unwrap())),
1030 );
1031
1032 assert!(
1034 !offer_to_all_would_duplicate(
1035 &offer_to_all,
1036 &offer,
1037 &Name::from_str("something").unwrap()
1038 )
1039 .unwrap()
1040 );
1041
1042 let mut offer = create_offer(
1043 "fuchsia.logger.LogSink",
1044 OneOrMany::One(OfferFromRef::Parent {}),
1045 OneOrMany::One(OfferToRef::Named(Name::from_str("something").unwrap())),
1046 );
1047
1048 offer.r#as = Some(Name::from_str("FakeLog").unwrap());
1049
1050 assert!(
1052 !offer_to_all_would_duplicate(
1053 &offer_to_all,
1054 &offer,
1055 &Name::from_str("something").unwrap()
1056 )
1057 .unwrap()
1058 );
1059
1060 let offer = create_offer(
1061 "fuchsia.logger.LogSink",
1062 OneOrMany::One(OfferFromRef::Parent {}),
1063 OneOrMany::One(OfferToRef::Named(Name::from_str("something").unwrap())),
1064 );
1065
1066 assert!(
1067 offer_to_all_would_duplicate(
1068 &offer_to_all,
1069 &offer,
1070 &Name::from_str("something").unwrap()
1071 )
1072 .unwrap()
1073 );
1074
1075 let offer = create_offer(
1076 "fuchsia.logger.LogSink",
1077 OneOrMany::One(OfferFromRef::Named(Name::from_str("other").unwrap())),
1078 OneOrMany::One(OfferToRef::Named(Name::from_str("something").unwrap())),
1079 );
1080
1081 assert!(
1082 offer_to_all_would_duplicate(
1083 &offer_to_all,
1084 &offer,
1085 &Name::from_str("something").unwrap()
1086 )
1087 .is_err()
1088 );
1089 }
1090}