Skip to main content

base_markdown/
message.rs

1// Released under the MIT License.
2// Copyright, 2024-2025, by Titus Wormer.
3// Copyright, 2026, by Samuel Williams.
4
5//! Diagnostics use the same byte offsets as documents and events.
6use alloc::borrow::Cow;
7use core::{fmt, ops::Range};
8
9/// A serializer or custom event producer diagnostic.
10///
11/// Static messages and identifiers require no allocation. A dynamically formatted
12/// reason can be supplied with `String::into`. Spans are half-open byte ranges in
13/// the source; consumers can derive line and column numbers when presenting them.
14#[derive(Clone, Debug, PartialEq, Eq)]
15pub struct Message {
16    /// Source bytes associated with this diagnostic, when available.
17    pub span: Option<Range<usize>>,
18    /// Human-readable explanation.
19    pub reason: Cow<'static, str>,
20    /// Category of message.
21    pub rule_id: &'static str,
22    /// Namespace of message.
23    pub source: &'static str,
24}
25
26impl fmt::Display for Message {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        if let Some(span) = &self.span {
29            write!(f, "{}..{}: ", span.start, span.end)?;
30        }
31        write!(f, "{} ({}:{})", self.reason, self.source, self.rule_id)
32    }
33}
34
35#[cfg(test)]
36mod tests {
37    use super::Message;
38    use alloc::{borrow::Cow, format, string::ToString};
39    use core::fmt::Write as _;
40
41    struct FailingWriter;
42
43    impl core::fmt::Write for FailingWriter {
44        fn write_str(&mut self, _value: &str) -> core::fmt::Result {
45            Err(core::fmt::Error)
46        }
47    }
48
49    #[test]
50    fn diagnostics_borrow_static_reasons_and_display_byte_ranges() {
51        let mut message = Message {
52            span: None,
53            reason: "invalid value".into(),
54            rule_id: "example",
55            source: "parser",
56        };
57        assert_eq!(message.reason, Cow::Borrowed("invalid value"));
58        assert_eq!(message.to_string(), "invalid value (parser:example)");
59        assert!(write!(&mut FailingWriter, "{message}").is_err());
60
61        message.span = Some(2..5);
62        message.reason = format!("invalid value {}", 42).into();
63        assert_eq!(
64            message.to_string(),
65            "2..5: invalid value 42 (parser:example)"
66        );
67        assert!(write!(&mut FailingWriter, "{message}").is_err());
68    }
69}