1use std::fmt::{self, Display, Formatter};
8
9pub struct OptionalDisplay<T>(pub Option<T>);
17
18impl<T> Display for OptionalDisplay<T>
19where
20 T: Display,
21{
22 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
23 match &self.0 {
24 Some(value) => value.fmt(f),
25 None => f.write_str("None"),
26 }
27 }
28}
29
30#[must_use]
34pub fn truncate(s: &str, max_chars: usize) -> String {
35 let mut chars = s.chars();
36 let truncated: String = chars.by_ref().take(max_chars).collect();
37
38 if chars.next().is_some() {
39 truncated
40 } else {
41 s.to_string()
42 }
43}
44
45#[must_use]
49#[expect(clippy::cast_precision_loss)]
50pub fn byte_size(bytes: u64) -> String {
51 const UNITS: [&str; 5] = ["B", "KiB", "MiB", "GiB", "TiB"];
52
53 let mut value = bytes as f64;
54 let mut unit_index = 0usize;
55
56 while value >= 1024.0 && unit_index < UNITS.len() - 1 {
57 value /= 1024.0;
58 unit_index += 1;
59 }
60
61 format!("{value:.2} {}", UNITS[unit_index])
62}
63
64#[must_use]
68pub fn cycles_tc(cycles: u128) -> String {
69 const HUNDREDTH_TC: u128 = 10_000_000_000;
70
71 let hundredths = cycles.saturating_add(HUNDREDTH_TC / 2) / HUNDREDTH_TC;
72 format!("{}.{:02} TC", hundredths / 100, hundredths % 100)
73}
74
75#[must_use]
77pub const fn display_optional<T>(value: Option<T>) -> OptionalDisplay<T>
78where
79 T: Display,
80{
81 OptionalDisplay(value)
82}
83
84#[cfg(test)]
89mod tests {
90 use super::{byte_size, cycles_tc, display_optional, truncate};
91 use crate::cdk::types::Principal;
92
93 #[test]
94 fn keeps_short_strings() {
95 assert_eq!(truncate("root", 9), "root");
96 assert_eq!(truncate("abcdefgh", 9), "abcdefgh");
97 assert_eq!(truncate("abcdefghi", 9), "abcdefghi");
98 }
99
100 #[test]
101 fn truncates_long_strings() {
102 assert_eq!(truncate("abcdefghijkl", 9), "abcdefghi");
103 assert_eq!(truncate("abcdefghijklmnopqrstuvwxyz", 9), "abcdefghi");
104 }
105
106 #[test]
107 fn formats_small_byte_sizes() {
108 assert_eq!(byte_size(0), "0.00 B");
109 assert_eq!(byte_size(512), "512.00 B");
110 assert_eq!(byte_size(1024), "1.00 KiB");
111 }
112
113 #[test]
114 fn formats_larger_byte_sizes() {
115 assert_eq!(byte_size(720_795), "703.90 KiB");
116 assert_eq!(byte_size(13_936_529), "13.29 MiB");
117 assert_eq!(byte_size(9_102_643), "8.68 MiB");
118 }
119
120 #[test]
121 fn formats_cycles_in_tc() {
122 assert_eq!(cycles_tc(4_487_280_757_485), "4.49 TC");
123 assert_eq!(cycles_tc(12_345_678_900_000), "12.35 TC");
124 }
125
126 #[test]
127 fn formats_optional_display_values() {
128 let pid = Principal::from_slice(&[7; 29]);
129 assert_eq!(display_optional(Some(pid)).to_string(), pid.to_string());
130 assert_eq!(display_optional::<Principal>(None).to_string(), "None");
131 }
132}