1use std::collections::BTreeSet;
13use std::fmt::Write as _;
14use std::time::Duration;
15
16use crate::ids::EntityId;
17
18pub trait SafLogValue {
20 fn fmt_saf_log(&self, buf: &mut String);
22
23 fn to_saf_log(&self) -> String {
25 let mut buf = String::new();
26 self.fmt_saf_log(&mut buf);
27 buf
28 }
29}
30
31impl SafLogValue for bool {
34 fn fmt_saf_log(&self, buf: &mut String) {
35 buf.push_str(if *self { "true" } else { "false" });
36 }
37}
38
39macro_rules! impl_saf_log_int {
40 ($($ty:ty),*) => {
41 $(
42 impl SafLogValue for $ty {
43 fn fmt_saf_log(&self, buf: &mut String) {
44 let _ = write!(buf, "{self}");
45 }
46 }
47 )*
48 };
49}
50
51impl_saf_log_int!(u8, u16, u32, u64, usize, i8, i16, i32, i64, isize);
52
53impl SafLogValue for f64 {
54 fn fmt_saf_log(&self, buf: &mut String) {
55 let _ = write!(buf, "{self:.3}");
56 }
57}
58
59impl SafLogValue for str {
62 fn fmt_saf_log(&self, buf: &mut String) {
63 buf.push_str(self);
64 }
65}
66
67impl SafLogValue for String {
68 fn fmt_saf_log(&self, buf: &mut String) {
69 buf.push_str(self);
70 }
71}
72
73impl SafLogValue for &str {
74 fn fmt_saf_log(&self, buf: &mut String) {
75 buf.push_str(self);
76 }
77}
78
79impl SafLogValue for u128 {
82 fn fmt_saf_log(&self, buf: &mut String) {
83 let _ = write!(buf, "0x{self:032x}");
84 }
85}
86
87impl<T: EntityId> SafLogValue for T {
89 fn fmt_saf_log(&self, buf: &mut String) {
90 self.raw().fmt_saf_log(buf);
91 }
92}
93
94impl<T: SafLogValue> SafLogValue for BTreeSet<T> {
97 fn fmt_saf_log(&self, buf: &mut String) {
98 buf.push('{');
99 for (i, item) in self.iter().enumerate() {
100 if i > 0 {
101 buf.push(',');
102 }
103 item.fmt_saf_log(buf);
104 }
105 buf.push('}');
106 }
107}
108
109impl<T: SafLogValue> SafLogValue for Vec<T> {
110 fn fmt_saf_log(&self, buf: &mut String) {
111 self.as_slice().fmt_saf_log(buf);
112 }
113}
114
115impl<T: SafLogValue> SafLogValue for [T] {
116 fn fmt_saf_log(&self, buf: &mut String) {
117 buf.push('[');
118 for (i, item) in self.iter().enumerate() {
119 if i > 0 {
120 buf.push(',');
121 }
122 item.fmt_saf_log(buf);
123 }
124 buf.push(']');
125 }
126}
127
128impl SafLogValue for Duration {
131 fn fmt_saf_log(&self, buf: &mut String) {
132 let _ = write!(buf, "{:.3}s", self.as_secs_f64());
133 }
134}
135
136impl<T: SafLogValue> SafLogValue for Option<T> {
139 fn fmt_saf_log(&self, buf: &mut String) {
140 match self {
141 Some(v) => v.fmt_saf_log(buf),
142 None => buf.push_str("none"),
143 }
144 }
145}
146
147pub struct SafPair<'a, T: SafLogValue>(pub &'a T, pub &'a T);
151
152impl<T: SafLogValue> SafLogValue for SafPair<'_, T> {
153 fn fmt_saf_log(&self, buf: &mut String) {
154 self.0.fmt_saf_log(buf);
155 buf.push_str("->");
156 self.1.fmt_saf_log(buf);
157 }
158}
159
160pub struct SafRatio(pub usize, pub usize);
162
163impl SafLogValue for SafRatio {
164 fn fmt_saf_log(&self, buf: &mut String) {
165 let _ = write!(buf, "{}/{}", self.0, self.1);
166 }
167}
168
169pub enum PtsDelta<'a, T: SafLogValue> {
171 Added(&'a [T]),
173 Removed(&'a [T]),
175}
176
177impl<T: SafLogValue> SafLogValue for PtsDelta<'_, T> {
178 fn fmt_saf_log(&self, buf: &mut String) {
179 let (prefix, items) = match self {
180 PtsDelta::Added(items) => ("+", *items),
181 PtsDelta::Removed(items) => ("-", *items),
182 };
183 buf.push_str(prefix);
184 buf.push('{');
185 for (i, item) in items.iter().enumerate() {
186 if i > 0 {
187 buf.push(',');
188 }
189 item.fmt_saf_log(buf);
190 }
191 buf.push('}');
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
200 fn test_bool() {
201 assert_eq!(true.to_saf_log(), "true");
202 assert_eq!(false.to_saf_log(), "false");
203 }
204
205 #[test]
206 fn test_integers() {
207 assert_eq!(42_u32.to_saf_log(), "42");
208 assert_eq!((-1_i32).to_saf_log(), "-1");
209 assert_eq!(0_usize.to_saf_log(), "0");
210 }
211
212 #[test]
213 fn test_u128_id() {
214 assert_eq!(
215 0x1a2b_u128.to_saf_log(),
216 "0x00000000000000000000000000001a2b"
217 );
218 }
219
220 #[test]
221 fn test_string() {
222 assert_eq!("main".to_saf_log(), "main");
223 assert_eq!(String::from("foo").to_saf_log(), "foo");
224 }
225
226 #[test]
227 fn test_btreeset() {
228 let set: BTreeSet<u32> = [3, 1, 2].into_iter().collect();
229 assert_eq!(set.to_saf_log(), "{1,2,3}");
230 }
231
232 #[test]
233 fn test_vec() {
234 let v = vec![10_u32, 20, 30];
235 assert_eq!(v.to_saf_log(), "[10,20,30]");
236 }
237
238 #[test]
239 fn test_duration() {
240 let d = Duration::from_secs_f64(1.2345);
241 assert_eq!(d.to_saf_log(), "1.234s");
242 }
243
244 #[test]
245 fn test_option() {
246 let some: Option<u32> = Some(42);
247 let none: Option<u32> = None;
248 assert_eq!(some.to_saf_log(), "42");
249 assert_eq!(none.to_saf_log(), "none");
250 }
251
252 #[test]
253 fn test_pair() {
254 let a = 1_u32;
255 let b = 2_u32;
256 assert_eq!(SafPair(&a, &b).to_saf_log(), "1->2");
257 }
258
259 #[test]
260 fn test_ratio() {
261 assert_eq!(SafRatio(12, 50).to_saf_log(), "12/50");
262 }
263
264 #[test]
265 fn test_delta() {
266 let items = [1_u32, 2, 3];
267 assert_eq!(PtsDelta::Added(&items).to_saf_log(), "+{1,2,3}");
268 assert_eq!(PtsDelta::Removed(&items).to_saf_log(), "-{1,2,3}");
269 }
270}