diagweave 0.1.0

Core runtime and macros re-export for diagweave error algebra and report diagweaveing.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
#[path = "types/attachment.rs"]
pub mod attachment;
#[path = "types/config.rs"]
mod config;
#[path = "types/context.rs"]
pub mod context;
#[path = "types/error.rs"]
pub mod error;
#[path = "types/source_error.rs"]
mod source_error;

use alloc::boxed::Box;
use alloc::string::String;
use alloc::string::ToString;
use alloc::sync::Arc;
use alloc::vec;
use alloc::vec::Vec;
use core::any;
use core::error::Error;
use core::fmt::{self, Display, Formatter};
use ref_str::StaticRefStr;

pub use attachment::*;
pub use config::*;
pub use context::*;
pub use error::*;
pub use source_error::*;

mod severity_state {
    /// A sealed trait marker for internal use only.
    pub trait Sealed {}
}

/// Typestate marker for reports whose severity has not been set.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct MissingSeverity;

/// Typestate marker for reports whose severity is present.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HasSeverity {
    severity: Severity,
}

impl HasSeverity {
    /// Creates a present severity typestate with the specified severity.
    pub const fn new(severity: Severity) -> Self {
        Self { severity }
    }

    /// Returns the guaranteed severity carried by this typestate.
    pub const fn severity(self) -> Severity {
        self.severity
    }
}

impl severity_state::Sealed for MissingSeverity {}
impl severity_state::Sealed for HasSeverity {}

/// Typestate contract for report severity metadata.
pub trait SeverityState: severity_state::Sealed + Clone + Copy + PartialEq + Eq {
    /// Returns the severity represented by the typestate, if any.
    fn severity(self) -> Option<Severity>;
}

impl SeverityState for MissingSeverity {
    fn severity(self) -> Option<Severity> {
        None
    }
}

impl SeverityState for HasSeverity {
    fn severity(self) -> Option<Severity> {
        Some(self.severity)
    }
}

#[cfg(feature = "json")]
impl serde::Serialize for MissingSeverity {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.serialize_none()
    }
}

#[cfg(feature = "json")]
impl<'de> serde::Deserialize<'de> for MissingSeverity {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        match Option::<Severity>::deserialize(deserializer)? {
            None => Ok(Self),
            Some(_) => Err(serde::de::Error::custom("expected null severity typestate")),
        }
    }
}

#[cfg(feature = "json")]
impl serde::Serialize for HasSeverity {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        self.severity.serialize(serializer)
    }
}

#[cfg(feature = "json")]
impl<'de> serde::Deserialize<'de> for HasSeverity {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        Severity::deserialize(deserializer).map(Self::new)
    }
}

/// Inner metadata structure containing the actual metadata fields.
/// This is boxed inside ReportMetadata to enable lazy allocation.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
pub(crate) struct MetadataInner {
    error_code: Option<ErrorCode>,
    category: Option<StaticRefStr>,
    retryable: Option<bool>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
/// Report metadata carried alongside a diagnostic.
///
/// Contains severity state and optional error code, category, and retryable flag.
/// Uses lazy allocation via `Option<Box<MetadataInner>>` for the inner metadata to minimize overhead when empty.
/// The `State` generic parameter tracks the severity typestate.
pub struct ReportMetadata<State: SeverityState> {
    severity: State,
    #[cfg_attr(feature = "json", serde(flatten))]
    inner: Option<Box<MetadataInner>>,
}

impl<State: SeverityState> ReportMetadata<State> {
    /// Returns a reference to the severity state.
    pub fn severity(&self) -> Option<Severity> {
        self.severity.severity()
    }

    /// Returns the severity state.
    pub fn severity_state(&self) -> State {
        self.severity
    }

    /// Ensures the inner metadata is allocated, creating it if necessary.
    fn ensure_inner(&mut self) -> &mut MetadataInner {
        self.inner.get_or_insert_with(|| {
            Box::new(MetadataInner {
                error_code: None,
                category: None,
                retryable: None,
            })
        })
    }

    /// Returns the error code, if present.
    pub fn error_code(&self) -> Option<&ErrorCode> {
        self.inner.as_ref()?.error_code.as_ref()
    }

    /// Returns the category, if present.
    pub fn category(&self) -> Option<&str> {
        self.inner.as_ref()?.category.as_deref()
    }

    /// Returns whether the metadata marks the diagnostic as retryable, if present.
    pub fn retryable(&self) -> Option<bool> {
        self.inner.as_ref()?.retryable
    }
}

impl ReportMetadata<MissingSeverity> {
    /// Creates a new ReportMetadata with all fields set to None (lazy, not allocated yet).
    pub fn new() -> Self {
        Self {
            severity: MissingSeverity,
            inner: None,
        }
    }

    /// Sets the severity, transitioning to `HasSeverity` typestate.
    pub fn set_severity(self, severity: Severity) -> ReportMetadata<HasSeverity> {
        ReportMetadata {
            severity: HasSeverity::new(severity),
            inner: self.inner,
        }
    }
}

impl Default for ReportMetadata<MissingSeverity> {
    fn default() -> Self {
        Self::new()
    }
}

impl ReportMetadata<HasSeverity> {
    /// Sets the severity to a new value.
    pub fn set_severity(mut self, severity: Severity) -> Self {
        self.severity = HasSeverity::new(severity);
        self
    }
}

impl<State: SeverityState> ReportMetadata<State> {
    /// Sets the error code, replacing any existing value.
    pub fn set_error_code(mut self, error_code: impl Into<ErrorCode>) -> Self {
        self.ensure_inner().error_code = Some(error_code.into());
        self
    }

