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}