1use crate::middleware::{Middleware, Next};
33use crate::{Error, HttpRequest, HttpResponse};
34use async_trait::async_trait;
35use serde::{Deserialize, Serialize};
36use std::collections::HashMap;
37use std::sync::Arc;
38use std::sync::atomic::{AtomicU64, Ordering};
39use std::time::{SystemTime, UNIX_EPOCH};
40use tokio::sync::RwLock;
41
42#[derive(Debug, Clone, Copy, Default)]
48pub enum IdGenerationStrategy {
49 #[default]
51 UuidV4,
52 UuidV7,
54 Snowflake,
56 Ulid,
58 Short,
60}
61
62static SEQUENCE_COUNTER: AtomicU64 = AtomicU64::new(0);
64
65impl IdGenerationStrategy {
66 pub fn generate(&self) -> String {
68 match self {
69 IdGenerationStrategy::UuidV4 => uuid::Uuid::new_v4().to_string(),
70 IdGenerationStrategy::UuidV7 => {
71 let timestamp = SystemTime::now()
73 .duration_since(UNIX_EPOCH)
74 .unwrap_or_default()
75 .as_millis() as u64;
76
77 let random_bytes: [u8; 10] = rand_bytes();
78 let mut bytes = [0u8; 16];
79
80 bytes[0..6].copy_from_slice(×tamp.to_be_bytes()[2..8]);
82 bytes[6] = (random_bytes[0] & 0x0F) | 0x70;
84 bytes[7] = random_bytes[1];
85 bytes[8] = (random_bytes[2] & 0x3F) | 0x80;
87 bytes[9..16].copy_from_slice(&random_bytes[3..10]);
88
89 uuid::Uuid::from_bytes(bytes).to_string()
90 }
91 IdGenerationStrategy::Snowflake => {
92 let timestamp = SystemTime::now()
93 .duration_since(UNIX_EPOCH)
94 .unwrap_or_default()
95 .as_millis() as u64;
96
97 let seq = SEQUENCE_COUNTER.fetch_add(1, Ordering::SeqCst) & 0xFFF;
98 let machine_id = std::process::id() as u64 & 0x3FF;
99
100 let id = ((timestamp & 0x1FFFFFFFFFF) << 22) | (machine_id << 12) | seq;
102 format!("{:016x}", id)
103 }
104 IdGenerationStrategy::Ulid => {
105 let timestamp = SystemTime::now()
106 .duration_since(UNIX_EPOCH)
107 .unwrap_or_default()
108 .as_millis() as u64;
109
110 let random: [u8; 10] = rand_bytes();
111
112 let mut result = String::with_capacity(26);
114 let alphabet = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ";
115
116 for i in (0..10).rev() {
118 let shift = i * 5;
119 if shift < 48 {
120 let idx = ((timestamp >> shift) & 0x1F) as usize;
121 result.push(alphabet[idx] as char);
122 }
123 }
124
125 let mut bits: u128 = 0;
127 for &b in &random {
128 bits = (bits << 8) | b as u128;
129 }
130 for i in (0..16).rev() {
131 let idx = ((bits >> (i * 5)) & 0x1F) as usize;
132 result.push(alphabet[idx] as char);
133 }
134
135 result
136 }
137 IdGenerationStrategy::Short => {
138 let random: [u8; 6] = rand_bytes::<6>();
139 let alphabet = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
140 let mut result = String::with_capacity(8);
141
142 for b in random {
143 result.push(alphabet[(b % 62) as usize] as char);
144 }
145 let ts = SystemTime::now()
147 .duration_since(UNIX_EPOCH)
148 .unwrap_or_default()
149 .as_nanos() as u64;
150 result.push(alphabet[(ts % 62) as usize] as char);
151 result.push(alphabet[((ts / 62) % 62) as usize] as char);
152
153 result
154 }
155 }
156 }
157}
158
159fn random_hex_id(hex_len: usize) -> String {
162 let mut s = String::with_capacity(hex_len);
163 while s.len() < hex_len {
164 let bytes: [u8; 16] = rand_bytes();
165 for b in bytes {
166 use std::fmt::Write;
167 let _ = write!(s, "{:02x}", b);
168 }
169 }
170 s.truncate(hex_len);
171 s
172}
173
174static RAND_BYTES_COUNTER: AtomicU64 = AtomicU64::new(0);
180
181fn rand_bytes<const N: usize>() -> [u8; N] {
182 let mut bytes = [0u8; N];
183 let seed = SystemTime::now()
188 .duration_since(UNIX_EPOCH)
189 .unwrap_or_default()
190 .as_nanos() as u64;
191 let counter = RAND_BYTES_COUNTER.fetch_add(1, Ordering::Relaxed);
192
193 let mut state = seed ^ 0xDEADBEEF ^ counter.wrapping_mul(0x9E3779B97F4A7C15);
194 for b in bytes.iter_mut() {
195 state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
196 *b = (state >> 33) as u8;
197 }
198 bytes
199}
200
201pub mod headers {
207 pub const CORRELATION_ID: &str = "X-Correlation-ID";
209 pub const REQUEST_ID: &str = "X-Request-ID";
211 pub const TRACE_PARENT: &str = "traceparent";
213 pub const TRACE_STATE: &str = "tracestate";
215 pub const B3_TRACE_ID: &str = "X-B3-TraceId";
217 pub const B3_SPAN_ID: &str = "X-B3-SpanId";
219 pub const B3_PARENT_SPAN_ID: &str = "X-B3-ParentSpanId";
221 pub const B3_SAMPLED: &str = "X-B3-Sampled";
223 pub const CAUSATION_ID: &str = "X-Causation-ID";
225 pub const SESSION_ID: &str = "X-Session-ID";
227}
228
229#[derive(Debug, Clone, Serialize, Deserialize)]
231pub struct CorrelationContext {
232 pub correlation_id: String,
234 pub request_id: String,
236 #[serde(skip_serializing_if = "Option::is_none")]
238 pub trace_id: Option<String>,
239 #[serde(skip_serializing_if = "Option::is_none")]
241 pub span_id: Option<String>,
242 #[serde(skip_serializing_if = "Option::is_none")]
244 pub parent_span_id: Option<String>,
245 #[serde(skip_serializing_if = "Option::is_none")]
247 pub causation_id: Option<String>,
248 #[serde(skip_serializing_if = "Option::is_none")]
250 pub session_id: Option<String>,
251 #[serde(skip_serializing_if = "Option::is_none")]
253 pub service: Option<String>,
254 #[serde(skip_serializing_if = "Option::is_none")]
256 pub service_version: Option<String>,
257 #[serde(skip_serializing_if = "Option::is_none")]
259 pub user_id: Option<String>,
260 #[serde(skip_serializing_if = "Option::is_none")]
262 pub tenant_id: Option<String>,
263 #[serde(skip_serializing_if = "HashMap::is_empty", default)]
265 pub baggage: HashMap<String, String>,
266 pub sampled: bool,
268 pub created_at: u64,
270}
271
272impl Default for CorrelationContext {
273 fn default() -> Self {
274 Self::new()
275 }
276}
277
278impl CorrelationContext {
279 pub fn new() -> Self {
281 let strategy = IdGenerationStrategy::UuidV4;
282 Self {
283 correlation_id: strategy.generate(),
284 request_id: strategy.generate(),
285 trace_id: None,
286 span_id: None,
287 parent_span_id: None,
288 causation_id: None,
289 session_id: None,
290 service: None,
291 service_version: None,
292 user_id: None,
293 tenant_id: None,
294 baggage: HashMap::new(),
295 sampled: true,
296 created_at: SystemTime::now()
297 .duration_since(UNIX_EPOCH)
298 .unwrap_or_default()
299 .as_millis() as u64,
300 }
301 }
302
303 pub fn with_strategy(strategy: IdGenerationStrategy) -> Self {
305 Self {
306 correlation_id: strategy.generate(),
307 request_id: strategy.generate(),
308 ..Default::default()
309 }
310 }
311
312 pub fn child(&self) -> Self {
314 let strategy = IdGenerationStrategy::UuidV4;
315 Self {
316 correlation_id: self.correlation_id.clone(),
317 request_id: strategy.generate(),
318 trace_id: self.trace_id.clone(),
319 span_id: Some(strategy.generate()),
320 parent_span_id: self.span_id.clone(),
321 causation_id: Some(self.request_id.clone()),
322 session_id: self.session_id.clone(),
323 service: self.service.clone(),
324 service_version: self.service_version.clone(),
325 user_id: self.user_id.clone(),
326 tenant_id: self.tenant_id.clone(),
327 baggage: self.baggage.clone(),
328 sampled: self.sampled,
329 created_at: SystemTime::now()
330 .duration_since(UNIX_EPOCH)
331 .unwrap_or_default()
332 .as_millis() as u64,
333 }
334 }
335
336 pub fn correlation_id(mut self, id: impl Into<String>) -> Self {
338 self.correlation_id = id.into();
339 self
340 }
341
342 pub fn trace_id(mut self, id: impl Into<String>) -> Self {
344 self.trace_id = Some(id.into());
345 self
346 }
347
348 pub fn span_id(mut self, id: impl Into<String>) -> Self {
350 self.span_id = Some(id.into());
351 self
352 }
353
354 pub fn with_causation(mut self, id: impl Into<String>) -> Self {
356 self.causation_id = Some(id.into());
357 self
358 }
359
360 pub fn with_session(mut self, id: impl Into<String>) -> Self {
362 self.session_id = Some(id.into());
363 self
364 }
365
366 pub fn with_service(mut self, service: impl Into<String>) -> Self {
368 self.service = Some(service.into());
369 self
370 }
371
372 pub fn with_service_version(mut self, version: impl Into<String>) -> Self {
374 self.service_version = Some(version.into());
375 self
376 }
377
378 pub fn with_user_id(mut self, user_id: impl Into<String>) -> Self {
380 self.user_id = Some(user_id.into());
381 self
382 }
383
384 pub fn with_tenant(mut self, tenant_id: impl Into<String>) -> Self {
386 self.tenant_id = Some(tenant_id.into());
387 self
388 }
389
390 pub fn with_baggage(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
392 self.baggage.insert(key.into(), value.into());
393 self
394 }
395
396 pub fn with_sampled(mut self, sampled: bool) -> Self {
398 self.sampled = sampled;
399 self
400 }
401
402 pub fn from_request(req: &HttpRequest) -> Self {
404 let mut ctx = Self::new();
405
406 if let Some(id) = req.headers.get(headers::CORRELATION_ID).or_else(|| {
408 req.headers
409 .get(headers::CORRELATION_ID.to_lowercase().as_str())
410 }) {
411 ctx.correlation_id = id.to_owned();
412 }
413
414 if let Some(id) = req
416 .headers
417 .get(headers::REQUEST_ID)
418 .or_else(|| req.headers.get(headers::REQUEST_ID.to_lowercase().as_str()))
419 {
420 ctx.request_id = id.to_owned();
421 }
422
423 if let Some(traceparent) = req.headers.get(headers::TRACE_PARENT)
425 && let Some((trace_id, span_id, sampled)) = parse_traceparent(traceparent)
426 {
427 ctx.trace_id = Some(trace_id);
428 ctx.parent_span_id = Some(span_id);
429 ctx.sampled = sampled;
430 ctx.span_id = Some(IdGenerationStrategy::Short.generate());
432 }
433
434 if ctx.trace_id.is_none()
436 && let Some(id) = req.headers.get(headers::B3_TRACE_ID)
437 {
438 ctx.trace_id = Some(id.to_owned());
439 }
440 if ctx.span_id.is_none()
441 && let Some(id) = req.headers.get(headers::B3_SPAN_ID)
442 {
443 ctx.parent_span_id = Some(id.to_owned());
444 ctx.span_id = Some(IdGenerationStrategy::Short.generate());
445 }
446
447 if let Some(id) = req.headers.get(headers::CAUSATION_ID) {
449 ctx.causation_id = Some(id.to_owned());
450 }
451
452 if let Some(id) = req.headers.get(headers::SESSION_ID) {
454 ctx.session_id = Some(id.to_owned());
455 }
456
457 ctx
458 }
459
460 pub fn inject_into_request(&self, req: &mut HttpRequest) {
462 req.headers
466 .insert(headers::CORRELATION_ID, self.correlation_id.as_str());
467 req.headers
468 .insert(headers::REQUEST_ID, self.request_id.as_str());
469
470 if let Some(ref trace_id) = self.trace_id {
471 let span_id = self.span_id.as_deref().unwrap_or("0000000000000000");
472 let sampled = if self.sampled { "01" } else { "00" };
473 let traceparent = format!("00-{trace_id}-{span_id}-{sampled}");
474 req.headers.insert(headers::TRACE_PARENT, traceparent);
475
476 req.headers.insert(headers::B3_TRACE_ID, trace_id.as_str());
478 req.headers.insert(headers::B3_SPAN_ID, span_id);
479 if let Some(ref parent) = self.parent_span_id {
480 req.headers
481 .insert(headers::B3_PARENT_SPAN_ID, parent.as_str());
482 }
483 req.headers
484 .insert(headers::B3_SAMPLED, if self.sampled { "1" } else { "0" });
485 }
486
487 if let Some(ref causation_id) = self.causation_id {
488 req.headers
489 .insert(headers::CAUSATION_ID, causation_id.as_str());
490 }
491
492 if let Some(ref session_id) = self.session_id {
493 req.headers.insert(headers::SESSION_ID, session_id.as_str());
494 }
495 }
496
497 pub fn inject_into_response(&self, res: &mut HttpResponse) {
499 res.headers.insert(
500 headers::CORRELATION_ID.to_string(),
501 self.correlation_id.clone(),
502 );
503 res.headers
504 .insert(headers::REQUEST_ID.to_string(), self.request_id.clone());
505
506 if let Some(ref trace_id) = self.trace_id {
507 let span_id = self.span_id.as_deref().unwrap_or("0000000000000000");
508 let sampled = if self.sampled { "01" } else { "00" };
509 let traceparent = format!("00-{}-{}-{}", trace_id, span_id, sampled);
510 res.headers
511 .insert(headers::TRACE_PARENT.to_string(), traceparent);
512 }
513 }
514
515 pub fn to_traceparent(&self) -> Option<String> {
517 let trace_id = self.trace_id.as_ref()?;
518 let span_id = self.span_id.as_deref().unwrap_or("0000000000000000");
519 let sampled = if self.sampled { "01" } else { "00" };
520 Some(format!("00-{}-{}-{}", trace_id, span_id, sampled))
521 }
522}
523
524fn parse_traceparent(value: &str) -> Option<(String, String, bool)> {
526 let parts: Vec<&str> = value.split('-').collect();
527 if parts.len() >= 4 && parts[0] == "00" {
528 let trace_id = parts[1].to_string();
529 let span_id = parts[2].to_string();
530 let sampled = parts[3] == "01";
531 Some((trace_id, span_id, sampled))
532 } else {
533 None
534 }
535}
536
537#[derive(Debug, Clone, Serialize, Deserialize)]
543pub struct CorrelatedError {
544 pub error_id: String,
546 pub context: CorrelationContext,
548 pub message: String,
550 #[serde(skip_serializing_if = "Option::is_none")]
552 pub code: Option<String>,
553 #[serde(skip_serializing_if = "Option::is_none")]
555 pub error_type: Option<String>,
556 pub status: u16,
558 #[serde(skip_serializing_if = "Option::is_none")]
560 pub source_service: Option<String>,
561 #[serde(skip_serializing_if = "Option::is_none")]
563 pub source_location: Option<String>,
564 #[serde(skip_serializing_if = "Vec::is_empty", default)]
566 pub causation_chain: Vec<String>,
567 #[serde(skip_serializing_if = "Vec::is_empty", default)]
569 pub related_errors: Vec<String>,
570 #[serde(skip_serializing_if = "Option::is_none")]
572 pub stack_trace: Option<String>,
573 #[serde(skip_serializing_if = "HashMap::is_empty", default)]
575 pub metadata: HashMap<String, serde_json::Value>,
576 pub timestamp: u64,
578 #[serde(skip_serializing_if = "Option::is_none")]
580 pub retry_info: Option<RetryInfo>,
581}
582
583#[derive(Debug, Clone, Serialize, Deserialize)]
585pub struct RetryInfo {
586 pub retryable: bool,
588 pub retry_delay_ms: Option<u64>,
590 pub max_retries: Option<u32>,
592 pub current_attempt: u32,
594}
595
596impl CorrelatedError {
597 pub fn new(message: impl Into<String>) -> Self {
599 Self {
600 error_id: IdGenerationStrategy::UuidV4.generate(),
601 context: CorrelationContext::new(),
602 message: message.into(),
603 code: None,
604 error_type: None,
605 status: 500,
606 source_service: None,
607 source_location: None,
608 causation_chain: Vec::new(),
609 related_errors: Vec::new(),
610 stack_trace: None,
611 metadata: HashMap::new(),
612 timestamp: SystemTime::now()
613 .duration_since(UNIX_EPOCH)
614 .unwrap_or_default()
615 .as_millis() as u64,
616 retry_info: None,
617 }
618 }
619
620 pub fn from_error(error: &Error, context: CorrelationContext) -> Self {
622 let status = error.status_code();
623 let error_type = match error {
624 Error::BadRequest(_) => "BAD_REQUEST",
625 Error::Unauthorized(_) => "UNAUTHORIZED",
626 Error::Forbidden(_) => "FORBIDDEN",
627 Error::NotFound(_) => "NOT_FOUND",
628 Error::Validation(_) => "VALIDATION_ERROR",
629 Error::Internal(_) => "INTERNAL_ERROR",
630 Error::Conflict(_) => "CONFLICT",
631 Error::TooManyRequests(_) => "RATE_LIMITED",
632 Error::ServiceUnavailable(_) => "SERVICE_UNAVAILABLE",
633 Error::RequestTimeout(_) => "TIMEOUT",
634 _ => "ERROR",
635 };
636
637 Self {
638 error_id: IdGenerationStrategy::UuidV4.generate(),
639 context,
640 message: error.to_string(),
641 code: None,
642 error_type: Some(error_type.to_string()),
643 status,
644 source_service: None,
645 source_location: None,
646 causation_chain: Vec::new(),
647 related_errors: Vec::new(),
648 stack_trace: None,
649 metadata: HashMap::new(),
650 timestamp: SystemTime::now()
651 .duration_since(UNIX_EPOCH)
652 .unwrap_or_default()
653 .as_millis() as u64,
654 retry_info: None,
655 }
656 }
657
658 pub fn with_context(mut self, context: CorrelationContext) -> Self {
660 self.context = context;
661 self
662 }
663
664 pub fn with_code(mut self, code: impl Into<String>) -> Self {
666 self.code = Some(code.into());
667 self
668 }
669
670 pub fn with_type(mut self, error_type: impl Into<String>) -> Self {
672 self.error_type = Some(error_type.into());
673 self
674 }
675
676 pub fn with_status(mut self, status: u16) -> Self {
678 self.status = status;
679 self
680 }
681
682 pub fn with_source_service(mut self, service: impl Into<String>) -> Self {
684 self.source_service = Some(service.into());
685 self
686 }
687
688 pub fn with_source_location(mut self, location: impl Into<String>) -> Self {
690 self.source_location = Some(location.into());
691 self
692 }
693
694 pub fn caused_by(mut self, cause: impl Into<String>) -> Self {
696 self.causation_chain.push(cause.into());
697 self
698 }
699
700 pub fn related_to(mut self, error_id: impl Into<String>) -> Self {
702 self.related_errors.push(error_id.into());
703 self
704 }
705
706 pub fn with_stack_trace(mut self, trace: impl Into<String>) -> Self {
708 self.stack_trace = Some(trace.into());
709 self
710 }
711
712 pub fn with_metadata(mut self, key: impl Into<String>, value: impl Serialize) -> Self {
714 if let Ok(json_value) = serde_json::to_value(value) {
715 self.metadata.insert(key.into(), json_value);
716 }
717 self
718 }
719
720 pub fn with_retry_info(mut self, info: RetryInfo) -> Self {
722 self.retry_info = Some(info);
723 self
724 }
725
726 pub fn retryable(mut self, delay_ms: u64, max_retries: u32) -> Self {
728 self.retry_info = Some(RetryInfo {
729 retryable: true,
730 retry_delay_ms: Some(delay_ms),
731 max_retries: Some(max_retries),
732 current_attempt: 0,
733 });
734 self
735 }
736
737 pub fn to_json(&self) -> String {
739 serde_json::to_string_pretty(self).unwrap_or_else(|_| {
740 format!(
741 r#"{{"error_id":"{}","message":"{}","status":{}}}"#,
742 self.error_id, self.message, self.status
743 )
744 })
745 }
746}
747
748pub struct ErrorRegistry {
754 max_size: usize,
756 errors: RwLock<HashMap<String, CorrelatedError>>,
758 by_correlation: RwLock<HashMap<String, Vec<String>>>,
760 by_trace: RwLock<HashMap<String, Vec<String>>>,
762}
763
764impl ErrorRegistry {
765 pub fn new(max_size: usize) -> Self {
767 Self {
768 max_size,
769 errors: RwLock::new(HashMap::new()),
770 by_correlation: RwLock::new(HashMap::new()),
771 by_trace: RwLock::new(HashMap::new()),
772 }
773 }
774
775 pub async fn register(&self, error: CorrelatedError) {
777 let error_id = error.error_id.clone();
778 let correlation_id = error.context.correlation_id.clone();
779 let trace_id = error.context.trace_id.clone();
780
781 let mut errors = self.errors.write().await;
783 let evicted = if errors.len() >= self.max_size {
784 errors
788 .keys()
789 .next()
790 .cloned()
791 .and_then(|id| errors.remove(&id).map(|e| (id, e)))
792 } else {
793 None
794 };
795 errors.insert(error_id.clone(), error);
796 drop(errors);
797
798 if let Some((evicted_id, evicted)) = evicted {
801 let mut by_correlation = self.by_correlation.write().await;
802 if let Some(ids) = by_correlation.get_mut(&evicted.context.correlation_id) {
803 ids.retain(|id| id != &evicted_id);
804 if ids.is_empty() {
805 by_correlation.remove(&evicted.context.correlation_id);
806 }
807 }
808 drop(by_correlation);
809
810 if let Some(ref evicted_trace) = evicted.context.trace_id {
811 let mut by_trace = self.by_trace.write().await;
812 if let Some(ids) = by_trace.get_mut(evicted_trace) {
813 ids.retain(|id| id != &evicted_id);
814 if ids.is_empty() {
815 by_trace.remove(evicted_trace);
816 }
817 }
818 }
819 }
820
821 let mut by_correlation = self.by_correlation.write().await;
823 by_correlation
824 .entry(correlation_id)
825 .or_insert_with(Vec::new)
826 .push(error_id.clone());
827 drop(by_correlation);
828
829 if let Some(trace_id) = trace_id {
831 let mut by_trace = self.by_trace.write().await;
832 by_trace
833 .entry(trace_id)
834 .or_insert_with(Vec::new)
835 .push(error_id);
836 }
837 }
838
839 pub async fn get(&self, error_id: &str) -> Option<CorrelatedError> {
841 self.errors.read().await.get(error_id).cloned()
842 }
843
844 pub async fn get_by_correlation(&self, correlation_id: &str) -> Vec<CorrelatedError> {
846 let by_correlation = self.by_correlation.read().await;
847 let error_ids = by_correlation.get(correlation_id);
848
849 if let Some(ids) = error_ids {
850 let errors = self.errors.read().await;
851 ids.iter()
852 .filter_map(|id| errors.get(id).cloned())
853 .collect()
854 } else {
855 Vec::new()
856 }
857 }
858
859 pub async fn get_by_trace(&self, trace_id: &str) -> Vec<CorrelatedError> {
861 let by_trace = self.by_trace.read().await;
862 let error_ids = by_trace.get(trace_id);
863
864 if let Some(ids) = error_ids {
865 let errors = self.errors.read().await;
866 ids.iter()
867 .filter_map(|id| errors.get(id).cloned())
868 .collect()
869 } else {
870 Vec::new()
871 }
872 }
873
874 pub async fn build_causation_tree(&self, error_id: &str) -> Option<ErrorTree> {
876 let error = self.get(error_id).await?;
877
878 let mut children = Vec::new();
879 for child_id in &error.related_errors {
880 if let Some(child_tree) = Box::pin(self.build_causation_tree(child_id)).await {
881 children.push(child_tree);
882 }
883 }
884
885 Some(ErrorTree { error, children })
886 }
887
888 pub async fn clear(&self) {
890 self.errors.write().await.clear();
891 self.by_correlation.write().await.clear();
892 self.by_trace.write().await.clear();
893 }
894
895 pub async fn len(&self) -> usize {
897 self.errors.read().await.len()
898 }
899
900 pub async fn is_empty(&self) -> bool {
902 self.errors.read().await.is_empty()
903 }
904}
905
906impl Default for ErrorRegistry {
907 fn default() -> Self {
908 Self::new(10000)
909 }
910}
911
912#[derive(Debug, Clone, Serialize)]
914pub struct ErrorTree {
915 pub error: CorrelatedError,
917 pub children: Vec<ErrorTree>,
919}
920
921#[derive(Debug, Clone)]
927pub struct CorrelationConfig {
928 pub id_strategy: IdGenerationStrategy,
930 pub service_name: Option<String>,
932 pub service_version: Option<String>,
934 pub generate_trace_id: bool,
936 pub propagate_in_response: bool,
938 pub correlation_header: String,
940 pub request_header: String,
942}
943
944impl Default for CorrelationConfig {
945 fn default() -> Self {
946 Self {
947 id_strategy: IdGenerationStrategy::UuidV4,
948 service_name: None,
949 service_version: None,
950 generate_trace_id: true,
951 propagate_in_response: true,
952 correlation_header: headers::CORRELATION_ID.to_string(),
953 request_header: headers::REQUEST_ID.to_string(),
954 }
955 }
956}
957
958impl CorrelationConfig {
959 pub fn new() -> Self {
961 Self::default()
962 }
963
964 pub fn service(mut self, name: impl Into<String>) -> Self {
966 self.service_name = Some(name.into());
967 self
968 }
969
970 pub fn version(mut self, version: impl Into<String>) -> Self {
972 self.service_version = Some(version.into());
973 self
974 }
975
976 pub fn strategy(mut self, strategy: IdGenerationStrategy) -> Self {
978 self.id_strategy = strategy;
979 self
980 }
981
982 pub fn generate_traces(mut self, enabled: bool) -> Self {
984 self.generate_trace_id = enabled;
985 self
986 }
987
988 pub fn propagate_response(mut self, enabled: bool) -> Self {
990 self.propagate_in_response = enabled;
991 self
992 }
993}
994
995pub struct CorrelationMiddleware {
997 config: CorrelationConfig,
998 registry: Option<Arc<ErrorRegistry>>,
999}
1000
1001impl CorrelationMiddleware {
1002 pub fn new(config: CorrelationConfig) -> Self {
1004 Self {
1005 config,
1006 registry: None,
1007 }
1008 }
1009
1010 pub fn default_config() -> Self {
1012 Self::new(CorrelationConfig::default())
1013 }
1014
1015 pub fn with_registry(mut self, registry: Arc<ErrorRegistry>) -> Self {
1017 self.registry = Some(registry);
1018 self
1019 }
1020}
1021
1022#[async_trait]
1023impl Middleware for CorrelationMiddleware {
1024 async fn handle(&self, mut req: HttpRequest, next: Next) -> Result<HttpResponse, Error> {
1025 let mut ctx = CorrelationContext::from_request(&req);
1027
1028 if !req.headers.contains_key(&self.config.correlation_header) {
1030 ctx.correlation_id = self.config.id_strategy.generate();
1031 }
1032 ctx.request_id = self.config.id_strategy.generate();
1033
1034 if self.config.generate_trace_id && ctx.trace_id.is_none() {
1038 ctx.trace_id = Some(random_hex_id(32));
1039 ctx.span_id = Some(random_hex_id(16));
1040 }
1041
1042 if let Some(ref service) = self.config.service_name {
1044 ctx.service = Some(service.clone());
1045 }
1046 if let Some(ref version) = self.config.service_version {
1047 ctx.service_version = Some(version.clone());
1048 }
1049
1050 ctx.inject_into_request(&mut req);
1052
1053 let result = next(req).await;
1055
1056 match result {
1057 Ok(mut response) => {
1058 if self.config.propagate_in_response {
1060 ctx.inject_into_response(&mut response);
1061 }
1062 Ok(response)
1063 }
1064 Err(error) => {
1065 if let Some(ref registry) = self.registry {
1067 let correlated_error = CorrelatedError::from_error(&error, ctx.clone());
1068 registry.register(correlated_error).await;
1069 }
1070 Err(error)
1071 }
1072 }
1073 }
1074}
1075
1076pub trait CorrelatedRequest {
1082 fn correlation_context(&self) -> CorrelationContext;
1084 fn correlation_id(&self) -> Option<String>;
1086 fn request_id(&self) -> Option<String>;
1088 fn trace_id(&self) -> Option<String>;
1090 fn span_id(&self) -> Option<String>;
1092}
1093
1094impl CorrelatedRequest for HttpRequest {
1095 fn correlation_context(&self) -> CorrelationContext {
1096 CorrelationContext::from_request(self)
1097 }
1098
1099 fn correlation_id(&self) -> Option<String> {
1100 self.headers.get(headers::CORRELATION_ID).map(str::to_owned)
1103 }
1104
1105 fn request_id(&self) -> Option<String> {
1106 self.headers.get(headers::REQUEST_ID).map(str::to_owned)
1107 }
1108
1109 fn trace_id(&self) -> Option<String> {
1110 if let Some(traceparent) = self.headers.get(headers::TRACE_PARENT)
1112 && let Some((trace_id, _, _)) = parse_traceparent(traceparent)
1113 {
1114 return Some(trace_id);
1115 }
1116 self.headers.get(headers::B3_TRACE_ID).map(str::to_owned)
1118 }
1119
1120 fn span_id(&self) -> Option<String> {
1121 if let Some(traceparent) = self.headers.get(headers::TRACE_PARENT)
1123 && let Some((_, span_id, _)) = parse_traceparent(traceparent)
1124 {
1125 return Some(span_id);
1126 }
1127 self.headers.get(headers::B3_SPAN_ID).map(str::to_owned)
1129 }
1130}
1131
1132pub trait CorrelatedErrorExt {
1134 fn correlate(&self, context: CorrelationContext) -> CorrelatedError;
1136 fn correlate_with_request(&self, request: &HttpRequest) -> CorrelatedError;
1138}
1139
1140impl CorrelatedErrorExt for Error {
1141 fn correlate(&self, context: CorrelationContext) -> CorrelatedError {
1142 CorrelatedError::from_error(self, context)
1143 }
1144
1145 fn correlate_with_request(&self, request: &HttpRequest) -> CorrelatedError {
1146 let context = CorrelationContext::from_request(request);
1147 CorrelatedError::from_error(self, context)
1148 }
1149}
1150
1151#[cfg(test)]
1156mod tests {
1157 use super::*;
1158
1159 #[test]
1160 fn test_id_generation_uuid_v4() {
1161 let id1 = IdGenerationStrategy::UuidV4.generate();
1162 let id2 = IdGenerationStrategy::UuidV4.generate();
1163 assert_ne!(id1, id2);
1164 assert_eq!(id1.len(), 36); }
1166
1167 #[test]
1168 fn test_id_generation_snowflake() {
1169 let id1 = IdGenerationStrategy::Snowflake.generate();
1170 let id2 = IdGenerationStrategy::Snowflake.generate();
1171 assert_ne!(id1, id2);
1172 assert_eq!(id1.len(), 16); }
1174
1175 #[test]
1176 fn test_id_generation_short() {
1177 let id = IdGenerationStrategy::Short.generate();
1178 assert_eq!(id.len(), 8);
1179 }
1180
1181 #[test]
1182 fn test_correlation_context_new() {
1183 let ctx = CorrelationContext::new();
1184 assert!(!ctx.correlation_id.is_empty());
1185 assert!(!ctx.request_id.is_empty());
1186 assert!(ctx.sampled);
1187 }
1188
1189 #[test]
1190 fn test_correlation_context_child() {
1191 let parent = CorrelationContext::new()
1192 .trace_id("trace-123")
1193 .span_id("span-456")
1194 .with_user_id("user-1");
1195
1196 let child = parent.child();
1197
1198 assert_eq!(child.correlation_id, parent.correlation_id);
1199 assert_ne!(child.request_id, parent.request_id);
1200 assert_eq!(child.trace_id, parent.trace_id);
1201 assert_eq!(child.parent_span_id, parent.span_id);
1202 assert_eq!(child.causation_id, Some(parent.request_id.clone()));
1203 assert_eq!(child.user_id, parent.user_id);
1204 }
1205
1206 #[test]
1207 fn test_correlation_context_from_request() {
1208 let mut req = HttpRequest::new("GET", "/test".to_string());
1209 req.headers
1210 .insert(headers::CORRELATION_ID, "corr-123".to_string());
1211 req.headers
1212 .insert(headers::REQUEST_ID, "req-456".to_string());
1213 req.headers.insert(
1214 headers::TRACE_PARENT,
1215 "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01".to_string(),
1216 );
1217
1218 let ctx = CorrelationContext::from_request(&req);
1219
1220 assert_eq!(ctx.correlation_id, "corr-123");
1221 assert_eq!(ctx.request_id, "req-456");
1222 assert_eq!(
1223 ctx.trace_id,
1224 Some("4bf92f3577b34da6a3ce929d0e0e4736".to_string())
1225 );
1226 assert_eq!(ctx.parent_span_id, Some("00f067aa0ba902b7".to_string()));
1227 assert!(ctx.sampled);
1228 }
1229
1230 #[test]
1231 fn test_traceparent_format() {
1232 let ctx = CorrelationContext::new()
1233 .trace_id("4bf92f3577b34da6a3ce929d0e0e4736")
1234 .span_id("00f067aa0ba902b7")
1235 .with_sampled(true);
1236
1237 let traceparent = ctx.to_traceparent().unwrap();
1238 assert!(traceparent.starts_with("00-"));
1239 assert!(traceparent.ends_with("-01"));
1240 }
1241
1242 #[test]
1243 fn test_correlated_error() {
1244 let ctx = CorrelationContext::new()
1245 .with_service("test-service")
1246 .with_user_id("user-123");
1247
1248 let error = CorrelatedError::new("Something went wrong")
1249 .with_context(ctx)
1250 .with_code("ERR_001")
1251 .with_type("VALIDATION_ERROR")
1252 .with_status(400)
1253 .caused_by("Invalid input")
1254 .with_metadata("field", "email");
1255
1256 assert_eq!(error.message, "Something went wrong");
1257 assert_eq!(error.status, 400);
1258 assert_eq!(error.code, Some("ERR_001".to_string()));
1259 assert_eq!(error.causation_chain.len(), 1);
1260 assert!(error.metadata.contains_key("field"));
1261 }
1262
1263 #[test]
1264 fn test_retry_info() {
1265 let error = CorrelatedError::new("Temporary failure").retryable(1000, 3);
1266
1267 let retry = error.retry_info.unwrap();
1268 assert!(retry.retryable);
1269 assert_eq!(retry.retry_delay_ms, Some(1000));
1270 assert_eq!(retry.max_retries, Some(3));
1271 }
1272
1273 #[tokio::test]
1274 async fn test_error_registry() {
1275 let registry = ErrorRegistry::new(100);
1276
1277 let ctx = CorrelationContext::new();
1278 let correlation_id = ctx.correlation_id.clone();
1279
1280 let error1 = CorrelatedError::new("Error 1").with_context(ctx.clone());
1281 let error2 = CorrelatedError::new("Error 2").with_context(ctx.child());
1282
1283 registry.register(error1.clone()).await;
1284 registry.register(error2.clone()).await;
1285
1286 let retrieved = registry.get(&error1.error_id).await.unwrap();
1288 assert_eq!(retrieved.message, "Error 1");
1289
1290 let errors = registry.get_by_correlation(&correlation_id).await;
1292 assert_eq!(errors.len(), 2);
1293 }
1294
1295 #[test]
1296 fn test_correlated_request_extension() {
1297 let mut req = HttpRequest::new("GET", "/test".to_string());
1298 req.headers
1299 .insert(headers::CORRELATION_ID, "corr-123".to_string());
1300 req.headers
1301 .insert(headers::REQUEST_ID, "req-456".to_string());
1302
1303 assert_eq!(req.correlation_id(), Some("corr-123".to_string()));
1304 assert_eq!(req.request_id(), Some("req-456".to_string()));
1305 }
1306
1307 #[test]
1308 fn test_correlation_config() {
1309 let config = CorrelationConfig::new()
1310 .service("my-service")
1311 .version("1.0.0")
1312 .strategy(IdGenerationStrategy::UuidV7)
1313 .generate_traces(true)
1314 .propagate_response(true);
1315
1316 assert_eq!(config.service_name, Some("my-service".to_string()));
1317 assert_eq!(config.service_version, Some("1.0.0".to_string()));
1318 assert!(config.generate_trace_id);
1319 assert!(config.propagate_in_response);
1320 }
1321
1322 #[test]
1323 fn test_inject_headers() {
1324 let ctx = CorrelationContext::new()
1325 .correlation_id("corr-123")
1326 .trace_id("trace-456")
1327 .span_id("span-789")
1328 .with_session("session-abc");
1329
1330 let mut req = HttpRequest::new("POST", "/api".to_string());
1331 ctx.inject_into_request(&mut req);
1332
1333 assert_eq!(req.headers.get(headers::CORRELATION_ID), Some("corr-123"));
1334 assert!(req.headers.get(headers::TRACE_PARENT).is_some());
1335 assert_eq!(req.headers.get(headers::SESSION_ID), Some("session-abc"));
1336 }
1337
1338 #[test]
1339 fn test_parse_traceparent() {
1340 let (trace_id, span_id, sampled) =
1341 parse_traceparent("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01").unwrap();
1342
1343 assert_eq!(trace_id, "4bf92f3577b34da6a3ce929d0e0e4736");
1344 assert_eq!(span_id, "00f067aa0ba902b7");
1345 assert!(sampled);
1346
1347 let (_, _, sampled) =
1349 parse_traceparent("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00").unwrap();
1350 assert!(!sampled);
1351 }
1352
1353 #[test]
1354 fn test_correlated_error_to_json() {
1355 let error = CorrelatedError::new("Test error")
1356 .with_code("TEST_001")
1357 .with_status(400);
1358
1359 let json = error.to_json();
1360 assert!(json.contains("Test error"));
1361 assert!(json.contains("TEST_001"));
1362 assert!(json.contains("400"));
1363 }
1364}