Skip to main content

xbp_analysis/domain/
concepts.rs

1//! Abstract error-handling concepts (not Rust-specific).
2//!
3//! Language adapters map concrete constructs into these concepts.
4//! Examples: Rust `Result`/`?`/`unwrap` vs Java exceptions vs Go multi-value errors.
5
6use serde::{Deserialize, Serialize};
7
8use super::types::SourceLocation;
9
10/// Kind of abstract operation / signal in the error model.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12#[serde(rename_all = "snake_case")]
13pub enum ConceptKind {
14    FallibleOperation,
15    ErrorSignal,
16    ErrorHandler,
17    Propagation,
18    Recovery,
19    Retry,
20    Termination,
21    ResourceCleanup,
22    StateMutation,
23    TransactionBoundary,
24    LoggingOnly,
25    Placeholder,
26    UnobservedTask,
27    PartialCommit,
28    ContextLoss,
29    EmptyHandler,
30    IgnoredFailure,
31}
32
33/// A single abstract concept instance extracted from source.
34#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
35pub struct ConceptInstance {
36    pub kind: ConceptKind,
37    pub location: SourceLocation,
38    /// Free-form classifier from the adapter (e.g. `unwrap`, `panic_macro`, `tokio_spawn`).
39    pub tag: String,
40    /// Optional name of the symbol/operation.
41    #[serde(default, skip_serializing_if = "Option::is_none")]
42    pub symbol: Option<String>,
43    /// Whether the adapter believes the operation is idempotent (retries).
44    #[serde(default, skip_serializing_if = "Option::is_none")]
45    pub idempotent: Option<bool>,
46    /// Whether a retry has an explicit bound / backoff.
47    #[serde(default, skip_serializing_if = "Option::is_none")]
48    pub bounded: Option<bool>,
49    /// Adjacent logging without propagation.
50    #[serde(default)]
51    pub logs_only: bool,
52    /// Original error dropped / mapped to unit success.
53    #[serde(default)]
54    pub drops_context: bool,
55    /// Critical path marker (e.g. error handler with TODO).
56    #[serde(default)]
57    pub critical_path: bool,
58    /// Human-readable surface form for evidence.
59    pub surface: String,
60}
61
62impl ConceptInstance {
63    pub fn new(
64        kind: ConceptKind,
65        location: SourceLocation,
66        tag: impl Into<String>,
67        surface: impl Into<String>,
68    ) -> Self {
69        Self {
70            kind,
71            location,
72            tag: tag.into(),
73            symbol: None,
74            idempotent: None,
75            bounded: None,
76            logs_only: false,
77            drops_context: false,
78            critical_path: false,
79            surface: surface.into(),
80        }
81    }
82
83    pub fn with_symbol(mut self, s: impl Into<String>) -> Self {
84        self.symbol = Some(s.into());
85        self
86    }
87
88    pub fn with_idempotent(mut self, v: bool) -> Self {
89        self.idempotent = Some(v);
90        self
91    }
92
93    pub fn with_bounded(mut self, v: bool) -> Self {
94        self.bounded = Some(v);
95        self
96    }
97
98    pub fn with_logs_only(mut self, v: bool) -> Self {
99        self.logs_only = v;
100        self
101    }
102
103    pub fn with_drops_context(mut self, v: bool) -> Self {
104        self.drops_context = v;
105        self
106    }
107
108    pub fn with_critical_path(mut self, v: bool) -> Self {
109        self.critical_path = v;
110        self
111    }
112}