    /// Sets the error code, replacing any existing value (mutable reference version).
    ///
    /// This method avoids cloning the entire metadata when modifying in place.
    pub fn set_error_code_mut(&mut self, error_code: impl Into<ErrorCode>) {
        self.ensure_inner().error_code = Some(error_code.into());
    }

    /// Sets the error code only if not already set.
    pub fn with_error_code(mut self, error_code: impl Into<ErrorCode>) -> Self {
        if self.error_code().is_none() {
            self.ensure_inner().error_code = Some(error_code.into());
        }
        self
    }

    /// Sets the error code only if not already set (mutable reference version).
    ///
    /// This method avoids cloning the entire metadata when modifying in place.
    pub fn with_error_code_mut(&mut self, error_code: impl Into<ErrorCode>) {
        if self.error_code().is_none() {
            self.ensure_inner().error_code = Some(error_code.into());
        }
    }

    /// Sets the category, replacing any existing value.
    pub fn set_category(mut self, category: impl Into<StaticRefStr>) -> Self {
        self.ensure_inner().category = Some(category.into());
        self
    }

    /// Sets the category, replacing any existing value (mutable reference version).
    ///
    /// This method avoids cloning the entire metadata when modifying in place.
    pub fn set_category_mut(&mut self, category: impl Into<StaticRefStr>) {
        self.ensure_inner().category = Some(category.into());
    }

    /// Sets the category only if not already set.
    pub fn with_category(mut self, category: impl Into<StaticRefStr>) -> Self {
        if self.category().is_none() {
            self.ensure_inner().category = Some(category.into());
        }
        self
    }

    /// Sets the category only if not already set (mutable reference version).
    ///
    /// This method avoids cloning the entire metadata when modifying in place.
    pub fn with_category_mut(&mut self, category: impl Into<StaticRefStr>) {
        if self.category().is_none() {
            self.ensure_inner().category = Some(category.into());
        }
    }

    /// Sets the retryability flag, replacing any existing value.
    pub fn set_retryable(mut self, retryable: bool) -> Self {
        self.ensure_inner().retryable = Some(retryable);
        self
    }

    /// Sets the retryability flag, replacing any existing value (mutable reference version).
    ///
    /// This method avoids cloning the entire metadata when modifying in place.
    pub fn set_retryable_mut(&mut self, retryable: bool) {
        self.ensure_inner().retryable = Some(retryable);
    }

    /// Sets the retryability flag only if not already set.
    pub fn with_retryable(mut self, retryable: bool) -> Self {
        if self.retryable().is_none() {
            self.ensure_inner().retryable = Some(retryable);
        }
        self
    }

    /// Sets the retryability flag only if not already set (mutable reference version).
    ///
    /// This method avoids cloning the entire metadata when modifying in place.
    pub fn with_retryable_mut(&mut self, retryable: bool) {
        if self.retryable().is_none() {
            self.ensure_inner().retryable = Some(retryable);
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "json", serde(rename_all = "snake_case"))]
pub enum StackTraceFormat {
    Native,
    Raw,
}

#[derive(Debug, Default, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
pub struct StackFrame {
    pub symbol: Option<StaticRefStr>,
    pub module_path: Option<StaticRefStr>,
    pub file: Option<StaticRefStr>,
    pub line: Option<u32>,
    pub column: Option<u32>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
pub struct StackTrace {
    pub format: StackTraceFormat,
    pub frames: Arc<[StackFrame]>,
    pub raw: Option<StaticRefStr>,
}

impl Default for StackTrace {
    fn default() -> Self {
        Self {
            format: StackTraceFormat::Native,
            frames: Vec::new().into(),
            raw: None,
        }
    }
}

impl StackTrace {
    /// Creates a new [`StackTrace`] with the specified format.
    pub fn new(format: StackTraceFormat) -> Self {
        Self {
            format,
            ..Self::default()
        }
    }

    /// Replaces the frames in the stack trace.
    pub fn set_frames(mut self, frames: Vec<StackFrame>) -> Self {
        self.frames = frames.into();
        self
    }

    /// Appends frames to the existing stack trace frames.
    pub fn with_frames(mut self, frames: Vec<StackFrame>) -> Self {
        let mut existing = self.frames.to_vec();
        existing.extend(frames);
        self.frames = existing.into();
        self
    }

    /// Sets the raw stack trace string, replacing any existing value.
    pub fn set_raw(mut self, raw: impl Into<StaticRefStr>) -> Self {
        self.raw = Some(raw.into());
        self
    }

    /// Sets the raw stack trace string only if not already set.
    pub fn with_raw(mut self, raw: impl Into<StaticRefStr>) -> Self {
        if self.raw.is_none() {
            self.raw = Some(raw.into());
        }
        self
    }

    /// Captures the current stack trace as a raw string (requires `std` feature).
    #[cfg(feature = "std")]
    pub fn capture_raw() -> Self {
        let backtrace = std::backtrace::Backtrace::force_capture();
        Self {
            format: StackTraceFormat::Raw,
            frames: Vec::new().into(),
            raw: Some(backtrace.to_string().into()),
        }
    }
}

/// Traversal state observed during cause collection.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct CauseTraversalState {
    /// Whether the traversal was truncated due to depth limit.
    pub truncated: bool,
    /// Whether a circular reference cycle was detected.
    pub cycle_detected: bool,
}

impl CauseTraversalState {
    /// Merges traversal flags from another state.
    pub fn merge_from(&mut self, other: Self) {
        self.truncated |= other.truncated;
        self.cycle_detected |= other.cycle_detected;
    }
}

/// A streamed attachment item for visitor-based traversal.
pub enum AttachmentVisit<'a> {
    Note {
        message: &'a (dyn Display + Send + Sync + 'static),
    },
    Payload {
        name: &'a StaticRefStr,
        value: &'a AttachmentValue,
        media_type: Option<&'a StaticRefStr>,
    },
}