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