Skip to main content

socketry_markdown/
message.rs

1// Released under the MIT License.
2// Copyright, 2024-2025, by Titus Wormer.
3// Copyright, 2026, by Samuel Williams.
4
5use crate::unist::{Point, Position};
6use alloc::{boxed::Box, fmt, string::String};
7
8#[derive(Clone, Debug, PartialEq)]
9pub struct Message {
10    /// Place of message.
11    pub place: Option<Box<Place>>,
12    /// Reason for message (should use markdown).
13    pub reason: String,
14    /// Category of message.
15    pub rule_id: Box<String>,
16    /// Namespace of message.
17    pub source: Box<String>,
18}
19
20impl fmt::Display for Message {
21    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22        if let Some(ref place) = self.place {
23            write!(f, "{place}: ")?;
24        }
25
26        write!(f, "{} ({}:{})", self.reason, self.source, self.rule_id)
27    }
28}
29
30/// Somewhere.
31#[derive(Clone, Debug, PartialEq)]
32pub enum Place {
33    /// Between two points.
34    Position(Position),
35    /// At a point.
36    Point(Point),
37}
38
39impl fmt::Display for Place {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        match self {
42            Place::Position(position) => write!(
43                f,
44                "{}:{}-{}:{}",
45                position.start.line, position.start.column, position.end.line, position.end.column
46            ),
47            Place::Point(point) => write!(f, "{}:{}", point.line, point.column),
48        }
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::{Message, Place};
55    use crate::unist::{Point, Position};
56    use alloc::boxed::Box;
57    use alloc::string::ToString;
58    use core::fmt::Write as _;
59
60    struct FailingWriter;
61
62    impl core::fmt::Write for FailingWriter {
63        fn write_str(&mut self, _value: &str) -> core::fmt::Result {
64            Err(core::fmt::Error)
65        }
66    }
67
68    #[test]
69    fn displays_a_message_with_its_place() {
70        let message = Message {
71            place: Some(Box::new(Place::Point(Point::new(2, 3, 4)))),
72            reason: "invalid value".into(),
73            rule_id: Box::new("example".into()),
74            source: Box::new("parser".into()),
75        };
76
77        assert_eq!(message.to_string(), "2:3: invalid value (parser:example)");
78
79        let message = Message {
80            place: Some(Box::new(Place::Position(Position::new(1, 2, 3, 4, 5, 6)))),
81            reason: "invalid range".into(),
82            rule_id: Box::new("example".into()),
83            source: Box::new("parser".into()),
84        };
85
86        assert_eq!(
87            message.to_string(),
88            "1:2-4:5: invalid range (parser:example)"
89        );
90
91        let message = Message {
92            place: None,
93            reason: "invalid without a location".into(),
94            rule_id: Box::new("example".into()),
95            source: Box::new("parser".into()),
96        };
97        assert_eq!(
98            message.to_string(),
99            "invalid without a location (parser:example)"
100        );
101
102        let mut writer = FailingWriter;
103        assert!(write!(&mut writer, "{message}").is_err());
104
105        let message = Message {
106            place: Some(Box::new(Place::Point(Point::new(2, 3, 4)))),
107            reason: "invalid value".into(),
108            rule_id: Box::new("example".into()),
109            source: Box::new("parser".into()),
110        };
111        assert!(write!(&mut writer, "{message}").is_err());
112    }
113}