sea_core/primitives/
resource.rs

1use crate::units::Unit;
2use crate::ConceptId;
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::collections::HashMap;
6use uuid::Uuid;
7
8/// Represents a quantifiable subject of value in enterprise models.
9///
10/// Resources are the "WHAT" - things that flow between entities,
11/// measured in specific units (units, kg, USD, etc.)
12///
13/// # Examples
14///
15/// Basic usage:
16///
17/// ```
18/// use sea_core::primitives::Resource;
19/// use sea_core::units::{Unit, Dimension};
20/// use rust_decimal::Decimal;
21///
22/// let units = Unit::new("units", "units", Dimension::Count, Decimal::from(1), "units");
23/// let product = Resource::new_with_namespace("Camera", units, "default".to_string());
24/// assert_eq!(product.name(), "Camera");
25/// assert_eq!(product.unit().symbol(), "units");
26/// assert_eq!(product.namespace(), "default");
27/// ```
28///
29/// With namespace:
30///
31/// ```
32/// use sea_core::primitives::Resource;
33/// use sea_core::units::{Unit, Dimension};
34/// use rust_decimal::Decimal;
35///
36/// let currency = Unit::new("currency", "currency", Dimension::Currency, Decimal::from(1), "currency");
37/// let usd = Resource::new_with_namespace("USD", currency, "finance");
38/// assert_eq!(usd.namespace(), "finance");
39/// ```
40///
41/// With custom attributes:
42///
43/// ```
44/// use sea_core::primitives::Resource;
45/// use sea_core::units::{Unit, Dimension};
46/// use rust_decimal::Decimal;
47/// use serde_json::json;
48///
49/// let kg = Unit::new("kg", "kilogram", Dimension::Mass, Decimal::from(1), "kg");
50/// let mut gold = Resource::new_with_namespace("Gold", kg, "default".to_string());
51/// gold.set_attribute("purity", json!(0.999));
52/// gold.set_attribute("origin", json!("South Africa"));
53///
54/// assert_eq!(gold.get_attribute("purity"), Some(&json!(0.999)));
55/// ```
56///
57/// Serialization:
58///
59/// ```
60/// use sea_core::primitives::Resource;
61/// use sea_core::units::{Unit, Dimension};
62/// use rust_decimal::Decimal;
63///
64/// let oz = Unit::new("oz", "ounce", Dimension::Mass, Decimal::new(28349523, 9), "oz");
65/// let resource = Resource::new_with_namespace("Silver", oz, "default".to_string());
66/// let json = serde_json::to_string(&resource).unwrap();
67/// let deserialized: Resource = serde_json::from_str(&json).unwrap();
68/// assert_eq!(resource.name(), deserialized.name());
69/// assert_eq!(resource.unit().symbol(), deserialized.unit().symbol());
70/// ```
71#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
72pub struct Resource {
73    id: ConceptId,
74    name: String,
75    unit: Unit,
76    namespace: String,
77    attributes: HashMap<String, Value>,
78}
79
80impl Resource {
81    /// Creates a new Resource (deprecated - use new_with_namespace).
82    ///
83    /// # Examples
84    ///
85    /// ```
86    /// use sea_core::primitives::Resource;
87    /// use sea_core::units::{Unit, Dimension};
88    /// use rust_decimal::Decimal;
89    ///
90    /// let kg = Unit::new("kg", "kilogram", Dimension::Mass, Decimal::from(1), "kg");
91    /// let resource = Resource::new_with_namespace("Camera", kg, "default".to_string());
92    /// assert_eq!(resource.name(), "Camera");
93    /// assert_eq!(resource.unit().symbol(), "kg");
94    /// ```
95    #[deprecated(note = "Use new_with_namespace instead")]
96    pub fn new(name: impl Into<String>, unit: Unit) -> Self {
97        let name = name.into();
98        let namespace = "default".to_string();
99        Self {
100            id: ConceptId::from_concept(&namespace, &name),
101            name,
102            unit,
103            namespace,
104            attributes: HashMap::new(),
105        }
106    }
107
108    /// Creates a new Resource with a specific namespace.
109    ///
110    /// # Examples
111    ///
112    /// ```
113    /// use sea_core::primitives::Resource;
114    /// use sea_core::units::{Unit, Dimension};
115    /// use rust_decimal::Decimal;
116    ///
117    /// let usd = Unit::new("USD", "US Dollar", Dimension::Currency, Decimal::from(1), "USD");
118    /// let resource = Resource::new_with_namespace("USD", usd, "finance");
119    /// assert_eq!(resource.namespace(), "finance");
120    /// ```
121    pub fn new_with_namespace(
122        name: impl Into<String>,
123        unit: Unit,
124        namespace: impl Into<String>,
125    ) -> Self {
126        let namespace = namespace.into();
127        let name = name.into();
128        let id = ConceptId::from_concept(&namespace, &name);
129
130        Self {
131            id,
132            name,
133            unit,
134            namespace,
135            attributes: HashMap::new(),
136        }
137    }
138
139    /// Creates a Resource from a legacy UUID for backward compatibility.
140    pub fn from_legacy_uuid(
141        uuid: Uuid,
142        name: impl Into<String>,
143        unit: Unit,
144        namespace: impl Into<String>,
145    ) -> Self {
146        Self {
147            id: ConceptId::from_legacy_uuid(uuid),
148            name: name.into(),
149            unit,
150            namespace: namespace.into(),
151            attributes: HashMap::new(),
152        }
153    }
154
155    /// Returns the resource's unique identifier.
156    pub fn id(&self) -> &ConceptId {
157        &self.id
158    }
159
160    /// Returns the resource's name.
161    pub fn name(&self) -> &str {
162        &self.name
163    }
164
165    /// Returns the resource's unit of measurement.
166    pub fn unit(&self) -> &Unit {
167        &self.unit
168    }
169
170    /// Returns the resource's unit symbol (for backward compatibility).
171    pub fn unit_symbol(&self) -> &str {
172        self.unit.symbol()
173    }
174
175    /// Returns the resource's namespace.
176    pub fn namespace(&self) -> &str {
177        &self.namespace
178    }
179
180    /// Sets a custom attribute.
181    ///
182    /// # Examples
183    ///
184    /// ```
185    /// use sea_core::primitives::Resource;
186    /// use sea_core::units::unit_from_string;
187    /// use serde_json::json;
188    ///
189    /// let mut resource = Resource::new_with_namespace("Gold", unit_from_string("kg"), "default".to_string());
190    /// resource.set_attribute("purity", json!(0.999));
191    /// assert_eq!(resource.get_attribute("purity"), Some(&json!(0.999)));
192    /// ```
193    pub fn set_attribute(&mut self, key: impl Into<String>, value: Value) {
194        self.attributes.insert(key.into(), value);
195    }
196
197    /// Gets a custom attribute.
198    ///
199    /// Returns `None` if the attribute doesn't exist.
200    ///
201    /// # Examples
202    ///
203    /// ```
204    /// use sea_core::primitives::Resource;
205    /// use sea_core::units::unit_from_string;
206    /// use serde_json::json;
207    ///
208    /// let mut resource = Resource::new_with_namespace("Gold", unit_from_string("kg"), "default".to_string());
209    /// resource.set_attribute("purity", json!(0.999));
210    /// assert_eq!(resource.get_attribute("purity"), Some(&json!(0.999)));
211    /// assert_eq!(resource.get_attribute("missing"), None);
212    /// ```
213    pub fn get_attribute(&self, key: &str) -> Option<&Value> {
214        self.attributes.get(key)
215    }
216
217    /// Returns all attributes as a reference.
218    ///
219    /// # Examples
220    ///
221    /// ```
222    /// use sea_core::primitives::Resource;
223    /// use sea_core::units::unit_from_string;
224    /// use serde_json::json;
225    ///
226    /// let mut resource = Resource::new_with_namespace("Gold", unit_from_string("kg"), "default".to_string());
227    /// resource.set_attribute("purity", json!(0.999));
228    /// resource.set_attribute("origin", json!("South Africa"));
229    ///
230    /// let attrs = resource.attributes();
231    /// assert_eq!(attrs.len(), 2);
232    /// assert_eq!(attrs.get("purity"), Some(&json!(0.999)));
233    /// ```
234    pub fn attributes(&self) -> &HashMap<String, Value> {
235        &self.attributes
236    }
237}