use_diagnostic_message/
lib.rs1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5
6#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
8pub struct DiagnosticMessage(String);
9
10impl DiagnosticMessage {
11 pub fn new(value: impl AsRef<str>) -> Result<Self, DiagnosticTextError> {
17 Ok(Self(normalize_text(value.as_ref())?))
18 }
19
20 #[must_use]
22 pub fn as_str(&self) -> &str {
23 &self.0
24 }
25
26 #[must_use]
28 pub fn into_string(self) -> String {
29 self.0
30 }
31}
32
33impl AsRef<str> for DiagnosticMessage {
34 fn as_ref(&self) -> &str {
35 self.as_str()
36 }
37}
38
39impl fmt::Display for DiagnosticMessage {
40 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
41 formatter.write_str(self.as_str())
42 }
43}
44
45impl FromStr for DiagnosticMessage {
46 type Err = DiagnosticTextError;
47
48 fn from_str(value: &str) -> Result<Self, Self::Err> {
49 Self::new(value)
50 }
51}
52
53#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
55pub struct DiagnosticNote(String);
56
57impl DiagnosticNote {
58 pub fn new(value: impl AsRef<str>) -> Result<Self, DiagnosticTextError> {
64 Ok(Self(normalize_text(value.as_ref())?))
65 }
66
67 #[must_use]
69 pub fn as_str(&self) -> &str {
70 &self.0
71 }
72
73 #[must_use]
75 pub fn into_string(self) -> String {
76 self.0
77 }
78}
79
80impl AsRef<str> for DiagnosticNote {
81 fn as_ref(&self) -> &str {
82 self.as_str()
83 }
84}
85
86impl fmt::Display for DiagnosticNote {
87 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
88 formatter.write_str(self.as_str())
89 }
90}
91
92impl FromStr for DiagnosticNote {
93 type Err = DiagnosticTextError;
94
95 fn from_str(value: &str) -> Result<Self, Self::Err> {
96 Self::new(value)
97 }
98}
99
100#[derive(Clone, Copy, Debug, Eq, PartialEq)]
102pub enum DiagnosticTextError {
103 Empty,
105}
106
107impl fmt::Display for DiagnosticTextError {
108 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
109 match self {
110 Self::Empty => formatter.write_str("diagnostic text cannot be empty"),
111 }
112 }
113}
114
115impl std::error::Error for DiagnosticTextError {}
116
117fn normalize_text(value: &str) -> Result<String, DiagnosticTextError> {
118 let trimmed = value.trim();
119
120 if trimmed.is_empty() {
121 Err(DiagnosticTextError::Empty)
122 } else {
123 Ok(trimmed.to_string())
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::{DiagnosticMessage, DiagnosticNote, DiagnosticTextError};
130
131 #[test]
132 fn accepts_valid_message() {
133 let message =
134 DiagnosticMessage::new("missing required field").expect("message should be valid");
135
136 assert_eq!(message.as_str(), "missing required field");
137 }
138
139 #[test]
140 fn rejects_empty_message() {
141 assert_eq!(
142 DiagnosticMessage::new(" \n\t "),
143 Err(DiagnosticTextError::Empty)
144 );
145 }
146
147 #[test]
148 fn trims_surrounding_message_whitespace() {
149 let message = DiagnosticMessage::new(" invalid configuration value ")
150 .expect("message should be valid");
151
152 assert_eq!(message.as_str(), "invalid configuration value");
153 }
154
155 #[test]
156 fn display_returns_plain_message() {
157 let message = DiagnosticMessage::new("malformed input").expect("message should be valid");
158
159 assert_eq!(message.to_string(), "malformed input");
160 }
161
162 #[test]
163 fn constructs_notes() {
164 let note =
165 DiagnosticNote::new("field names are case-sensitive").expect("note should be valid");
166
167 assert_eq!(note.as_str(), "field names are case-sensitive");
168 }
169}