Skip to main content

microde_application/
lifecycle_context.rs

1use std::any::Any;
2use std::collections::HashMap;
3use std::sync::Arc;
4
5use crate::{Dependency, MicrodeError, ModuleInstanceId, Reference, RelationshipKind};
6
7#[derive(Clone)]
8pub(crate) struct ResolvedRelationship {
9    pub(crate) owner: ModuleInstanceId,
10    pub(crate) name: String,
11    pub(crate) kind: RelationshipKind,
12    pub(crate) value: Arc<dyn Any + Send + Sync>,
13}
14
15#[derive(Clone)]
16pub struct SetupContext {
17    owner: ModuleInstanceId,
18    resolutions: Arc<HashMap<u64, ResolvedRelationship>>,
19}
20
21#[derive(Clone)]
22pub struct RunContext {
23    owner: ModuleInstanceId,
24    resolutions: Arc<HashMap<u64, ResolvedRelationship>>,
25}
26
27impl SetupContext {
28    pub(crate) fn new(
29        owner: ModuleInstanceId,
30        resolutions: Arc<HashMap<u64, ResolvedRelationship>>,
31    ) -> Self {
32        Self { owner, resolutions }
33    }
34
35    pub fn use_dependency<T>(&self, relationship: &Dependency<T>) -> Result<T, MicrodeError>
36    where
37        T: Clone + Send + Sync + 'static,
38    {
39        resolve(
40            &self.owner,
41            &self.resolutions,
42            relationship.slot_id(),
43            relationship.name(),
44            Some(RelationshipKind::Dependency),
45        )
46    }
47}
48
49pub trait RunRelationship<T> {
50    fn slot_id(&self) -> u64;
51    fn name(&self) -> &str;
52}
53
54impl<T> RunRelationship<T> for Dependency<T> {
55    fn slot_id(&self) -> u64 {
56        self.slot_id()
57    }
58    fn name(&self) -> &str {
59        self.name()
60    }
61}
62
63impl<T> RunRelationship<T> for Reference<T> {
64    fn slot_id(&self) -> u64 {
65        self.slot_id()
66    }
67    fn name(&self) -> &str {
68        self.name()
69    }
70}
71
72impl RunContext {
73    pub(crate) fn new(
74        owner: ModuleInstanceId,
75        resolutions: Arc<HashMap<u64, ResolvedRelationship>>,
76    ) -> Self {
77        Self { owner, resolutions }
78    }
79
80    pub fn use_relationship<T, Slot>(&self, relationship: &Slot) -> Result<T, MicrodeError>
81    where
82        T: Clone + Send + Sync + 'static,
83        Slot: RunRelationship<T>,
84    {
85        resolve(
86            &self.owner,
87            &self.resolutions,
88            relationship.slot_id(),
89            relationship.name(),
90            None,
91        )
92    }
93}
94
95fn resolve<T>(
96    owner: &ModuleInstanceId,
97    resolutions: &HashMap<u64, ResolvedRelationship>,
98    slot_id: u64,
99    name: &str,
100    expected_kind: Option<RelationshipKind>,
101) -> Result<T, MicrodeError>
102where
103    T: Clone + Send + Sync + 'static,
104{
105    let resolved = resolutions
106        .get(&slot_id)
107        .filter(|value| &value.owner == owner)
108        .ok_or_else(|| {
109            MicrodeError::new(format!(
110                "relationship '{}.{}' is not resolved for this module",
111                owner.as_str(),
112                name
113            ))
114        })?;
115    if expected_kind.is_some_and(|kind| resolved.kind != kind) {
116        return Err(MicrodeError::new(format!(
117            "relationship '{}.{}' is not available during setup",
118            owner.as_str(),
119            resolved.name
120        )));
121    }
122    resolved.value.downcast_ref::<T>().cloned().ok_or_else(|| {
123        MicrodeError::new(format!(
124            "provider type mismatch for relationship '{}.{}'",
125            owner.as_str(),
126            resolved.name
127        ))
128    })
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134    use crate::Port;
135
136    fn resolved(
137        owner: &str,
138        name: &str,
139        kind: RelationshipKind,
140        value: Arc<dyn Any + Send + Sync>,
141    ) -> ResolvedRelationship {
142        ResolvedRelationship {
143            owner: ModuleInstanceId::new(owner),
144            name: name.to_owned(),
145            kind,
146            value,
147        }
148    }
149
150    #[test]
151    fn reports_unresolved_wrong_phase_and_provider_type_errors() {
152        let dependency = Dependency::new("database", Port::<String>::new("database"));
153        let mut values = HashMap::new();
154        values.insert(
155            dependency.slot_id(),
156            resolved(
157                "other",
158                "database",
159                RelationshipKind::Dependency,
160                Arc::new("value".to_owned()),
161            ),
162        );
163        let context = SetupContext::new(ModuleInstanceId::new("consumer"), Arc::new(values));
164        assert_eq!(
165            context.use_dependency(&dependency).unwrap_err().to_string(),
166            "relationship 'consumer.database' is not resolved for this module"
167        );
168
169        let mut values = HashMap::new();
170        values.insert(
171            dependency.slot_id(),
172            resolved(
173                "consumer",
174                "database",
175                RelationshipKind::Reference,
176                Arc::new("value".to_owned()),
177            ),
178        );
179        let context = SetupContext::new(ModuleInstanceId::new("consumer"), Arc::new(values));
180        assert_eq!(
181            context.use_dependency(&dependency).unwrap_err().to_string(),
182            "relationship 'consumer.database' is not available during setup"
183        );
184
185        let mut values = HashMap::new();
186        values.insert(
187            dependency.slot_id(),
188            resolved(
189                "consumer",
190                "database",
191                RelationshipKind::Dependency,
192                Arc::new(7_u8),
193            ),
194        );
195        let context = RunContext::new(ModuleInstanceId::new("consumer"), Arc::new(values));
196        assert_eq!(
197            context
198                .use_relationship::<String, _>(&dependency)
199                .unwrap_err()
200                .to_string(),
201            "provider type mismatch for relationship 'consumer.database'"
202        );
203    }
204}