Skip to main content

harmos_sdk/operations/
resources.rs

1/* ***
2 *
3 * Harmos
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * ***
8 */
9
10/* === Modules === */
11
12/* === Re-Exports === */
13
14/* === Imports === */
15
16// Standard Library Imports
17use std::any::{Any, TypeId, type_name};
18use std::collections::HashMap;
19use std::sync::{Arc, OnceLock};
20
21// Third Party Imports
22
23// Local Imports
24use super::CallContext;
25
26/* === Constants === */
27
28/* === Macros === */
29
30/* === Type Aliases === */
31
32/* === Structs === */
33
34#[derive(Clone, Default)]
35pub struct ResourceSet {
36    entries: Arc<HashMap<TypeId, Arc<dyn Any + Send + Sync>>>,
37}
38
39#[derive(Clone, Default)]
40pub struct ExecutionContext {
41    resources: ResourceSet,
42    pub call: CallContext,
43}
44
45#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
46pub struct AssignedOperation {
47    pub operation: String,
48    pub fingerprint: [u8; 32],
49    pub generation: u64,
50}
51
52#[derive(Clone, Default)]
53pub struct OperationAssignments {
54    entries: Arc<OnceLock<Vec<AssignedOperation>>>,
55}
56
57/* === Enums === */
58
59#[derive(Debug, thiserror::Error)]
60pub enum ResourceError {
61    #[error("resource `{name}` is already present")]
62    Duplicate { name: &'static str },
63
64    #[error("resource `{name}` is missing")]
65    Missing { name: &'static str },
66}
67
68/* === Traits === */
69
70/* === Implementations === */
71
72impl ResourceSet {
73    pub fn new() -> Self {
74        Self::default()
75    }
76
77    pub fn insert<T: Send + Sync + 'static>(&mut self, value: Arc<T>) -> Result<(), ResourceError> {
78        let key = TypeId::of::<T>();
79        if self.entries.contains_key(&key) {
80            return Err(ResourceError::Duplicate { name: type_name::<T>() });
81        }
82        Arc::make_mut(&mut self.entries).insert(key, value);
83        Ok(())
84    }
85
86    pub fn resource<T: Send + Sync + 'static>(&self) -> Result<Arc<T>, ResourceError> {
87        self.entries
88            .get(&TypeId::of::<T>())
89            .cloned()
90            .and_then(|entry| entry.downcast().ok())
91            .ok_or(ResourceError::Missing { name: type_name::<T>() })
92    }
93
94    pub fn replace<T: Send + Sync + 'static>(&mut self, value: Arc<T>) {
95        Arc::make_mut(&mut self.entries).insert(TypeId::of::<T>(), value);
96    }
97
98    pub fn contains(&self, resource: TypeId) -> bool {
99        self.entries.contains_key(&resource)
100    }
101}
102
103impl ExecutionContext {
104    #[doc(hidden)]
105    pub fn new(resources: ResourceSet, call: CallContext) -> Self {
106        Self { resources, call }
107    }
108
109    pub fn resource<T: Send + Sync + 'static>(&self) -> Result<Arc<T>, ResourceError> {
110        self.resources.resource::<T>()
111    }
112
113    #[doc(hidden)]
114    pub fn resources(&self) -> &ResourceSet {
115        &self.resources
116    }
117
118    #[doc(hidden)]
119    pub fn with_resources(&self, mut resources: ResourceSet) -> Self {
120        // Trusted Carrier Handles Retain Priority In Each Invocation Snapshot.
121        Arc::make_mut(&mut resources.entries).extend(self.resources.entries.iter().map(|(key, value)| (*key, value.clone())));
122        Self::new(resources, self.call.clone())
123    }
124}
125
126impl AssignedOperation {
127    pub fn encode(entries: &[Self]) -> Result<Vec<u8>, super::ServingError> {
128        postcard::to_allocvec(entries).map_err(|_| super::ServingError::Catalog)
129    }
130}
131
132impl OperationAssignments {
133    pub fn install(&self, bytes: &[u8]) -> Result<(), super::ServingError> {
134        let entries: Vec<AssignedOperation> =
135            if bytes.is_empty() { Vec::new() } else { postcard::from_bytes(bytes).map_err(|_| super::ServingError::Catalog)? };
136        let mut locals = std::collections::BTreeSet::new();
137        for entry in &entries {
138            harmos_domain::QualifiedId::parse(&entry.operation).map_err(|_| super::ServingError::Catalog)?;
139            let (_, local) = entry.operation.split_once('.').ok_or(super::ServingError::Catalog)?;
140            if entry.generation == 0 || !locals.insert(local) {
141                return Err(super::ServingError::Catalog);
142            }
143        }
144        if let Some(original) = self.entries.get() {
145            return if original == &entries { Ok(()) } else { Err(super::ServingError::Catalog) };
146        }
147        self.entries.set(entries).map_err(|_| super::ServingError::Catalog)
148    }
149
150    pub fn resolve<O: super::OperationDeclaration>(&self) -> Result<&AssignedOperation, super::ServingError> {
151        self.resolve_target(&super::Target::of::<O>())
152    }
153
154    pub fn resolve_target(&self, target: &super::Target) -> Result<&AssignedOperation, super::ServingError> {
155        let local = |entry: &&AssignedOperation| entry.operation.split_once('.').is_some_and(|(_, local)| local == target.id);
156        let entry = self
157            .entries
158            .get()
159            .and_then(|entries| entries.iter().find(local))
160            .ok_or(super::ServingError::Unassigned { operation: target.id })?;
161        if entry.fingerprint != (target.definition)().fingerprint.0 {
162            return Err(super::ServingError::Identity { operation: entry.operation.clone() });
163        }
164        Ok(entry)
165    }
166}
167
168/* === Public Functions === */
169
170/* === Private Functions === */
171
172/* === Tests === */
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    #[test]
179    fn independent_resource_snapshots() {
180        let mut resources = ResourceSet::new();
181        let value = Arc::new(7u32);
182        resources.insert(value.clone()).unwrap();
183        let snapshot = resources.clone();
184        resources.insert(Arc::new(9u64)).unwrap();
185        assert!(Arc::ptr_eq(&snapshot.resource::<u32>().unwrap(), &value));
186        assert!(matches!(snapshot.resource::<u64>(), Err(ResourceError::Missing { .. })));
187        assert!(matches!(resources.insert(Arc::new(8u32)), Err(ResourceError::Duplicate { .. })));
188        assert_eq!(*resources.resource::<u32>().unwrap(), 7);
189    }
190}