bgpsim/
formatter.rs

1// BgpSim: BGP Network Simulator written in Rust
2// Copyright 2022-2024 Tibor Schneider <sctibor@ethz.ch>
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! Module that introduces a formatter to display all types containing `RouterId`.
17
18use std::fmt::Write;
19
20use itertools::{join, Itertools};
21
22use crate::{
23    bgp::{BgpEvent, BgpRibEntry, BgpRoute},
24    config::{Config, ConfigExpr, ConfigExprKey, ConfigModifier, ConfigPatch, RouteMapEdit},
25    event::{BasicEventQueue, Event, FmtPriority},
26    forwarding_state::{ForwardingState, TO_DST},
27    network::Network,
28    ospf::{
29        global::GlobalOspfProcess,
30        local::{LocalOspfProcess, OspfRibEntry},
31        Edge, ExternalEdge, InternalEdge, OspfImpl, OspfProcess,
32    },
33    policies::{FwPolicy, PathCondition, PathConditionCNF, PolicyError, Waypoint},
34    record::{ConvergenceRecording, ConvergenceTrace, FwDelta},
35    route_map::{RouteMap, RouteMapDirection, RouteMapMatch, RouteMapSet, RouteMapState},
36    router::StaticRoute,
37    types::{ConfigError, DeviceError, NetworkError, Prefix, PrefixMap, PrefixSet, RouterId},
38};
39
40/// Trait to format a type that contains RouterIds
41pub trait NetworkFormatter<'n, P: Prefix, Q, Ospf: OspfImpl> {
42    /// Return a formatted string by looking up router IDs in the network.
43    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String;
44
45    /// Return a multiline struct that can be formatted and displayed.
46    ///
47    /// You should typically not have to re-implement that function. Instead, re-implement the
48    /// `fmt_multiline_indent` function if you want your type to have a multiline formatting output.
49    fn fmt_multiline(&self, net: &'n Network<P, Q, Ospf>) -> String {
50        self.fmt_multiline_indent(net, 0)
51    }
52
53    /// Return a multiline struct that can be formatted and displayed.
54    ///
55    /// Overwrite this function for your type to give the type a special multiline formatting
56    /// output. If you don't overwrite this function, then your type will not have a special
57    /// multiline output.
58    fn fmt_multiline_indent(&self, net: &'n Network<P, Q, Ospf>, _indent: usize) -> String {
59        self.fmt(net)
60    }
61}
62
63/// A specialized network formatter, to be defined for types on which the `NetworkFormatter` is
64/// already defined (automatically)
65pub trait NetworkFormatterExt<'n, P: Prefix, Q, Ospf: OspfImpl> {
66    /// Return a special formatted string.
67    fn fmt_ext(&self, net: &'n Network<P, Q, Ospf>) -> String;
68}
69
70impl<'n, P, Q, Ospf, T> NetworkFormatter<'n, P, Q, Ospf> for &T
71where
72    P: Prefix,
73    Ospf: OspfImpl,
74    T: NetworkFormatter<'n, P, Q, Ospf>,
75{
76    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
77        T::fmt(*self, net)
78    }
79
80    fn fmt_multiline_indent(&self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String {
81        T::fmt_multiline_indent(*self, net, indent)
82    }
83}
84
85impl<'n, P, Q, Ospf, T> NetworkFormatter<'n, P, Q, Ospf> for &mut T
86where
87    P: Prefix,
88    Ospf: OspfImpl,
89    T: NetworkFormatter<'n, P, Q, Ospf>,
90{
91    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
92        T::fmt(*self, net)
93    }
94
95    fn fmt_multiline_indent(&self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String {
96        T::fmt_multiline_indent(*self, net, indent)
97    }
98}
99
100macro_rules! fmt_with_display {
101    ($t:ty) => {
102        impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for $t {
103            fn fmt(&self, _net: &'n Network<P, Q, Ospf>) -> String {
104                self.to_string()
105            }
106        }
107    };
108    ($($t:ty),*) => {
109        $(fmt_with_display!{$t})*
110    }
111}
112
113fmt_with_display! {u8, u16, u32, u64, u128, usize}
114fmt_with_display! {i8, i16, i32, i64, i128, isize}
115fmt_with_display! {f32, f64, &str, String}
116fmt_with_display! {std::net::Ipv4Addr, ipnet::Ipv4Net}
117fmt_with_display! {crate::types::SinglePrefix, crate::types::SimplePrefix, crate::types::Ipv4Prefix}
118fmt_with_display! {std::io::Error}
119
120macro_rules! fmt_iterable {
121    ($t:ty, $k:ident, $k_multiline:ident) => {
122        impl<'n, P, Q, Ospf, T> NetworkFormatter<'n, P, Q, Ospf> for $t
123        where
124            P: Prefix,
125            Ospf: OspfImpl,
126            T: NetworkFormatter<'n, P, Q, Ospf>,
127        {
128            fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
129                self.iter().$k(net)
130            }
131
132            fn fmt_multiline_indent(&self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String {
133                self.iter().$k_multiline(net, indent)
134            }
135        }
136    };
137}
138
139fmt_iterable! {&[T], fmt_list, fmt_list_multiline}
140fmt_iterable! {Vec<T>, fmt_list, fmt_list_multiline}
141fmt_iterable! {std::collections::HashSet<T>, fmt_set, fmt_set_multiline}
142fmt_iterable! {std::collections::BTreeSet<T>, fmt_set, fmt_set_multiline}
143fmt_iterable! {std::collections::VecDeque<T>, fmt_list, fmt_list_multiline}
144fmt_iterable! {std::collections::BinaryHeap<T>, fmt_list, fmt_list_multiline}
145
146macro_rules! fmt_mapping {
147    ($t:ty) => {
148        impl<'n, P, Q, Ospf, K, V> NetworkFormatter<'n, P, Q, Ospf> for $t
149        where
150            P: Prefix,
151            Ospf: OspfImpl,
152            K: NetworkFormatter<'n, P, Q, Ospf>,
153            V: NetworkFormatter<'n, P, Q, Ospf>,
154        {
155            fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
156                self.iter().fmt_map(net)
157            }
158
159            fn fmt_multiline_indent(&self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String {
160                self.iter().fmt_map_multiline(net, indent)
161            }
162        }
163    };
164}
165
166fmt_mapping! {std::collections::HashMap<K, V>}
167fmt_mapping! {std::collections::BTreeMap<K, V>}
168
169macro_rules! fmt_prefix_trie {
170    ($t:ty) => {
171        impl<'n, P, Q, Ospf, K, V> NetworkFormatter<'n, P, Q, Ospf> for $t
172        where
173            P: Prefix,
174            Ospf: OspfImpl,
175            K: NetworkFormatter<'n, P, Q, Ospf> + prefix_trie::Prefix,
176            V: NetworkFormatter<'n, P, Q, Ospf>,
177        {
178            fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
179                self.iter().fmt_map(net)
180            }
181
182            fn fmt_multiline_indent(&self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String {
183                self.iter().fmt_map_multiline(net, indent)
184            }
185        }
186    };
187}
188
189fmt_prefix_trie! {prefix_trie::PrefixMap<K, V>}
190fmt_prefix_trie! {prefix_trie::trieview::TrieView<'_, K, V>}
191
192macro_rules! fmt_tuple {
193    () => {
194        impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for () {
195            fn fmt(&self, _net: &'n Network<P, Q, Ospf>) -> String {
196                "()".to_string()
197            }
198        }
199    };
200    ($t1:ident, ) => {
201        impl<'n, P, Q, Ospf, $t1> NetworkFormatter<'n, P, Q, Ospf> for ($t1,)
202        where
203            P: Prefix,
204            Ospf: OspfImpl,
205            $t1: NetworkFormatter<'n, P, Q, Ospf>,
206        {
207            fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
208                #[allow(non_snake_case)]
209                let ($t1,) = self;
210                let mut s = "(".to_string();
211                s.push_str(&$t1.fmt(net));
212                s.push(')');
213                s
214            }
215
216            fn fmt_multiline_indent(&self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String {
217                #[allow(non_snake_case)]
218                let ($t1,) = self;
219                let mut s = "(".to_string();
220                s.push_str(&$t1.fmt_multiline_indent(net, indent));
221                s.push(')');
222                s
223            }
224        }
225    };
226    ($t1:ident, $($t:ident),+) => {
227        impl<'n, P, Q, Ospf, $t1, $($t),*> NetworkFormatter<'n, P, Q, Ospf> for ($t1, $($t),*)
228        where
229            P: Prefix,
230            Ospf: OspfImpl,
231            $t1: NetworkFormatter<'n, P, Q, Ospf>,
232            $($t: NetworkFormatter<'n, P, Q, Ospf>),*
233        {
234            fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
235                #[allow(non_snake_case)]
236                let ($t1, $($t),*) = self;
237                let mut s = "(".to_string();
238                s.push_str(&$t1.fmt(net));
239                $({
240                    s.push_str(", ");
241                    s.push_str(&$t.fmt(net));
242                })*
243                s.push(')');
244                s
245            }
246
247            fn fmt_multiline_indent(&self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String {
248                let spc = " ".repeat(indent);
249                #[allow(non_snake_case)]
250                let ($t1, $($t),*) = self;
251                let mut s = "(\n  ".to_string();
252                s.push_str(&spc);
253                s.push_str(&$t1.fmt_multiline_indent(net, indent + 2));
254                $({
255                    s.push_str(",\n  ");
256                    s.push_str(&spc);
257                    s.push_str(&$t.fmt_multiline_indent(net, indent + 2));
258                })*
259                s.push('\n');
260                s.push_str(&spc);
261                s.push(')');
262                s
263            }
264        }
265    };
266}
267
268fmt_tuple!();
269fmt_tuple!(T1,);
270fmt_tuple!(T1, T2);
271fmt_tuple!(T1, T2, T3);
272fmt_tuple!(T1, T2, T3, T4);
273fmt_tuple!(T1, T2, T3, T4, T5);
274fmt_tuple!(T1, T2, T3, T4, T5, T6);
275fmt_tuple!(T1, T2, T3, T4, T5, T6, T7);
276fmt_tuple!(T1, T2, T3, T4, T5, T6, T7, T8);
277
278impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for RouterId {
279    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
280        match net.get_router(*self) {
281            Ok(r) => r.name().to_string(),
282            Err(_) => "?".to_string(),
283        }
284    }
285}
286
287impl<'n, P, Q, Ospf, T> NetworkFormatter<'n, P, Q, Ospf> for Option<T>
288where
289    P: Prefix,
290    Ospf: OspfImpl,
291    T: NetworkFormatter<'n, P, Q, Ospf>,
292{
293    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
294        match self {
295            Some(x) => format!("Some({})", x.fmt(net)),
296            None => "None".to_string(),
297        }
298    }
299
300    fn fmt_multiline_indent(&self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String {
301        match self {
302            Some(x) => format!("Some({})", x.fmt_multiline_indent(net, indent)),
303            None => "None".to_string(),
304        }
305    }
306}
307
308impl<'n, P, Q, Ospf, T, E> NetworkFormatter<'n, P, Q, Ospf> for Result<T, E>
309where
310    P: Prefix,
311    Ospf: OspfImpl,
312    T: NetworkFormatter<'n, P, Q, Ospf>,
313    E: NetworkFormatter<'n, P, Q, Ospf>,
314{
315    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
316        match self {
317            Ok(x) => format!("Ok({})", x.fmt(net)),
318            Err(x) => format!("Err({})", x.fmt(net)),
319        }
320    }
321
322    fn fmt_multiline_indent(&self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String {
323        match self {
324            Ok(x) => format!("Ok({})", x.fmt_multiline_indent(net, indent)),
325            Err(x) => format!("Err({})", x.fmt_multiline_indent(net, indent)),
326        }
327    }
328}
329
330/// Formatting a sequence as a set, list, or a path.
331pub trait NetworkFormatterSequence<'n, P, Q, Ospf>
332where
333    P: Prefix,
334    Ospf: OspfImpl,
335{
336    /// Format the iterator as a set, e.g., `{a, b, c}`.
337    fn fmt_set(self, net: &'n Network<P, Q, Ospf>) -> String;
338
339    /// Format the iterator as a set over multiple lines.
340    fn fmt_set_multiline(self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String;
341
342    /// Format the iterator as a list, e.g., `[a, b, c]`.
343    fn fmt_list(self, net: &'n Network<P, Q, Ospf>) -> String;
344
345    /// Format the iterator as a milti-line list.
346    fn fmt_list_multiline(self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String;
347
348    /// Format the iterator as a path, e.g., `a -> b -> c`.
349    fn fmt_path(self, net: &'n Network<P, Q, Ospf>) -> String;
350}
351
352impl<'n, P, Q, Ospf, I, T> NetworkFormatterSequence<'n, P, Q, Ospf> for I
353where
354    P: Prefix,
355    Ospf: OspfImpl,
356    I: IntoIterator<Item = T>,
357    T: NetworkFormatter<'n, P, Q, Ospf>,
358{
359    fn fmt_set(self, net: &'n Network<P, Q, Ospf>) -> String {
360        format!(
361            "{{{}}}",
362            self.into_iter().map(|t| t.fmt(net).to_string()).join(", ")
363        )
364    }
365
366    fn fmt_set_multiline(self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String {
367        let spc = " ".repeat(indent);
368        format!(
369            "{{\n{spc}  {}\n{spc}}}",
370            self.into_iter()
371                .map(|t| t.fmt_multiline_indent(net, indent + 2))
372                .join(&format!(",\n{spc}  "))
373        )
374    }
375
376    fn fmt_list(self, net: &'n Network<P, Q, Ospf>) -> String {
377        format!(
378            "[{}]",
379            self.into_iter().map(|t| t.fmt(net).to_string()).join(", ")
380        )
381    }
382
383    fn fmt_list_multiline(self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String {
384        let spc = " ".repeat(indent);
385        format!(
386            "[\n{spc}  {}\n{spc}]",
387            self.into_iter()
388                .map(|t| t.fmt_multiline_indent(net, indent + 2).to_string())
389                .join(&format!(",\n{spc}  "))
390        )
391    }
392
393    fn fmt_path(self, net: &'n Network<P, Q, Ospf>) -> String {
394        self.into_iter()
395            .map(|t| t.fmt(net).to_string())
396            .join(" -> ")
397    }
398}
399
400/// Formatting a mapping
401pub trait NetworkFormatterMap<'n, P, Q, Ospf>
402where
403    P: Prefix,
404    Ospf: OspfImpl,
405{
406    /// Format the map on a single line, e.g., `{a: 1, b: 2}`
407    fn fmt_map(self, net: &'n Network<P, Q, Ospf>) -> String;
408
409    /// Format the iterator as a list, e.g., `[a, b, c]`.
410    fn fmt_map_multiline(self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String;
411}
412
413impl<'n, P, Q, Ospf, I, K, V> NetworkFormatterMap<'n, P, Q, Ospf> for I
414where
415    P: Prefix,
416    Ospf: OspfImpl,
417    I: IntoIterator<Item = (K, V)>,
418    K: NetworkFormatter<'n, P, Q, Ospf>,
419    V: NetworkFormatter<'n, P, Q, Ospf>,
420{
421    fn fmt_map(self, net: &'n Network<P, Q, Ospf>) -> String {
422        format!(
423            "{{{}}}",
424            self.into_iter()
425                .map(|(k, v)| format!("{}: {}", k.fmt(net), v.fmt(net)))
426                .join(", ")
427        )
428    }
429
430    fn fmt_map_multiline(self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String {
431        let spc = " ".repeat(indent);
432        format!(
433            "{{\n{spc}  {}\n{spc}}}",
434            self.into_iter()
435                .map(|(k, v)| format!(
436                    "{}: {}",
437                    k.fmt(net),
438                    v.fmt_multiline_indent(net, indent + 2)
439                ))
440                .join(&format!(",\n{spc}  "))
441        )
442    }
443}
444
445/// Formatting a sequence of sequences.
446pub trait NetworkFormatterNestedSequence<'n, P, Q, Ospf>
447where
448    P: Prefix,
449    Ospf: OspfImpl,
450{
451    /// Format path options on a single line, e.g., `a -> b -> c | a -> c`
452    fn fmt_path_options(self, net: &'n Network<P, Q, Ospf>) -> String;
453
454    /// Format path options as a set on a single line, e.g., `{a -> b -> c, a -> c}`
455    fn fmt_path_set(self, net: &'n Network<P, Q, Ospf>) -> String;
456
457    /// Format path options as a seton multiple lines.
458    fn fmt_path_multiline(self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String;
459}
460
461impl<'n, P, Q, Ospf, I, T> NetworkFormatterNestedSequence<'n, P, Q, Ospf> for I
462where
463    P: Prefix,
464    Ospf: OspfImpl,
465    I: IntoIterator<Item = T>,
466    T: NetworkFormatterSequence<'n, P, Q, Ospf>,
467{
468    fn fmt_path_options(self, net: &'n Network<P, Q, Ospf>) -> String {
469        self.into_iter().map(|p| p.fmt_path(net)).join(" | ")
470    }
471
472    fn fmt_path_set(self, net: &'n Network<P, Q, Ospf>) -> String {
473        format!(
474            "{{{}}}",
475            self.into_iter().map(|p| p.fmt_path(net)).join(", ")
476        )
477    }
478
479    fn fmt_path_multiline(self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String {
480        let spc = " ".repeat(indent);
481        format!(
482            "{{\n{spc}  {}\n}}",
483            self.into_iter()
484                .map(|p| p.fmt_path(net))
485                .join(&format!(",\n  {spc}"))
486        )
487    }
488}
489
490//
491// Forwarding State
492//
493
494impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for ForwardingState<P> {
495    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
496        let mut result = String::new();
497        let f = &mut result;
498        for (router, table) in self.state.iter() {
499            writeln!(f, "{}:", router.fmt(net)).unwrap();
500            for (prefix, next_hops) in table.iter() {
501                let next_hops_str = if next_hops.is_empty() {
502                    "XX".to_string()
503                } else if next_hops == &[*TO_DST] {
504                    "DST".to_string()
505                } else {
506                    next_hops.iter().map(|r| r.fmt(net)).join("|")
507                };
508                writeln!(
509                    f,
510                    "  {} -> {}; reversed: [{}]",
511                    prefix,
512                    next_hops_str,
513                    self.reversed
514                        .get(router)
515                        .and_then(|table| table.get(prefix))
516                        .map(|s| s.iter().map(|r| r.fmt(net)).join(", "))
517                        .unwrap_or_default(),
518                )
519                .unwrap();
520            }
521        }
522        result
523    }
524}
525
526//
527// Event
528//
529
530impl<'n, P: Prefix, Q, Ospf: OspfImpl, T: FmtPriority> NetworkFormatter<'n, P, Q, Ospf>
531    for Event<P, T>
532{
533    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
534        match self {
535            Event::Bgp { p, src, dst, e } => format!(
536                "BGP Event: {} -> {}: {} {}",
537                src.fmt(net),
538                dst.fmt(net),
539                e.fmt(net),
540                p.fmt()
541            ),
542            Event::Ospf {
543                p,
544                src,
545                dst,
546                area,
547                e,
548            } => format!(
549                "OSPF Event: {} -> {} ({area}): {} {}",
550                src.fmt(net),
551                dst.fmt(net),
552                e.fmt(net),
553                p.fmt()
554            ),
555        }
556    }
557}
558
559impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for BgpEvent<P> {
560    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
561        match self {
562            BgpEvent::Withdraw(prefix) => format!("Withdraw {prefix}"),
563            BgpEvent::Update(route) => format!("Update {}", route.fmt(net)),
564        }
565    }
566}
567
568//
569// BGP Route
570//
571
572impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for BgpRoute<P> {
573    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
574        format!(
575            "{{ {}, path: [{}], next hop: {}{}{}{} }}",
576            self.prefix,
577            self.as_path.iter().join(", "),
578            self.next_hop.fmt(net),
579            if let Some(local_pref) = self.local_pref {
580                format!(", local pref: {local_pref}")
581            } else {
582                String::new()
583            },
584            if let Some(med) = self.med {
585                format!(", MED: {med}")
586            } else {
587                String::new()
588            },
589            if self.community.is_empty() {
590                String::new()
591            } else {
592                format!(", community: {}", join(self.community.iter(), ";"))
593            },
594        )
595    }
596}
597
598//
599// BGP RIB Entry
600//
601
602impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for BgpRibEntry<P> {
603    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
604        format!(
605            "{p}, as_path: {path:?}, weight: {w}, local_pref: {lp}, MED: {med}, IGP Cost: {cost}, next_hop: {nh}, from: {next}{comm}",
606            p = self.route.prefix,
607            path = self.route.as_path.iter().map(|x| x.0).collect::<Vec<u32>>(),
608            w = self.weight,
609            lp = self.route.local_pref.unwrap_or(100),
610            med = self.route.med.unwrap_or(0),
611            cost = self.igp_cost.unwrap_or_default(),
612            nh = self.route.next_hop.fmt(net),
613            next = self.from_id.fmt(net),
614            comm = if self.route.community.is_empty() {
615                String::from("")
616            } else {
617                format!(", communities = [{}]", self.route.community.iter().join(", "))
618            },
619        )
620    }
621}
622
623//
624// Route Map
625//
626
627impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for RouteMapMatch<P> {
628    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
629        match self {
630            RouteMapMatch::Prefix(_pl) => {
631                format!("Prefix in {{{}}}", _pl.iter().join(", "))
632            }
633            RouteMapMatch::AsPath(c) => format!("{c}"),
634            RouteMapMatch::NextHop(nh) => format!("NextHop == {}", nh.fmt(net)),
635            RouteMapMatch::Community(c) => format!("Community {c}"),
636            RouteMapMatch::DenyCommunity(c) => format!("Deny Community {c}"),
637        }
638    }
639}
640
641impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for RouteMapSet {
642    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
643        match self {
644            RouteMapSet::NextHop(nh) => format!("NextHop = {}", nh.fmt(net)),
645            RouteMapSet::Weight(Some(w)) => format!("Weight = {w}"),
646            RouteMapSet::Weight(None) => "clear Weight".to_string(),
647            RouteMapSet::LocalPref(Some(lp)) => format!("LocalPref = {lp}"),
648            RouteMapSet::LocalPref(None) => "clear LocalPref".to_string(),
649            RouteMapSet::Med(Some(med)) => format!("MED = {med}"),
650            RouteMapSet::Med(None) => "clear MED".to_string(),
651            RouteMapSet::IgpCost(w) => format!("IgpCost = {w:.2}"),
652            RouteMapSet::SetCommunity(c) => format!("Set community {c}"),
653            RouteMapSet::DelCommunity(c) => format!("Remove community {c}"),
654        }
655    }
656}
657
658impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for RouteMap<P> {
659    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
660        format!(
661            "{} {}{}.",
662            match self.state {
663                RouteMapState::Allow => "allow",
664                RouteMapState::Deny => "deny ",
665            },
666            if self.conds.is_empty() {
667                String::from("*")
668            } else {
669                self.conds.iter().map(|c| c.fmt(net)).join(" AND ")
670            },
671            if self.set.is_empty() {
672                String::from("")
673            } else {
674                format!("; {}", self.set.iter().map(|s| s.fmt(net)).join(", "))
675            }
676        )
677    }
678}
679
680//
681// Configuration
682//
683
684impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for ConfigExpr<P> {
685    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
686        match self {
687            ConfigExpr::IgpLinkWeight {
688                source,
689                target,
690                weight,
691            } => format!(
692                "IGP Link Weight: {} -> {}: {}",
693                source.fmt(net),
694                target.fmt(net),
695                weight
696            ),
697            ConfigExpr::OspfArea {
698                source,
699                target,
700                area,
701            } => format!(
702                "OSPF Area: {} -- {}: {}",
703                source.fmt(net),
704                target.fmt(net),
705                area
706            ),
707            ConfigExpr::BgpSession {
708                source,
709                target,
710                target_is_client: true,
711            } => format!(
712                "BGP Session: {} -> {} (RR client)",
713                source.fmt(net),
714                target.fmt(net),
715            ),
716            ConfigExpr::BgpSession { source, target, .. } => {
717                format!("BGP Session: {} -> {}", source.fmt(net), target.fmt(net),)
718            }
719            ConfigExpr::BgpRouteMap {
720                router,
721                neighbor,
722                direction,
723                map,
724            } => format!(
725                "BGP Route Map on {} from {} [{}:{}]: {}",
726                router.fmt(net),
727                neighbor.fmt(net),
728                match direction {
729                    RouteMapDirection::Incoming => "in",
730                    RouteMapDirection::Outgoing => "out",
731                },
732                map.order,
733                map.fmt(net)
734            ),
735            ConfigExpr::StaticRoute {
736                router,
737                prefix,
738                target,
739            } => format!(
740                "Static Route: {}: {} via {}",
741                router.fmt(net),
742                prefix,
743                target.fmt(net)
744            ),
745            ConfigExpr::LoadBalancing { router } => {
746                format!("Load Balancing: {}", router.fmt(net))
747            }
748            ConfigExpr::AdvertiseRoute {
749                router,
750                prefix,
751                as_path,
752                med,
753                community,
754            } => {
755                let mut options = Vec::new();
756                if !as_path.is_empty() {
757                    options.push(format!("AS path [{}]", as_path.iter().join(" ")));
758                }
759                if let Some(med) = med {
760                    options.push(format!("MED {med}"));
761                }
762                if !community.is_empty() {
763                    options.push(format!("Community [{}]", community.iter().join(" ")));
764                }
765                let opt = if options.is_empty() {
766                    String::new()
767                } else {
768                    format!(" with {}", options.into_iter().join("; "))
769                };
770                format!("Advertise Route on {} for {prefix}{opt}", router.fmt(net))
771            }
772        }
773    }
774}
775
776impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for ConfigExprKey<P> {
777    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
778        match self {
779            ConfigExprKey::IgpLinkWeight { source, target } => format!(
780                "IGP Link Weight: {} -> {}",
781                source.fmt(net),
782                target.fmt(net),
783            ),
784            ConfigExprKey::OspfArea { router_a, router_b } => {
785                format!("OSPF Area: {} -- {}", router_a.fmt(net), router_b.fmt(net),)
786            }
787            ConfigExprKey::BgpSession {
788                speaker_a,
789                speaker_b,
790            } => format!(
791                "BGP Session: {} <-> {}",
792                speaker_a.fmt(net),
793                speaker_b.fmt(net),
794            ),
795            ConfigExprKey::BgpRouteMap {
796                router,
797                neighbor,
798                direction,
799                order,
800            } => format!(
801                "BGP Route Map on {} from {} [{}:{}]",
802                router.fmt(net),
803                neighbor.fmt(net),
804                direction,
805                order
806            ),
807            ConfigExprKey::StaticRoute { router, prefix } => {
808                format!("Static Route: {}: {}", router.fmt(net), prefix,)
809            }
810            ConfigExprKey::LoadBalancing { router } => {
811                format!("Load Balancing: {}", router.fmt(net))
812            }
813            ConfigExprKey::AdvertiseRoute { router, prefix } => {
814                format!("Advertise Route on {} for {prefix}", router.fmt(net))
815            }
816        }
817    }
818}
819
820impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for ConfigModifier<P> {
821    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
822        match self {
823            ConfigModifier::Insert(e) => format!("INSERT {}", e.fmt(net)),
824            ConfigModifier::Remove(e) => format!("REMOVE {}", e.fmt(net)),
825            ConfigModifier::Update { from: _, to } => format!("MODIFY {}", to.fmt(net)),
826            ConfigModifier::BatchRouteMapEdit { router, updates } => format!(
827                "BATCH at {}: {}",
828                router.fmt(net),
829                updates.iter().map(|u| u.fmt(net)).join(", ")
830            ),
831        }
832    }
833}
834
835impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for RouteMapEdit<P> {
836    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
837        let dir = match self.direction {
838            RouteMapDirection::Incoming => "in",
839            RouteMapDirection::Outgoing => "out",
840        };
841        let peer = self.neighbor.fmt(net);
842        match (self.old.as_ref(), self.new.as_ref()) {
843            (None, None) => String::new(),
844            (Some(old), None) => format!("del [{peer}:{dir}:{}]", old.order),
845            (None, Some(new)) => format!("add [{peer}:{dir}:{}] {}", new.order, new.fmt(net)),
846            (Some(_), Some(new)) => format!("upd [{peer}:{dir}:{}] {}", new.order, new.fmt(net)),
847        }
848    }
849}
850
851impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for ConfigPatch<P> {
852    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
853        let mut result = String::new();
854        let f = &mut result;
855        writeln!(f, "ConfigPatch {{").unwrap();
856        for modifier in self.modifiers.iter() {
857            writeln!(f, "    {}", modifier.fmt(net)).unwrap();
858        }
859        writeln!(f, "}}").unwrap();
860        result
861    }
862}
863
864impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for Config<P> {
865    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
866        let mut result = String::new();
867        let f = &mut result;
868        writeln!(f, "Config {{").unwrap();
869        for expr in self.iter() {
870            writeln!(f, "    {}", expr.fmt(net)).unwrap();
871        }
872        writeln!(f, "}}").unwrap();
873        result
874    }
875}
876
877//
878// Recording
879//
880
881impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatterExt<'n, P, Q, Ospf> for FwDelta {
882    fn fmt_ext(&self, net: &'n Network<P, Q, Ospf>) -> String {
883        format!(
884            "{}: {} => {}",
885            self.0.fmt(net),
886            self.1.iter().map(|r| r.fmt(net)).join("|"),
887            self.2.iter().map(|r| r.fmt(net)).join("|"),
888        )
889    }
890}
891
892impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatterExt<'n, P, Q, Ospf> for ConvergenceTrace {
893    fn fmt_ext(&self, net: &'n Network<P, Q, Ospf>) -> String {
894        self.iter()
895            .enumerate()
896            .map(|(i, (deltas, time))| {
897                format!(
898                    "step {}{}: {}",
899                    i,
900                    time.as_ref()
901                        .map(|t| format!("at time {t}"))
902                        .unwrap_or_default(),
903                    deltas.iter().map(|x| x.fmt_ext(net)).join(", ")
904                )
905            })
906            .join("\n")
907    }
908}
909
910impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for ConvergenceRecording {
911    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
912        self.trace().fmt_ext(net)
913    }
914}
915
916//
917// Policies
918//
919
920impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for FwPolicy<P> {
921    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
922        match self {
923            Self::Reachable(r, p) => {
924                format!("Reachable({}, {})", r.fmt(net), p)
925            }
926            Self::NotReachable(r, p) => format!("Isolation({}, {})", r.fmt(net), p),
927            Self::PathCondition(r, p, c) => {
928                format!("Path({}, {}, {})", r.fmt(net), p, c.fmt(net))
929            }
930            Self::LoopFree(r, p) => {
931                format!("LoopFree({}, {})", r.fmt(net), p)
932            }
933            Self::LoadBalancing(r, p, k) => format!("LoadBalancing({}, {}, {})", r.fmt(net), p, k),
934            Self::LoadBalancingVertexDisjoint(r, p, k) => {
935                format!("LoadBalancingVertexDisjoint({}, {}, {})", r.fmt(net), p, k)
936            }
937            Self::LoadBalancingEdgeDisjoint(r, p, k) => {
938                format!("LoadBalancingEdgeDisjoint({}, {}, {})", r.fmt(net), p, k)
939            }
940        }
941    }
942}
943
944impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for Edge {
945    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
946        match self {
947            Edge::Internal(i) => i.fmt(net),
948            Edge::External(e) => e.fmt(net),
949        }
950    }
951}
952
953impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for InternalEdge {
954    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
955        format!(
956            "{} -> {} (weight {}, area {})",
957            self.src.fmt(net),
958            self.dst.fmt(net),
959            self.weight,
960            self.area
961        )
962    }
963}
964
965impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for ExternalEdge {
966    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
967        format!("{} -> {}", self.int.fmt(net), self.ext.fmt(net))
968    }
969}
970
971impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for PathCondition {
972    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
973        match self {
974            Self::Node(r) => format!("[* {} *]", r.fmt(net)),
975            Self::Edge(a, b) => format!("[* ({},{}) *]", a.fmt(net), b.fmt(net)),
976            Self::And(v) if v.is_empty() => String::from("(true)"),
977            Self::And(v) => format!("({})", v.iter().map(|c| c.fmt(net)).join(" && ")),
978            Self::Or(v) if v.is_empty() => String::from("(false)"),
979            Self::Or(v) => format!("({})", v.iter().map(|c| c.fmt(net)).join(" || ")),
980            Self::Not(c) => format!("!{}", c.fmt(net)),
981            Self::Positional(v) => format!("[{}]", v.iter().map(|p| p.fmt(net)).join(" ")),
982        }
983    }
984}
985
986impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for Waypoint {
987    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
988        match self {
989            Waypoint::Any => "?".to_string(),
990            Waypoint::Star => "*".to_string(),
991            Waypoint::Fix(r) => r.fmt(net),
992        }
993    }
994}
995
996impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for PathConditionCNF {
997    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
998        PathCondition::from(self.clone()).fmt(net)
999    }
1000}
1001
1002impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for PolicyError<P> {
1003    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
1004        match self {
1005            PolicyError::BlackHole { router, prefix } => {
1006                format!("Black hole for {} at {}", prefix, router.fmt(net),)
1007            }
1008            PolicyError::ForwardingLoop { path, prefix } => format!(
1009                "Forwarding loop for {}: {} -> {}",
1010                prefix,
1011                path.iter().fmt_list(net),
1012                path.first().unwrap().fmt(net),
1013            ),
1014            PolicyError::PathCondition {
1015                path,
1016                condition,
1017                prefix,
1018            } => format!(
1019                "Path condition invalidated for {}: path: {}, condition: {}",
1020                prefix,
1021                path.iter().fmt_path(net),
1022                condition.fmt(net)
1023            ),
1024            PolicyError::UnallowedPathExists {
1025                router,
1026                prefix,
1027                paths,
1028            } => format!(
1029                "{} can reach unallowed {} via path(s) {}",
1030                router.fmt(net),
1031                prefix,
1032                paths.iter().fmt_path_set(net)
1033            ),
1034            PolicyError::InsufficientPathsExist { router, prefix, k } => format!(
1035                "{} cannot reach {} via {} paths",
1036                router.fmt(net),
1037                prefix,
1038                k
1039            ),
1040            PolicyError::NoConvergence => String::from("No Convergence"),
1041        }
1042    }
1043}
1044
1045impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for StaticRoute {
1046    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
1047        match self {
1048            StaticRoute::Direct(r) => r.fmt(net).to_string(),
1049            StaticRoute::Indirect(r) => format!("{} (indirect)", r.fmt(net)),
1050            StaticRoute::Drop => "drop".to_string(),
1051        }
1052    }
1053}
1054
1055impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for NetworkError {
1056    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
1057        match self {
1058            NetworkError::DeviceError(e) => e.fmt(net),
1059            NetworkError::ConfigError(e) => e.fmt(net).to_string(),
1060            NetworkError::UnknownAS(asn) => format!("No router in {asn} exists."),
1061            NetworkError::DeviceNotFound(r) => format!("Device with id={} not found!", r.index()),
1062            NetworkError::DeviceNameNotFound(n) => format!("Device with name={n} not found!"),
1063            NetworkError::LinkNotFound(src, dst) => format!(
1064                "No link between {} and {} exists!",
1065                src.fmt(net),
1066                dst.fmt(net)
1067            ),
1068            NetworkError::ForwardingLoop {
1069                to_loop,
1070                first_loop,
1071            } => format!(
1072                "Forwarding loop found! {}, {}",
1073                to_loop.fmt_list(net),
1074                first_loop.fmt_list(net)
1075            ),
1076            NetworkError::ForwardingBlackHole(p) => {
1077                format!("Black hole found! {}", p.fmt_path(net))
1078            }
1079            NetworkError::InvalidBgpSessionType(src, dst, ty) => format!(
1080                "BGP session of type {} cannot be established from {} to {}!",
1081                ty,
1082                src.fmt(net),
1083                dst.fmt(net)
1084            ),
1085            NetworkError::InconsistentBgpSession(src, dst) => format!(
1086                "{} and {} maintain an inconsistent BGP session!",
1087                src.fmt(net),
1088                dst.fmt(net)
1089            ),
1090            NetworkError::NoConvergence => String::from("Network could not converge!"),
1091            NetworkError::InvalidBgpTable(r) => {
1092                format!("Router {} has an invalid BGP table!", r.fmt(net))
1093            }
1094            NetworkError::JsonError(e) => format!("Json error occurred: {e}"),
1095            NetworkError::InconsistentOspfState(k) => {
1096                format!("OSPF state is inconsistent for key {}", k.fmt(net))
1097            }
1098        }
1099    }
1100}
1101
1102impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for DeviceError {
1103    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
1104        match self {
1105            DeviceError::RouterNotFound(r) => {
1106                format!("Router {} was not found in the IGP table!", r.fmt(net))
1107            }
1108            DeviceError::NoBgpSession(r) => {
1109                format!("No BGP session established with {}!", r.fmt(net))
1110            }
1111            DeviceError::AlreadyOspfNeighbors(r, n) => {
1112                format!(
1113                    "Router {} is already an OSPF neighbor of {}.",
1114                    n.fmt(net),
1115                    r.fmt(net)
1116                )
1117            }
1118            DeviceError::NotAnOspfNeighbor(r, n) => {
1119                format!(
1120                    "Router {} is not an OSPF neighbor of {}",
1121                    n.fmt(net),
1122                    r.fmt(net)
1123                )
1124            }
1125            DeviceError::WrongRouter(executing, recipiant) => format!(
1126                "Router {} cannot execute an event destined for {}",
1127                executing.fmt(net),
1128                recipiant.fmt(net)
1129            ),
1130        }
1131    }
1132}
1133
1134impl<'n, P: Prefix + std::fmt::Debug, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf>
1135    for ConfigError
1136{
1137    fn fmt(&self, _net: &'n Network<P, Q, Ospf>) -> String {
1138        match self {
1139            ConfigError::ConfigExprOverload { old, new } => {
1140                format!("Adding `{old:?}` would overwrite `{new:?}`!",)
1141            }
1142            ConfigError::ConfigModifier(m) => format!("Could not apply modifier: {m:?}"),
1143        }
1144    }
1145}
1146
1147//
1148// Formatting the queue
1149//
1150impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for BasicEventQueue<P> {
1151    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
1152        self.0.iter().map(|e| e.fmt(net)).join("\n")
1153    }
1154}
1155
1156//
1157// formatting OSPF Rib Entries
1158//
1159impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for OspfRibEntry {
1160    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
1161        let kind = if self.inter_area { "R" } else { "I" };
1162        let nhs = self.fibs.iter().map(|r| r.fmt(net)).join(" || ");
1163        let nhs = if nhs.is_empty() {
1164            "XX".to_string()
1165        } else {
1166            nhs
1167        };
1168        let cost = self.cost.into_inner();
1169        format!("{} -> {nhs} (cost: {cost} {kind})", self.router_id.fmt(net))
1170    }
1171}
1172
1173impl<'n, P: Prefix, Q, Ospf: OspfImpl<Process = GlobalOspfProcess>> NetworkFormatter<'n, P, Q, Ospf>
1174    for GlobalOspfProcess
1175{
1176    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
1177        OspfProcess::fmt(self, net)
1178    }
1179}
1180
1181impl<'n, P: Prefix, Q, Ospf: OspfImpl<Process = LocalOspfProcess>> NetworkFormatter<'n, P, Q, Ospf>
1182    for LocalOspfProcess
1183{
1184    fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
1185        OspfProcess::fmt(self, net)
1186    }
1187}
1188
1189#[cfg(test)]
1190mod test {
1191    use crate::prelude::*;
1192    use maplit::{btreemap, btreeset};
1193    use pretty_assertions::assert_eq;
1194
1195    use super::*;
1196
1197    type Net = Network<SimplePrefix, BasicEventQueue<SimplePrefix>, GlobalOspf>;
1198
1199    #[test]
1200    fn fmt_list() {
1201        let net = Net::default();
1202        assert_eq!(vec!["a", "b", "c"].fmt(&net), "[a, b, c]");
1203        assert_eq!(btreeset!["a", "b", "c"].fmt(&net), "{a, b, c}");
1204        assert_eq!(vec!["a", "b", "c"].fmt_set(&net), "{a, b, c}");
1205        assert_eq!(vec!["a", "b", "c"].fmt_list(&net), "[a, b, c]");
1206        assert_eq!(vec!["a", "b", "c"].fmt_path(&net), "a -> b -> c");
1207    }
1208
1209    #[test]
1210    fn fmt_list_multiline() {
1211        let net = Net::default();
1212        assert_eq!(
1213            vec!["a", "b", "c"].fmt_multiline(&net),
1214            "[\n  a,\n  b,\n  c\n]"
1215        );
1216        assert_eq!(
1217            vec!["a", "b", "c"].fmt_set_multiline(&net, 2),
1218            "{\n    a,\n    b,\n    c\n  }"
1219        );
1220        assert_eq!(
1221            vec!["a", "b", "c"].fmt_list_multiline(&net, 2),
1222            "[\n    a,\n    b,\n    c\n  ]"
1223        );
1224    }
1225
1226    #[test]
1227    fn fmt_nested_list() {
1228        let net = Net::default();
1229        let orig = vec![vec!["a", "b"], vec!["c", "d"]];
1230        let x = orig.as_slice();
1231        assert_eq!(x.fmt(&net), "[[a, b], [c, d]]");
1232        assert_eq!(
1233            x.fmt_multiline(&net),
1234            "[\n  [\n    a,\n    b\n  ],\n  [\n    c,\n    d\n  ]\n]"
1235        );
1236        assert_eq!(
1237            x.fmt_set_multiline(&net, 0),
1238            "{\n  [\n    a,\n    b\n  ],\n  [\n    c,\n    d\n  ]\n}"
1239        );
1240        assert_eq!(
1241            x.fmt_list_multiline(&net, 0),
1242            "[\n  [\n    a,\n    b\n  ],\n  [\n    c,\n    d\n  ]\n]"
1243        );
1244    }
1245
1246    #[test]
1247    fn fmt_map() {
1248        let net = Net::default();
1249        let orig = btreemap! { "a" => 1, "b" => 2};
1250        let x = &orig;
1251        assert_eq!(x.fmt(&net), "{a: 1, b: 2}");
1252        assert_eq!(x.fmt_multiline(&net), "{\n  a: 1,\n  b: 2\n}");
1253    }
1254
1255    #[test]
1256    fn fmt_nested_map() {
1257        let net = Net::default();
1258        let orig = btreemap! { "a" => vec![1, 2, 3], "b" => vec![4, 5, 6]};
1259        let x = &orig;
1260        assert_eq!(x.fmt(&net), "{a: [1, 2, 3], b: [4, 5, 6]}");
1261        assert_eq!(
1262            x.fmt_multiline(&net),
1263            "{\n  a: [\n    1,\n    2,\n    3\n  ],\n  b: [\n    4,\n    5,\n    6\n  ]\n}"
1264        );
1265    }
1266}