1import_stdlib!();
2
3use super::{
4 string_util::{flanked, sanitized},
5 varint::{EncodeVarInt, MajorType},
6};
7use crate::{
8 CBOR, CBORCase, TagsStoreOpt, tags_store::TagsStoreTrait, with_tags,
9};
10
11#[derive(Default)]
12pub struct HexFormatOpts<'a> {
13 annotate: bool,
14 tags: TagsStoreOpt<'a>,
15}
16
17impl<'a> HexFormatOpts<'a> {
18 pub fn annotate(mut self, annotate: bool) -> Self {
20 self.annotate = annotate;
21 self
22 }
23
24 pub fn context(mut self, tags: TagsStoreOpt<'a>) -> Self {
26 self.tags = tags;
27 self
28 }
29}
30
31impl CBOR {
34 pub fn hex(&self) -> String { hex::encode(self.to_cbor_data()) }
36
37 pub fn hex_opt(&self, opts: &HexFormatOpts<'_>) -> String {
43 if !opts.annotate {
44 return self.hex();
45 }
46 let items = self.dump_items(0, opts);
47 let note_column = items.iter().fold(0, |largest, item| {
48 largest.max(item.format_first_column().len())
49 });
50 let note_column = ((note_column + 4) & !3) - 1;
52 let lines: Vec<_> =
53 items.iter().map(|x| x.format(note_column)).collect();
54 lines.join("\n")
55 }
56
57 pub fn hex_annotated(&self) -> String {
60 self.hex_opt(&HexFormatOpts::default().annotate(true))
61 }
62
63 fn dump_items(
64 &self,
65 level: usize,
66 opts: &HexFormatOpts<'_>,
67 ) -> Vec<DumpItem> {
68 match self.as_case() {
69 CBORCase::Unsigned(n) => vec![DumpItem::new(
70 level,
71 vec![self.to_cbor_data()],
72 Some(format!("unsigned({})", n)),
73 )],
74 CBORCase::Negative(n) => vec![DumpItem::new(
75 level,
76 vec![self.to_cbor_data()],
77 Some(format!("negative({})", -1 - (*n as i128))),
78 )],
79 CBORCase::ByteString(d) => {
80 let mut items = vec![DumpItem::new(
81 level,
82 vec![d.len().encode_varint(MajorType::ByteString)],
83 Some(format!("bytes({})", d.len())),
84 )];
85 if !d.is_empty() {
86 let mut note: Option<String> = None;
87 if let Ok(a) = str::from_utf8(d)
88 && let Some(b) = sanitized(a)
89 {
90 note = Some(flanked(&b, "\"", "\""));
91 }
92 items.push(DumpItem::new(
93 level + 1,
94 vec![d.to_vec()],
95 note,
96 ));
97 }
98 items
99 }
100 CBORCase::Text(s) => {
101 let header = s.len().encode_varint(MajorType::Text);
102 let header_data = vec![vec![header[0]], header[1..].to_vec()];
103 let utf8_data = s.as_bytes().to_vec();
104 vec![
105 DumpItem::new(
106 level,
107 header_data,
108 Some(format!("text({})", utf8_data.len())),
109 ),
110 DumpItem::new(
111 level + 1,
112 vec![utf8_data],
113 Some(flanked(s, "\"", "\"")),
114 ),
115 ]
116 }
117 CBORCase::Simple(v) => {
118 let data = v.cbor_data();
119 let note = format!("{}", v);
120 vec![DumpItem::new(level, vec![data], Some(note))]
121 }
122 CBORCase::Tagged(tag, item) => {
123 let header = tag.value().encode_varint(MajorType::Tagged);
124 let header_data = vec![vec![header[0]], header[1..].to_vec()];
125 let mut note_components: Vec<String> =
126 vec![format!("tag({})", tag.value())];
127 match opts.tags {
128 TagsStoreOpt::None => {}
129 TagsStoreOpt::Global => {
130 with_tags!(|tags_store: &dyn TagsStoreTrait| {
131 if let Some(name) =
132 tags_store.assigned_name_for_tag(tag)
133 {
134 note_components.push(name);
135 }
136 })
137 }
138 TagsStoreOpt::Custom(tags_store_trait) => {
139 if let Some(name) =
140 tags_store_trait.assigned_name_for_tag(tag)
141 {
142 note_components.push(name);
143 }
144 }
145 }
146 let tag_note = note_components.join(" ");
147 vec![
148 vec![DumpItem::new(level, header_data, Some(tag_note))],
149 item.dump_items(level + 1, opts),
150 ]
151 .into_iter()
152 .flatten()
153 .collect()
154 }
155 CBORCase::Array(array) => {
156 let header = array.len().encode_varint(MajorType::Array);
157 let header_data = vec![vec![header[0]], header[1..].to_vec()];
158 vec![
159 vec![DumpItem::new(
160 level,
161 header_data,
162 Some(format!("array({})", array.len())),
163 )],
164 array
165 .iter()
166 .flat_map(|x| x.dump_items(level + 1, opts))
167 .collect(),
168 ]
169 .into_iter()
170 .flatten()
171 .collect()
172 }
173 CBORCase::Map(m) => {
174 let header = m.len().encode_varint(MajorType::Map);
175 let header_data = vec![vec![header[0]], header[1..].to_vec()];
176 vec![
177 vec![DumpItem::new(
178 level,
179 header_data,
180 Some(format!("map({})", m.len())),
181 )],
182 m.iter()
183 .flat_map(|x| {
184 vec![
185 x.0.dump_items(level + 1, opts),
186 x.1.dump_items(level + 1, opts),
187 ]
188 .into_iter()
189 .flatten()
190 .collect::<Vec<DumpItem>>()
191 })
192 .collect(),
193 ]
194 .into_iter()
195 .flatten()
196 .collect()
197 }
198 }
199 }
200}
201
202#[derive(Debug)]
203struct DumpItem {
204 level: usize,
205 data: Vec<Vec<u8>>,
206 note: Option<String>,
207}
208
209impl DumpItem {
210 fn new(level: usize, data: Vec<Vec<u8>>, note: Option<String>) -> DumpItem {
211 DumpItem { level, data, note }
212 }
213
214 fn format(&self, note_column: usize) -> String {
215 let column_1 = self.format_first_column();
216 let (column_2, padding) = {
217 if let Some(note) = &self.note {
218 let padding_count = 1.max(
219 39.min(note_column as i64) - (column_1.len() as i64) + 1,
220 );
221 let padding = " ".repeat(padding_count.try_into().unwrap());
222 let column_2 = format!("# {}", note);
223 (column_2, padding)
224 } else {
225 ("".to_string(), "".to_string())
226 }
227 };
228 column_1 + &padding + &column_2
229 }
230
231 fn format_first_column(&self) -> String {
232 let indent = " ".repeat(self.level * 4);
233 let hex: Vec<_> = self
234 .data
235 .iter()
236 .map(hex::encode)
237 .filter(|x| !x.is_empty())
238 .collect();
239 let hex = hex.join(" ");
240 indent + &hex
241 }
242}