1use std::{collections::BTreeMap, error::Error, fmt};
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6use super::configuration::resolve_configuration_layers;
7use crate::{
8 AppComposition, CapabilityBinding, CapabilityCardinality, CapabilityEndpointPlan,
9 CapabilityRequirementPlan, ExecutionClassId, ExecutionLaneId, ExecutionLanePlan,
10 PluginCriticality, PluginInstancePlan, RequestAdmissionPlan, ResolvedAppPlan, RestartPolicy,
11};
12
13mod release;
14mod resolution;
15mod selection;
16pub use selection::{DependencyChoice, DependencySelection};
17
18pub use release::{PluginContract, PluginImplementation};
19use resolution::{derive_root_bindings, map_configuration_error};
20pub use resolution::{propose_plugin_root, resolve_plugin_root};
21
22fn empty_configuration() -> Value {
23 Value::Object(serde_json::Map::new())
24}
25
26fn is_empty_configuration(configuration: &Value) -> bool {
27 configuration == &empty_configuration()
28}
29
30fn default_entrypoint() -> String {
31 "default".to_owned()
32}
33
34#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
36#[serde(deny_unknown_fields)]
37pub struct PluginInstanceId {
38 plugin_id: String,
39 instance_key: String,
40}
41
42impl PluginInstanceId {
43 pub fn new(plugin_id: impl Into<String>, instance_key: impl Into<String>) -> Self {
44 Self {
45 plugin_id: plugin_id.into(),
46 instance_key: instance_key.into(),
47 }
48 }
49
50 pub fn plugin_id(&self) -> &str {
51 &self.plugin_id
52 }
53
54 pub fn instance_key(&self) -> &str {
55 &self.instance_key
56 }
57
58 pub fn plan_key(&self) -> String {
60 format!("{}/{}", self.plugin_id, self.instance_key)
61 }
62}
63
64impl fmt::Display for PluginInstanceId {
65 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
66 write!(formatter, "{}/{}", self.plugin_id, self.instance_key)
67 }
68}
69
70#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
72#[serde(deny_unknown_fields)]
73pub struct PluginDescriptor {
74 #[serde(default = "crate::schema::old_authoring_version")]
75 authoring_version: u32,
76 #[serde(default)]
77 runtime_profile: String,
78 plugin_id: String,
79 release_version: String,
80 root_slot: String,
81 runtime_package_id: String,
82 runtime_package_revision: String,
83 #[serde(default = "default_entrypoint")]
84 entrypoint: String,
85 #[serde(default, skip_serializing_if = "Option::is_none")]
86 configuration_schema: Option<Value>,
87 #[serde(
88 default = "empty_configuration",
89 skip_serializing_if = "is_empty_configuration"
90 )]
91 configuration_defaults: Value,
92 provided_capabilities: Vec<CapabilityEndpointPlan>,
93 required_capabilities: Vec<CapabilityRequirementPlan>,
94 execution_class: ExecutionClassId,
95 restart_policy: RestartPolicy,
96 criticality: PluginCriticality,
97}
98
99impl PluginDescriptor {
100 pub fn new(
101 plugin_id: impl Into<String>,
102 release_version: impl Into<String>,
103 root_slot: impl Into<String>,
104 ) -> Self {
105 let plugin_id = plugin_id.into();
106 let release_version = release_version.into();
107 Self {
108 authoring_version: 1,
109 runtime_profile: "lenso.native-authoring@1".to_owned(),
110 runtime_package_id: plugin_id.clone(),
111 runtime_package_revision: release_version.clone(),
112 plugin_id,
113 release_version,
114 root_slot: root_slot.into(),
115 entrypoint: default_entrypoint(),
116 configuration_schema: None,
117 configuration_defaults: empty_configuration(),
118 provided_capabilities: Vec::new(),
119 required_capabilities: Vec::new(),
120 execution_class: ExecutionClassId::native_rust(),
121 restart_policy: RestartPolicy::default(),
122 criticality: PluginCriticality::default(),
123 }
124 }
125
126 #[must_use]
127 pub fn with_runtime_package(
128 mut self,
129 package_id: impl Into<String>,
130 package_revision: impl Into<String>,
131 ) -> Self {
132 self.runtime_package_id = package_id.into();
133 self.runtime_package_revision = package_revision.into();
134 self
135 }
136
137 #[must_use]
138 pub fn with_authoring(mut self, version: u32, runtime_profile: impl Into<String>) -> Self {
139 self.authoring_version = version;
140 self.runtime_profile = runtime_profile.into();
141 self
142 }
143
144 pub const fn authoring_version(&self) -> u32 {
145 self.authoring_version
146 }
147
148 pub fn runtime_profile(&self) -> &str {
149 if self.runtime_profile.is_empty() {
150 self.execution_class.as_str()
151 } else {
152 &self.runtime_profile
153 }
154 }
155
156 #[must_use]
157 pub fn with_entrypoint(mut self, entrypoint: impl Into<String>) -> Self {
158 self.entrypoint = entrypoint.into();
159 self
160 }
161
162 #[must_use]
163 pub fn with_configuration_schema(mut self, schema: Value) -> Self {
164 self.configuration_schema = Some(schema);
165 self
166 }
167
168 #[must_use]
169 pub fn with_configuration_defaults(mut self, defaults: Value) -> Self {
170 self.configuration_defaults = defaults;
171 self
172 }
173
174 #[must_use]
175 pub fn with_capability(mut self, capability: CapabilityEndpointPlan) -> Self {
176 self.provided_capabilities.push(capability);
177 self
178 }
179
180 #[must_use]
181 pub fn with_requirement(mut self, requirement: CapabilityRequirementPlan) -> Self {
182 self.required_capabilities.push(requirement);
183 self
184 }
185
186 #[must_use]
187 pub fn with_execution_class(mut self, execution_class: ExecutionClassId) -> Self {
188 if self.authoring_version == 1
189 && self.runtime_profile == crate::schema::old_runtime_profile(&self.execution_class)
190 {
191 self.runtime_profile = crate::schema::old_runtime_profile(&execution_class);
192 }
193 self.execution_class = execution_class;
194 self
195 }
196
197 #[must_use]
198 pub fn with_restart_policy(mut self, restart_policy: RestartPolicy) -> Self {
199 self.restart_policy = restart_policy;
200 self
201 }
202
203 #[must_use]
204 pub fn with_criticality(mut self, criticality: PluginCriticality) -> Self {
205 self.criticality = criticality;
206 self
207 }
208
209 pub fn plugin_id(&self) -> &str {
210 &self.plugin_id
211 }
212
213 pub fn release_version(&self) -> &str {
214 &self.release_version
215 }
216
217 pub fn root_slot(&self) -> &str {
218 &self.root_slot
219 }
220
221 pub fn runtime_package_id(&self) -> &str {
222 &self.runtime_package_id
223 }
224
225 pub fn runtime_package_revision(&self) -> &str {
226 &self.runtime_package_revision
227 }
228
229 pub fn entrypoint(&self) -> &str {
230 &self.entrypoint
231 }
232
233 pub const fn configuration_schema(&self) -> Option<&Value> {
234 self.configuration_schema.as_ref()
235 }
236
237 pub const fn configuration_defaults(&self) -> &Value {
238 &self.configuration_defaults
239 }
240
241 pub fn provided_capabilities(&self) -> &[CapabilityEndpointPlan] {
242 &self.provided_capabilities
243 }
244
245 pub fn required_capabilities(&self) -> &[CapabilityRequirementPlan] {
246 &self.required_capabilities
247 }
248
249 pub fn execution_class(&self) -> &ExecutionClassId {
250 &self.execution_class
251 }
252
253 pub const fn restart_policy(&self) -> RestartPolicy {
254 self.restart_policy
255 }
256
257 pub const fn criticality(&self) -> PluginCriticality {
258 self.criticality
259 }
260}
261
262#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
264#[serde(rename_all = "snake_case")]
265pub enum HostSlotCardinality {
266 One,
267 Optional,
268 Many,
269}
270
271#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
273#[serde(deny_unknown_fields)]
274pub struct HostSlot {
275 id: String,
276 cardinality: HostSlotCardinality,
277 #[serde(default)]
278 replaceable: bool,
279 #[serde(default = "default_execution_lane")]
280 execution_lane: String,
281}
282
283fn default_execution_lane() -> String {
284 "main".to_owned()
285}
286
287impl HostSlot {
288 pub fn one(id: impl Into<String>) -> Self {
289 Self::new(id, HostSlotCardinality::One)
290 }
291
292 pub fn optional(id: impl Into<String>) -> Self {
293 Self::new(id, HostSlotCardinality::Optional)
294 }
295
296 pub fn many(id: impl Into<String>) -> Self {
297 Self::new(id, HostSlotCardinality::Many)
298 }
299
300 fn new(id: impl Into<String>, cardinality: HostSlotCardinality) -> Self {
301 Self {
302 id: id.into(),
303 cardinality,
304 replaceable: false,
305 execution_lane: default_execution_lane(),
306 }
307 }
308
309 #[must_use]
310 pub const fn replaceable(mut self) -> Self {
311 self.replaceable = true;
312 self
313 }
314
315 #[must_use]
316 pub fn with_execution_lane(mut self, execution_lane: impl Into<String>) -> Self {
317 self.execution_lane = execution_lane.into();
318 self
319 }
320
321 pub fn id(&self) -> &str {
322 &self.id
323 }
324
325 pub const fn cardinality(&self) -> HostSlotCardinality {
326 self.cardinality
327 }
328
329 pub const fn is_replaceable(&self) -> bool {
330 self.replaceable
331 }
332
333 pub fn execution_lane(&self) -> &str {
334 &self.execution_lane
335 }
336}
337
338#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
340#[serde(deny_unknown_fields)]
341pub struct HostPluginRelease {
342 descriptor: PluginDescriptor,
343 #[serde(default)]
344 allow_root_override: bool,
345}
346
347impl HostPluginRelease {
348 pub const fn new(descriptor: PluginDescriptor) -> Self {
349 Self {
350 descriptor,
351 allow_root_override: false,
352 }
353 }
354
355 #[must_use]
356 pub const fn allow_root_override(mut self) -> Self {
357 self.allow_root_override = true;
358 self
359 }
360
361 pub const fn descriptor(&self) -> &PluginDescriptor {
362 &self.descriptor
363 }
364
365 pub const fn root_override_allowed(&self) -> bool {
366 self.allow_root_override
367 }
368}
369
370#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
372#[serde(deny_unknown_fields)]
373pub struct HostDefaultPlugin {
374 id: PluginInstanceId,
375 #[serde(default = "empty_configuration")]
376 configuration: Value,
377 #[serde(default)]
378 disableable: bool,
379}
380
381#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
384#[serde(deny_unknown_fields)]
385pub struct HostPluginConfiguration {
386 id: PluginInstanceId,
387 #[serde(default = "empty_configuration")]
388 configuration: Value,
389}
390
391impl HostPluginConfiguration {
392 pub fn new(
393 plugin_id: impl Into<String>,
394 instance_key: impl Into<String>,
395 configuration: Value,
396 ) -> Self {
397 Self {
398 id: PluginInstanceId::new(plugin_id, instance_key),
399 configuration,
400 }
401 }
402
403 pub const fn id(&self) -> &PluginInstanceId {
404 &self.id
405 }
406
407 pub const fn configuration(&self) -> &Value {
408 &self.configuration
409 }
410}
411
412#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
417#[serde(deny_unknown_fields)]
418pub struct HostBinding {
419 #[serde(default)]
420 selection: DependencySelection,
421 #[serde(default)]
422 default_provider: Option<PluginInstanceId>,
423 #[serde(default)]
424 requirement_id: Option<String>,
425 consumer: PluginInstanceId,
426 capability_id: String,
427 #[serde(default, skip_serializing_if = "Option::is_none")]
428 provider_slot: Option<String>,
429 #[serde(default, skip_serializing_if = "Option::is_none")]
430 provider_instance: Option<PluginInstanceId>,
431 #[serde(default, skip_serializing_if = "Vec::is_empty")]
432 provider_instances: Vec<PluginInstanceId>,
433 #[serde(default, skip_serializing_if = "Option::is_none")]
434 admission: Option<RequestAdmissionPlan>,
435}
436
437impl HostBinding {
438 pub fn new(
439 consumer: PluginInstanceId,
440 capability_id: impl Into<String>,
441 provider_slot: impl Into<String>,
442 ) -> Self {
443 Self {
444 consumer,
445 selection: DependencySelection::Fixed,
446 default_provider: None,
447 requirement_id: None,
448 capability_id: capability_id.into(),
449 provider_slot: Some(provider_slot.into()),
450 provider_instance: None,
451 provider_instances: Vec::new(),
452 admission: None,
453 }
454 }
455
456 pub fn to_instance(
457 consumer: PluginInstanceId,
458 capability_id: impl Into<String>,
459 provider: PluginInstanceId,
460 ) -> Self {
461 Self {
462 consumer,
463 selection: DependencySelection::Fixed,
464 default_provider: None,
465 requirement_id: None,
466 capability_id: capability_id.into(),
467 provider_slot: None,
468 provider_instance: Some(provider),
469 provider_instances: Vec::new(),
470 admission: None,
471 }
472 }
473
474 pub fn to_instances(
476 consumer: PluginInstanceId,
477 capability_id: impl Into<String>,
478 providers: impl IntoIterator<Item = PluginInstanceId>,
479 ) -> Self {
480 Self {
481 consumer,
482 selection: DependencySelection::Fixed,
483 default_provider: None,
484 requirement_id: None,
485 capability_id: capability_id.into(),
486 provider_slot: None,
487 provider_instance: None,
488 provider_instances: providers.into_iter().collect(),
489 admission: None,
490 }
491 }
492
493 #[must_use]
494 pub const fn with_admission(mut self, admission: RequestAdmissionPlan) -> Self {
495 self.admission = Some(admission);
496 self
497 }
498
499 #[must_use]
500 pub fn with_requirement_id(mut self, id: impl Into<String>) -> Self {
501 self.requirement_id = Some(id.into());
502 self
503 }
504
505 #[must_use]
507 pub fn selectable(mut self, default_provider: Option<PluginInstanceId>) -> Self {
508 self.selection = DependencySelection::Selectable;
509 self.default_provider = default_provider;
510 self
511 }
512
513 pub fn requirement_id(&self) -> std::borrow::Cow<'_, str> {
514 self.requirement_id.as_deref().map_or_else(
515 || std::borrow::Cow::Owned(format!("~{}", self.capability_id)),
516 std::borrow::Cow::Borrowed,
517 )
518 }
519
520 pub const fn consumer(&self) -> &PluginInstanceId {
521 &self.consumer
522 }
523
524 pub fn capability_id(&self) -> &str {
525 &self.capability_id
526 }
527
528 pub fn provider_slot(&self) -> Option<&str> {
529 self.provider_slot.as_deref()
530 }
531
532 pub const fn provider_instance(&self) -> Option<&PluginInstanceId> {
533 self.provider_instance.as_ref()
534 }
535
536 pub fn provider_instances(&self) -> &[PluginInstanceId] {
537 &self.provider_instances
538 }
539
540 pub const fn admission(&self) -> Option<RequestAdmissionPlan> {
541 self.admission
542 }
543}
544
545impl HostDefaultPlugin {
546 pub fn new(plugin_id: impl Into<String>, instance_key: impl Into<String>) -> Self {
547 Self {
548 id: PluginInstanceId::new(plugin_id, instance_key),
549 configuration: empty_configuration(),
550 disableable: false,
551 }
552 }
553
554 #[must_use]
555 pub fn with_configuration(mut self, configuration: Value) -> Self {
556 self.configuration = configuration;
557 self
558 }
559
560 #[must_use]
561 pub const fn disableable(mut self) -> Self {
562 self.disableable = true;
563 self
564 }
565
566 pub const fn id(&self) -> &PluginInstanceId {
567 &self.id
568 }
569
570 pub const fn configuration(&self) -> &Value {
571 &self.configuration
572 }
573
574 pub const fn is_disableable(&self) -> bool {
575 self.disableable
576 }
577}
578
579#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
581#[serde(deny_unknown_fields)]
582pub struct HostCatalog {
583 #[serde(default)]
584 slots: Vec<HostSlot>,
585 #[serde(default)]
586 plugins: Vec<HostPluginRelease>,
587 #[serde(default)]
588 defaults: Vec<HostDefaultPlugin>,
589 #[serde(default)]
590 configurations: Vec<HostPluginConfiguration>,
591 #[serde(default)]
592 bindings: Vec<HostBinding>,
593 #[serde(default = "default_execution_lanes")]
594 execution_lanes: Vec<ExecutionLanePlan>,
595}
596
597fn default_execution_lanes() -> Vec<ExecutionLanePlan> {
598 vec![ExecutionLanePlan::new("main")]
599}
600
601impl HostCatalog {
602 pub fn new(
603 slots: impl IntoIterator<Item = HostSlot>,
604 plugins: impl IntoIterator<Item = HostPluginRelease>,
605 defaults: impl IntoIterator<Item = HostDefaultPlugin>,
606 ) -> Self {
607 Self {
608 slots: slots.into_iter().collect(),
609 plugins: plugins.into_iter().collect(),
610 defaults: defaults.into_iter().collect(),
611 configurations: Vec::new(),
612 bindings: Vec::new(),
613 execution_lanes: default_execution_lanes(),
614 }
615 }
616
617 #[must_use]
618 pub fn with_execution_lanes(mut self, lanes: Vec<ExecutionLanePlan>) -> Self {
619 self.execution_lanes = lanes;
620 self
621 }
622
623 #[must_use]
624 pub fn with_bindings(mut self, bindings: impl IntoIterator<Item = HostBinding>) -> Self {
625 self.bindings = bindings.into_iter().collect();
626 self
627 }
628
629 #[must_use]
630 pub fn with_configurations(
631 mut self,
632 configurations: impl IntoIterator<Item = HostPluginConfiguration>,
633 ) -> Self {
634 self.configurations = configurations.into_iter().collect();
635 self
636 }
637
638 pub fn slots(&self) -> &[HostSlot] {
639 &self.slots
640 }
641
642 pub fn plugins(&self) -> &[HostPluginRelease] {
643 &self.plugins
644 }
645
646 pub fn defaults(&self) -> &[HostDefaultPlugin] {
647 &self.defaults
648 }
649
650 pub fn configurations(&self) -> &[HostPluginConfiguration] {
651 &self.configurations
652 }
653
654 pub fn bindings(&self) -> &[HostBinding] {
655 &self.bindings
656 }
657
658 pub fn execution_lanes(&self) -> &[ExecutionLanePlan] {
659 &self.execution_lanes
660 }
661}
662
663#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
665#[serde(deny_unknown_fields)]
666pub struct PluginRootInstance {
667 id: PluginInstanceId,
668 #[serde(default = "empty_configuration")]
669 configuration: Value,
670}
671
672impl PluginRootInstance {
673 pub fn new(plugin_id: impl Into<String>, instance_key: impl Into<String>) -> Self {
674 Self {
675 id: PluginInstanceId::new(plugin_id, instance_key),
676 configuration: empty_configuration(),
677 }
678 }
679
680 #[must_use]
681 pub fn with_configuration(mut self, configuration: Value) -> Self {
682 self.configuration = configuration;
683 self
684 }
685
686 pub const fn id(&self) -> &PluginInstanceId {
687 &self.id
688 }
689
690 pub const fn configuration(&self) -> &Value {
691 &self.configuration
692 }
693}
694
695#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
697#[serde(deny_unknown_fields)]
698pub struct PluginRootSnapshot {
699 #[serde(default)]
700 dependency_selection_adopted: bool,
701 #[serde(default)]
702 dependency_choices: Vec<DependencyChoice>,
703 #[serde(default)]
704 releases: Vec<PluginDescriptor>,
705 #[serde(default)]
706 instances: Vec<PluginRootInstance>,
707 #[serde(default)]
708 disabled: Vec<PluginInstanceId>,
709}
710
711impl PluginRootSnapshot {
712 pub fn new(
713 releases: impl IntoIterator<Item = PluginDescriptor>,
714 instances: impl IntoIterator<Item = PluginRootInstance>,
715 disabled: impl IntoIterator<Item = PluginInstanceId>,
716 ) -> Self {
717 Self {
718 dependency_selection_adopted: false,
719 dependency_choices: Vec::new(),
720 releases: releases.into_iter().collect(),
721 instances: instances.into_iter().collect(),
722 disabled: disabled.into_iter().collect(),
723 }
724 }
725
726 pub fn releases(&self) -> &[PluginDescriptor] {
727 &self.releases
728 }
729
730 #[must_use]
732 pub fn with_dependency_choices(mut self, choices: Vec<DependencyChoice>) -> Self {
733 self.dependency_selection_adopted = true;
734 self.dependency_choices = choices;
735 self
736 }
737
738 pub fn dependency_choices(&self) -> &[DependencyChoice] {
739 &self.dependency_choices
740 }
741
742 pub const fn dependency_selection_adopted(&self) -> bool {
744 self.dependency_selection_adopted
745 }
746
747 pub fn instances(&self) -> &[PluginRootInstance] {
748 &self.instances
749 }
750
751 pub fn disabled(&self) -> &[PluginInstanceId] {
752 &self.disabled
753 }
754}
755
756#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
758#[serde(rename_all = "snake_case")]
759pub enum PluginInstanceSource {
760 HostDefault,
761 HostDefaultConfiguredByRoot,
762 PluginRoot,
763}
764
765#[derive(Clone, Debug, Eq, PartialEq)]
767pub struct ResolvedPluginInstance {
768 id: PluginInstanceId,
769 plan_key: String,
770 source: PluginInstanceSource,
771}
772
773impl ResolvedPluginInstance {
774 pub const fn id(&self) -> &PluginInstanceId {
775 &self.id
776 }
777
778 pub fn plan_key(&self) -> &str {
779 &self.plan_key
780 }
781
782 pub const fn source(&self) -> PluginInstanceSource {
783 self.source
784 }
785}
786
787#[derive(Clone, Debug, Eq, PartialEq)]
789pub struct ResolvedApp {
790 dependency_choices: Vec<DependencyChoice>,
791 plan: ResolvedAppPlan,
792 instances: Vec<ResolvedPluginInstance>,
793}
794
795impl ResolvedApp {
796 pub fn dependency_choices(&self) -> &[DependencyChoice] {
798 &self.dependency_choices
799 }
800 pub const fn plan(&self) -> &ResolvedAppPlan {
801 &self.plan
802 }
803
804 pub fn instances(&self) -> &[ResolvedPluginInstance] {
805 &self.instances
806 }
807}
808
809#[derive(Clone, Debug)]
810struct CandidateInstance<'a> {
811 id: PluginInstanceId,
812 descriptor: &'a PluginDescriptor,
813 host_configuration: Option<&'a Value>,
814 root_configuration: Option<&'a Value>,
815 source: PluginInstanceSource,
816}
817
818#[derive(Clone, Debug, Eq, PartialEq)]
820pub enum PluginRootResolutionError {
821 DuplicateHostSlot(String),
822 DuplicatePluginRelease(String),
823 RootReleaseOverrideDenied(String),
824 DuplicateInstance(PluginInstanceId),
825 DuplicateDisabledMarker(PluginInstanceId),
826 UnknownPlugin(PluginInstanceId),
827 UnknownDisabledInstance(PluginInstanceId),
828 RequiredInstanceDisabled(PluginInstanceId),
829 UnknownRootSlot {
830 plugin_id: String,
831 slot: String,
832 },
833 MultipleHostDefaults {
834 slot: String,
835 instances: Vec<String>,
836 },
837 ExplicitProviderDenied {
838 slot: String,
839 instance: PluginInstanceId,
840 },
841 MissingRequiredSlot(String),
842 AmbiguousSlot {
843 slot: String,
844 instances: Vec<String>,
845 },
846 MissingCapability {
847 consumer: PluginInstanceId,
848 capability_id: String,
849 descriptor_version: String,
850 },
851 AmbiguousCapability {
852 consumer: PluginInstanceId,
853 capability_id: String,
854 candidates: Vec<PluginInstanceId>,
855 },
856 InvalidHostBinding(String),
857 InvalidHostConfiguration(String),
858 InvalidConfiguration {
859 instance: PluginInstanceId,
860 detail: String,
861 },
862 InvalidResolvedApp(String),
863}
864
865impl fmt::Display for PluginRootResolutionError {
866 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
867 match self {
868 Self::DuplicateHostSlot(slot) => write!(formatter, "duplicate Host Slot `{slot}`"),
869 Self::DuplicatePluginRelease(plugin) => {
870 write!(formatter, "duplicate Plugin Release `{plugin}`")
871 }
872 Self::RootReleaseOverrideDenied(plugin) => write!(
873 formatter,
874 "Plugin Root cannot replace Host Release `{plugin}`"
875 ),
876 Self::DuplicateInstance(instance) => {
877 write!(formatter, "duplicate Plugin Instance `{instance}`")
878 }
879 Self::DuplicateDisabledMarker(instance) => {
880 write!(formatter, "duplicate disabled marker for `{instance}`")
881 }
882 Self::UnknownPlugin(instance) => {
883 write!(
884 formatter,
885 "Plugin Instance `{instance}` has no exact Release"
886 )
887 }
888 Self::UnknownDisabledInstance(instance) => {
889 write!(
890 formatter,
891 "disabled marker refers to unknown Instance `{instance}`"
892 )
893 }
894 Self::RequiredInstanceDisabled(instance) => {
895 write!(
896 formatter,
897 "required Host Instance `{instance}` cannot be disabled"
898 )
899 }
900 Self::UnknownRootSlot { plugin_id, slot } => write!(
901 formatter,
902 "Plugin `{plugin_id}` offers unknown Host Slot `{slot}`"
903 ),
904 Self::MultipleHostDefaults { slot, instances } => write!(
905 formatter,
906 "Host Slot `{slot}` has multiple defaults: {}",
907 instances.join(", ")
908 ),
909 Self::ExplicitProviderDenied { slot, instance } => write!(
910 formatter,
911 "Host Slot `{slot}` does not allow `{instance}` to replace its default"
912 ),
913 Self::MissingRequiredSlot(slot) => {
914 write!(
915 formatter,
916 "required Host Slot `{slot}` has no enabled Plugin"
917 )
918 }
919 Self::AmbiguousSlot { slot, instances } => write!(
920 formatter,
921 "Host Slot `{slot}` has multiple explicit Plugins: {}",
922 instances.join(", ")
923 ),
924 Self::MissingCapability {
925 consumer,
926 capability_id,
927 descriptor_version,
928 } => write!(
929 formatter,
930 "Plugin Instance `{consumer}` has no provider for Capability `{capability_id}` version `{descriptor_version}`"
931 ),
932 Self::AmbiguousCapability {
933 consumer,
934 capability_id,
935 candidates,
936 } => write!(
937 formatter,
938 "Plugin Instance `{consumer}` has multiple providers for Capability `{capability_id}`: {}",
939 candidates
940 .iter()
941 .map(ToString::to_string)
942 .collect::<Vec<_>>()
943 .join(", ")
944 ),
945 Self::InvalidHostBinding(detail) => write!(formatter, "invalid Host binding: {detail}"),
946 Self::InvalidHostConfiguration(detail) => {
947 write!(formatter, "invalid Host Plugin configuration: {detail}")
948 }
949 Self::InvalidConfiguration { instance, detail } => {
950 write!(
951 formatter,
952 "Plugin Instance `{instance}` has invalid configuration: {detail}"
953 )
954 }
955 Self::InvalidResolvedApp(detail) => {
956 write!(formatter, "derived App is invalid: {detail}")
957 }
958 }
959 }
960}
961
962impl Error for PluginRootResolutionError {}
963
964fn select_slot_candidates<'a>(
966 slots: &BTreeMap<&str, &'a HostSlot>,
967 candidates: Vec<CandidateInstance<'a>>,
968) -> Result<Vec<(CandidateInstance<'a>, &'a HostSlot)>, PluginRootResolutionError> {
969 let mut by_slot = BTreeMap::<&str, Vec<CandidateInstance<'a>>>::new();
970 for candidate in candidates {
971 by_slot
972 .entry(candidate.descriptor.root_slot())
973 .or_default()
974 .push(candidate);
975 }
976 let mut selected = Vec::new();
977 for (slot_id, slot) in slots {
978 let mut candidates = by_slot.remove(slot_id).unwrap_or_default();
979 candidates.sort_by(|left, right| left.id.cmp(&right.id));
980 if slot.cardinality == HostSlotCardinality::Many {
981 selected.extend(candidates.into_iter().map(|candidate| (candidate, *slot)));
982 continue;
983 }
984 let (defaults, explicit): (Vec<_>, Vec<_>) = candidates
985 .into_iter()
986 .partition(|candidate| candidate.source != PluginInstanceSource::PluginRoot);
987 if defaults.len() > 1 {
988 return Err(PluginRootResolutionError::MultipleHostDefaults {
989 slot: (*slot_id).to_owned(),
990 instances: defaults
991 .iter()
992 .map(|candidate| candidate.id.to_string())
993 .collect(),
994 });
995 }
996 if explicit.len() > 1 {
997 return Err(PluginRootResolutionError::AmbiguousSlot {
998 slot: (*slot_id).to_owned(),
999 instances: explicit
1000 .iter()
1001 .map(|candidate| candidate.id.to_string())
1002 .collect(),
1003 });
1004 }
1005 if let Some(candidate) = explicit.into_iter().next() {
1006 if !defaults.is_empty() && !slot.replaceable {
1007 return Err(PluginRootResolutionError::ExplicitProviderDenied {
1008 slot: (*slot_id).to_owned(),
1009 instance: candidate.id,
1010 });
1011 }
1012 selected.push((candidate, *slot));
1013 } else if let Some(candidate) = defaults.into_iter().next() {
1014 selected.push((candidate, *slot));
1015 } else if slot.cardinality == HostSlotCardinality::One {
1016 return Err(PluginRootResolutionError::MissingRequiredSlot(
1017 (*slot_id).to_owned(),
1018 ));
1019 }
1020 }
1021 Ok(selected)
1022}
1023
1024fn materialize_app(
1025 selected: Vec<(CandidateInstance<'_>, &HostSlot)>,
1026 host_bindings: &[HostBinding],
1027 lanes: &[ExecutionLanePlan],
1028 root: &PluginRootSnapshot,
1029 propose: bool,
1030) -> Result<ResolvedApp, PluginRootResolutionError> {
1031 let mut plan_instances = Vec::with_capacity(selected.len());
1032 let mut resolved_instances = Vec::with_capacity(selected.len());
1033 let mut plan_slots = BTreeMap::new();
1034 for (candidate, slot) in selected {
1035 let plan_key = candidate.id.plan_key();
1036 plan_slots.insert(plan_key.clone(), slot.id().to_owned());
1037 let overlays = candidate
1038 .host_configuration
1039 .into_iter()
1040 .chain(candidate.root_configuration)
1041 .collect::<Vec<_>>();
1042 let configuration = resolve_configuration_layers(
1043 candidate.descriptor.configuration_defaults(),
1044 &overlays,
1045 candidate.descriptor.configuration_schema(),
1046 &plan_key,
1047 )
1048 .map_err(|error| map_configuration_error(&candidate.id, error))?;
1049 let configuration = serde_json::to_string(&configuration).map_err(|error| {
1050 PluginRootResolutionError::InvalidConfiguration {
1051 instance: candidate.id.clone(),
1052 detail: error.to_string(),
1053 }
1054 })?;
1055 let descriptor = candidate.descriptor;
1056 let mut instance = PluginInstancePlan::new(&plan_key, descriptor.runtime_package_id())
1057 .with_authoring(descriptor.authoring_version(), descriptor.runtime_profile())
1058 .with_entrypoint(descriptor.entrypoint())
1059 .with_package_revision(descriptor.runtime_package_revision())
1060 .with_configuration(configuration)
1061 .with_execution_class(descriptor.execution_class().clone())
1062 .with_restart_policy(descriptor.restart_policy())
1063 .with_criticality(descriptor.criticality())
1064 .with_execution_lane(ExecutionLaneId::new(&slot.execution_lane));
1065 for capability in descriptor.provided_capabilities() {
1066 instance = instance.with_capability(capability.clone());
1067 }
1068 for requirement in descriptor.required_capabilities() {
1069 instance = instance.with_requirement(requirement.clone());
1070 }
1071 plan_instances.push(instance);
1072 resolved_instances.push(ResolvedPluginInstance {
1073 id: candidate.id,
1074 plan_key,
1075 source: candidate.source,
1076 });
1077 }
1078 let (bindings, dependency_choices) = derive_root_bindings(
1079 &plan_instances,
1080 &resolved_instances,
1081 &plan_slots,
1082 host_bindings,
1083 root,
1084 propose,
1085 )?;
1086 let composition =
1087 AppComposition::new(plan_instances, bindings).with_execution_lanes(lanes.to_vec());
1088 let plan = composition
1089 .resolve()
1090 .map_err(|error| PluginRootResolutionError::InvalidResolvedApp(error.to_string()))?;
1091 resolved_instances.sort_by(|left, right| left.id.cmp(&right.id));
1092 Ok(ResolvedApp {
1093 dependency_choices,
1094 plan,
1095 instances: resolved_instances,
1096 })
1097}