guardia_rs_urn/
lib.rs

1use dotenv::dotenv;
2use regex::Regex;
3use std::env;
4use std::fmt;
5use uuid::Uuid;
6use thiserror::Error;
7
8const VALID_PRODUCTS: [&str; 6] = ["lke", "base", "tms", "psa", "bsa", "dwa"];
9const BANKING_PRODUCTS: [&str; 2] = ["psa", "bsa"];
10const DWA_PRODUCT: [&str; 1] = ["dwa"];
11const VALID_RAILS: [&str; 2] = ["p2p", "pix"];
12const VALID_PROVIDERS: [&str; 2] = ["guardia", "pix"];
13const VALID_PROTOCOLS: [&str; 4] = ["stream", "amqp", "obdc", "http"];
14
15#[derive(Debug, Error)]
16pub enum UrnError {
17    #[error("Invalid product: {0}")]
18    InvalidProduct(String),
19
20    #[error("Invalid cloud provider: {0}")]
21    InvalidCloudProvider(String),
22
23    #[error("Invalid region: {0}. Valid regions are: {1}")]
24    InvalidRegion(String, String),
25
26    #[error("Missing required field: {0}")]
27    MissingRequiredField(String),
28
29    #[error("Invalid rail: {0}")]
30    InvalidRail(String),
31
32    #[error("Invalid provider: {0}")]
33    InvalidProvider(String),
34
35    #[error("Invalid protocol: {0}")]
36    InvalidProtocol(String),
37
38    #[error("Invalid URN format")]
39    InvalidFormat,
40}
41
42#[derive(Debug)]
43pub struct GuardiaUrn {
44    pub organization_id: Option<String>,
45    pub tenant_id: Option<String>,
46    pub product: Option<String>,
47    pub rail: Option<String>,
48    pub provider: Option<String>,
49    pub protocol: Option<String>,
50    pub entity_type: Option<String>,
51    pub entity_id: Option<Uuid>,
52}
53
54impl GuardiaUrn {
55    pub fn parse(urn: &str) -> Result<Self, Vec<UrnError>> {
56        let regex = Regex::new(r"[:/]").unwrap();
57        let parts: Vec<&str> = regex.split(urn).collect();
58
59        if parts.len() < 9 || parts.len() > 11 {
60            return Err(vec![UrnError::InvalidFormat]);
61        }
62
63        if parts[0] != "urn" || parts[1] != "guardia" || parts[2] != "org" || parts[4] != "tenant" {
64            return Err(vec![UrnError::InvalidFormat]);
65        }
66
67        let organization_id = parts[3].to_string();
68        let tenant_id = parts[5].to_string();
69        let product = parts[6].to_string();
70        let entity_type = parts[7].to_string();
71        let entity_id = parts[8].to_string();
72
73        let mut builder = UrnBuilder::new()
74            .with_organization_id(&organization_id)
75            .with_tenant_id(&tenant_id)
76            .with_product(&product)
77            .with_entity_type(&entity_type)
78            .with_entity_id(&entity_id);
79
80        if parts.len() == 11 {
81            if BANKING_PRODUCTS.contains(&product.as_str()) {
82                let rail = parts[9].to_string();
83                builder = builder.with_rail(&rail);
84
85                let provider = parts[10].to_string();
86                builder = builder.with_provider(&provider);
87            } else if DWA_PRODUCT.contains(&product.as_str()) {
88                let protocol = parts[9].to_string();
89                builder = builder.with_protocol(&protocol);
90
91                let provider = parts[10].to_string();
92                builder = builder.with_provider(&provider);
93            } else {
94                return Err(vec![UrnError::InvalidFormat]);
95            }
96        } else if parts.len() > 11 {
97            return Err(vec![UrnError::InvalidFormat]);
98        }
99
100        builder.validate()?;
101
102        Ok(Self {
103            organization_id: builder.organization_id,
104            tenant_id: builder.tenant_id,
105            product: builder.product,
106            rail: builder.rail,
107            provider: builder.provider,
108            protocol: builder.protocol,
109            entity_type: builder.entity_type,
110            entity_id: builder.entity_id,
111        })
112    }
113}
114
115impl fmt::Display for GuardiaUrn {
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        let mut parts = vec![
118            format!(
119                "urn:guardia:org:{}",
120                self.organization_id.as_deref().unwrap_or("unknown")
121            ),
122            format!("tenant:{}", self.tenant_id.as_deref().unwrap_or("unknown")),
123            self.product.as_deref().unwrap_or("unknown").to_string(),
124            self.entity_type.as_deref().unwrap_or("unknown").to_string(),
125            self.entity_id.unwrap_or_default().to_string(),
126        ];
127
128        if let Some(rail) = &self.rail {
129            if !rail.trim().is_empty() {
130                parts.push(rail.clone());
131            }
132        }
133        if let Some(protocol) = &self.protocol {
134            if !protocol.trim().is_empty() {
135                parts.push(protocol.clone());
136            }
137        }
138        if let Some(provider) = &self.provider {
139            if !provider.trim().is_empty() {
140                parts.push(provider.clone());
141            }
142        }
143
144        write!(f, "{}", parts.join(":"))
145    }
146}
147
148#[derive(Debug)]
149pub struct UrnBuilder {
150    organization_id: Option<String>,
151    tenant_id: Option<String>,
152    product: Option<String>,
153    rail: Option<String>,
154    provider: Option<String>,
155    protocol: Option<String>,
156    entity_type: Option<String>,
157    entity_id: Option<Uuid>,
158}
159
160impl UrnBuilder {
161    pub fn new() -> Self {
162        Self {
163            organization_id: None,
164            tenant_id: None,
165            product: None,
166            rail: None,
167            provider: None,
168            protocol: None,
169            entity_type: None,
170            entity_id: None,
171        }
172    }
173
174    pub fn with_organization_id(mut self, organization_id: &str) -> Self {
175        self.organization_id = Some(organization_id.to_string());
176        self
177    }
178
179    pub fn with_tenant_id(mut self, tenant_id: &str) -> Self {
180        self.tenant_id = Some(tenant_id.to_string());
181        self
182    }
183
184    pub fn with_product(mut self, product: &str) -> Self {
185        self.product = Some(product.to_string());
186        self
187    }
188
189    pub fn with_rail(mut self, rail: &str) -> Self {
190        self.rail = Some(rail.to_string());
191        self
192    }
193
194    pub fn with_protocol(mut self, protocol: &str) -> Self {
195        self.protocol = Some(protocol.to_string());
196        self
197    }
198
199    pub fn with_provider(mut self, provider: &str) -> Self {
200        self.provider = Some(provider.to_string());
201        self
202    }
203
204    pub fn with_entity_type(mut self, entity_type: &str) -> Self {
205        self.entity_type = Some(entity_type.to_string());
206        self
207    }
208
209    pub fn with_entity_id(mut self, entity_id: &str) -> Self {
210        self.entity_id = Some(Uuid::parse_str(entity_id).unwrap_or_default());
211        self
212    }
213
214    fn validate_required_field(
215        field_value: &Option<String>,
216        field_name: &str,
217    ) -> Result<(), UrnError> {
218        match field_value {
219            Some(value) if !value.trim().is_empty() => Ok(()),
220            _ => Err(UrnError::MissingRequiredField(field_name.into())),
221        }
222    }
223
224    fn validate_product(&self) -> Result<(), UrnError> {
225        match &self.product {
226            Some(product) if VALID_PRODUCTS.contains(&product.as_str()) => Ok(()),
227            Some(product) => Err(UrnError::InvalidProduct(product.clone())),
228            None => Err(UrnError::MissingRequiredField("product".to_string())),
229        }
230    }
231
232    fn validate_rail(&self) -> Result<(), UrnError> {
233        match &self.rail {
234            Some(rail) if VALID_RAILS.contains(&rail.as_str()) => Ok(()),
235            Some(rail) => Err(UrnError::InvalidRail(rail.clone())),
236            None => Err(UrnError::MissingRequiredField("rail".to_string())),
237        }
238    }
239
240    fn validate_provider(&self) -> Result<(), UrnError> {
241        match &self.provider {
242            Some(provider) if VALID_PROVIDERS.contains(&provider.as_str()) => Ok(()),
243            Some(provider) => Err(UrnError::InvalidProvider(provider.clone())),
244            None => Err(UrnError::MissingRequiredField("provider".to_string())),
245        }
246    }
247
248    fn validate_protocol(&self) -> Result<(), UrnError> {
249        match &self.protocol {
250            Some(protocol) if VALID_PROTOCOLS.contains(&protocol.as_str()) => Ok(()),
251            Some(protocol) => Err(UrnError::InvalidProtocol(protocol.clone())),
252            None => Err(UrnError::MissingRequiredField("protocol".to_string())),
253        }
254    }
255
256    fn validate_dwa(&self) -> Result<(), Vec<UrnError>> {
257        let mut errors = Vec::new();
258
259        if let Err(e) = self.validate_no_rail() {
260            errors.push(e);
261        }
262
263        if let Err(e) = self.validate_protocol() {
264            errors.push(e);
265        }
266
267        if let Err(e) = self.validate_provider() {
268            errors.push(e);
269        }
270
271        if errors.is_empty() {
272            Ok(())
273        } else {
274            Err(errors)
275        }
276    }
277
278    fn validate_psa_bsa(&self) -> Result<(), Vec<UrnError>> {
279        let mut errors = Vec::new();
280
281        if let Err(e) = self.validate_no_protocol() {
282            errors.push(e);
283        }
284
285        if let Err(e) = self.validate_rail() {
286            errors.push(e);
287        }
288
289        if let Err(e) = self.validate_provider() {
290            errors.push(e);
291        }
292
293        if errors.is_empty() {
294            Ok(())
295        } else {
296            Err(errors)
297        }
298    }
299
300    fn validate_no_rail(&self) -> Result<(), UrnError> {
301        if let Some(rail) = &self.rail {
302            if !rail.trim().is_empty() {
303                return Err(UrnError::InvalidRail(format!(
304                    "Rail should not be provided for product '{}'",
305                    self.product.as_deref().unwrap_or("unknown")
306                )));
307            }
308        }
309        Ok(())
310    }
311
312    fn validate_no_protocol(&self) -> Result<(), UrnError> {
313        if self
314            .protocol
315            .as_deref()
316            .map_or(false, |p| !p.trim().is_empty())
317        {
318            return Err(UrnError::InvalidProtocol(format!(
319                "Protocol should not be provided for product '{}'",
320                self.product.as_deref().unwrap_or("unknown")
321            )));
322        }
323
324        Ok(())
325    }
326
327    fn validate_no_provider(&self) -> Result<(), UrnError> {
328        if self
329            .provider
330            .as_deref()
331            .map_or(false, |p| !p.trim().is_empty())
332        {
333            return Err(UrnError::InvalidProvider(format!(
334                "Provider should not be provided for product '{}'",
335                self.product.as_deref().unwrap_or("unknown")
336            )));
337        }
338        Ok(())
339    }
340
341    fn validate(&self) -> Result<(), Vec<UrnError>> {
342        let mut errors: Vec<_> = [
343            Self::validate_required_field(&self.organization_id, "organization_id"),
344            Self::validate_required_field(&self.tenant_id, "tenant_id"),
345            Self::validate_required_field(&self.entity_type, "entity_type"),
346            Self::validate_required_field(&self.entity_id.as_ref().map(|id| id.to_string()), "entity_id"),
347            self.validate_product(),
348        ]
349        .into_iter()
350        .filter_map(Result::err)
351        .collect();
352
353        match self.product.as_deref() {
354            Some(product) if BANKING_PRODUCTS.contains(&product) => {
355                if let Err(e) = self.validate_psa_bsa() {
356                    errors.extend(e);
357                }
358            }
359            Some(product) if DWA_PRODUCT.contains(&product) => {
360                if let Err(e) = self.validate_dwa() {
361                    errors.extend(e);
362                }
363            }
364            Some(_) | None => {
365                if let Err(e) = self.validate_no_rail() {
366                    errors.push(e);
367                }
368                if let Err(e) = self.validate_no_protocol() {
369                    errors.push(e);
370                }
371                if let Err(e) = self.validate_no_provider() {
372                    errors.push(e);
373                }
374            }
375        }
376
377        if errors.is_empty() {
378            Ok(())
379        } else {
380            Err(errors)
381        }
382    }
383
384    pub fn build(self) -> Result<String, Vec<UrnError>> {
385        self.validate()?;
386
387        Ok(GuardiaUrn {
388            organization_id: self.organization_id,
389            tenant_id: self.tenant_id,
390            product: self.product,
391            rail: self.rail,
392            provider: self.provider,
393            protocol: self.protocol,
394            entity_type: self.entity_type,
395            entity_id: self.entity_id,
396        }
397        .to_string())
398    }
399
400    pub fn from_env() -> Result<Self, UrnError> {
401        dotenv().ok();
402        let organization_id = env::var("ORGANIZATION_ID").ok();
403        let tenant_id = env::var("TENANT_ID").ok();
404        let product = env::var("PRODUCT").ok();
405        let rail = env::var("RAIL").ok();
406        let provider = env::var("PROVIDER").ok();
407        let protocol = env::var("PROTOCOL").ok();
408        let entity_id = Uuid::parse_str(&env::var("ENTITY_ID").ok().unwrap_or_default()).ok();
409        let entity_type = env::var("ENTITY_TYPE").ok();
410
411        Ok(Self {
412            organization_id,
413            tenant_id,
414            product,
415            rail,
416            provider,
417            protocol,
418            entity_type,
419            entity_id,
420        })
421    }
422}