network/
network.rs

1// SPDX-FileCopyrightText: 2025 Madeline Baggins <declanbaggins@gmail.com>
2//
3// SPDX-License-Identifier: MIT
4
5use maddi_xml::{Content, Element, FromElement, FromValue, Parser, Result};
6use std::{net::Ipv4Addr, path::Path};
7
8/// An example custom type. Here we're adding
9/// support for id addresses.
10struct IP(Ipv4Addr);
11
12impl<'a, 'b> FromValue<'a, 'b> for IP {
13    fn from_value(value: &'b str, position: &'b maddi_xml::Position<'a>) -> Result<'a, Self> {
14        let Ok(ip) = value.parse::<Ipv4Addr>() else {
15            return Err(position.error("expected an ip address".into()));
16        };
17        Ok(IP(ip))
18    }
19}
20
21/// This struct represents the whole configuration file.
22#[derive(Debug)]
23struct Config {
24    servers: Vec<Server>,
25}
26
27impl<'a, 'b> FromElement<'a, 'b> for Config {
28    fn from_element(element: &'b Element<'a>) -> Result<'a, Self> {
29        Ok(Config {
30            servers: element.children("server").collect::<Result<_>>()?,
31        })
32    }
33}
34
35/// This represents a theoretical server that we want to
36/// list information about in our config.
37#[derive(Debug)]
38struct Server {
39    name: String,
40    ip: Ipv4Addr,
41    aliases: Vec<Alias>,
42}
43
44impl<'a, 'b> FromElement<'a, 'b> for Server {
45    fn from_element(element: &'b Element<'a>) -> Result<'a, Self> {
46        Ok(Server {
47            name: element.attribute("name")?,
48            ip: element.attribute::<IP>("ip")?.0,
49            aliases: element.children("alias").collect::<Result<Vec<_>>>()?,
50        })
51    }
52}
53
54/// As alias that the server can be referred to by.
55#[derive(Debug)]
56struct Alias(String);
57
58impl<'a, 'b> FromElement<'a, 'b> for Alias {
59    fn from_element(element: &'b Element<'a>) -> Result<'a, Self> {
60        match element.contents.as_slice() {
61            [] => Err(element.position.error("alias cannot be empty".into())),
62            [Content::Text(alias)] => Ok(Alias(alias.clone())),
63            _ => Err(element
64                .position
65                .error("expected a single alias name".into())),
66        }
67    }
68}
69
70fn main() {
71    // The configuration we're going to parse
72    let config = r#"
73        <config>
74            <server name="localhost" ip="127.0.0.1">
75                <alias>local</alias>
76                <alias>me</alias>
77            </server>
78            <server name="example" ip="10.0.0.101">
79                <alias>example.com</alias>
80            </server>
81        </config>
82    "#;
83    // The parser state machine we'll use to do the work
84    let mut parser = Parser::new(Path::new("example.xml"), config);
85    // Parsing a piece of content from the top of the file.
86    // Content is either a piece of non-xml text or an xml element.
87    match parser.parse::<Option<Result<Content>>>() {
88        Some(Ok(content)) => println!("{content:?}"),
89        Some(Err(e)) => println!("{e}"),
90        None => {
91            let error = parser
92                .position
93                .error("Could not find root config node".into());
94            println!("{error}");
95        }
96    }
97}