Skip to main content

miden_node_utils/
formatting.rs

1use std::fmt::Display;
2
3use itertools::Itertools;
4use miden_protocol::transaction::{InputNoteCommitment, InputNotes, OutputNotes};
5use url::Url;
6
7pub fn format_opt<T: Display>(opt: Option<&T>) -> String {
8    opt.map_or("None".to_owned(), ToString::to_string)
9}
10
11pub fn format_input_notes(notes: &InputNotes<InputNoteCommitment>) -> String {
12    format_array(notes.iter().map(|c| match c.header() {
13        Some(header) => format!(
14            "{{ nullifier: {}, note_id: {} }}",
15            c.nullifier().to_hex(),
16            header.id().to_hex()
17        ),
18        None => format!("{{ nullifier: {} }}", c.nullifier().to_hex()),
19    }))
20}
21
22pub fn format_output_notes(notes: &OutputNotes) -> String {
23    format_array(notes.iter().map(|output_note| {
24        let metadata = output_note.metadata();
25        format!(
26            "{{ note_id: {}, note_metadata: {{sender: {}, tag: {} }}}}",
27            output_note.id().to_hex(),
28            metadata.sender(),
29            metadata.tag(),
30        )
31    }))
32}
33
34pub fn format_array(list: impl IntoIterator<Item = impl Display>) -> String {
35    let comma_separated = list.into_iter().join(", ");
36    if comma_separated.is_empty() {
37        "None".to_owned()
38    } else {
39        format!("[{comma_separated}]")
40    }
41}
42
43/// Formats a service endpoint without credentials, query parameters, or fragments.
44pub fn format_endpoint(endpoint: &Url) -> String {
45    let mut endpoint = endpoint.clone();
46    let _ = endpoint.set_username("");
47    let _ = endpoint.set_password(None);
48    endpoint.set_query(None);
49    endpoint.set_fragment(None);
50    endpoint.to_string()
51}
52
53#[cfg(test)]
54mod tests {
55    use miden_protocol::Word;
56    use miden_protocol::account::AccountId;
57    use miden_protocol::note::{
58        NoteAttachments,
59        NoteDetailsCommitment,
60        NoteHeader,
61        NoteMetadata,
62        NoteTag,
63        NoteType,
64        Nullifier,
65        PartialNoteMetadata,
66    };
67    use miden_protocol::transaction::{InputNoteCommitment, InputNotes};
68    use url::Url;
69
70    use super::{format_endpoint, format_input_notes};
71
72    #[test]
73    fn input_notes_are_labeled() {
74        let unresolved_nullifier = Nullifier::from_raw(Word::from([1, 2, 3, 4u32]));
75        let resolved_nullifier = Nullifier::from_raw(Word::from([5, 6, 7, 8u32]));
76        let sender = AccountId::try_from(0xfa00_0000_0000_bb01_0000_cc00_0000_de00_u128).unwrap();
77        let header = NoteHeader::new(
78            NoteDetailsCommitment::from_raw(Word::from([9, 10, 11, 12u32])),
79            NoteMetadata::new(
80                PartialNoteMetadata::new(sender, NoteType::Private).with_tag(NoteTag::new(1)),
81                &NoteAttachments::default(),
82            ),
83        );
84        let notes = InputNotes::new_unchecked(vec![
85            InputNoteCommitment::from(unresolved_nullifier),
86            InputNoteCommitment::from_parts_unchecked(resolved_nullifier, Some(header)),
87        ]);
88
89        assert_eq!(
90            format_input_notes(&notes),
91            format!(
92                "[{{ nullifier: {} }}, {{ nullifier: {}, note_id: {} }}]",
93                unresolved_nullifier.to_hex(),
94                resolved_nullifier.to_hex(),
95                header.id().to_hex(),
96            ),
97        );
98    }
99
100    #[test]
101    fn endpoint_formatting_redacts_sensitive_parts() {
102        let endpoint =
103            Url::parse("https://user:secret@example.com:443/grpc?token=secret#fragment").unwrap();
104
105        assert_eq!(format_endpoint(&endpoint), "https://example.com/grpc");
106    }
107}