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}