aboutsummaryrefslogtreecommitdiff
path: root/examples/example-custom-pickable/src/main.rs
blob: a2aa6624bbf334fe0ad87685286a03f6471c19e9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
//! Example Custom Pickable
//!
//! > This example demonstrates how to use the Pickable trait to add parsing for your types
//!
//! Run:
//! ```bash
//! cargo run --manifest-path examples/example-custom-pickable/Cargo.toml --quiet -- connect 127.0.0.1:5012
//! cargo run --manifest-path examples/example-custom-pickable/Cargo.toml --quiet -- connect 127.0.0.1
//! ```
//!
//! Output:
//! ```plaintext
//! Connected to "127.0.0.1:5012"
//! Failed to parse address
//! ```

use mingling::{macros::route, parser::Pickable, prelude::*, Groupped};

// Define types that can be recognized by Mingling
//               ________________________ `Pickable` trait needs to implement Default
//              /                ________ The Groupped derive macro registers an ID for this type
//              |               /           Mingling uses this ID to identify the type
//              vvvvvvv         vvvvvvvv
#[derive(Debug, Default, Clone, Groupped)]
pub struct Address {
    pub ip: [u8; 4],
    pub port: u16,
}

// --------- IMPORTANT ---------
impl Pickable for Address {
    type Output = Address;
    fn pick(args: &mut mingling::parser::Argument, flag: mingling::Flag) -> Option<Self::Output> {
        // Extract the raw string from Argument using the Flag
        let raw: String = args.pick_argument(flag)?.clone();

        // Use TryFrom to parse the address
        Address::try_from(raw).ok()
    }
}
// --------- IMPORTANT ---------

dispatcher!("connect", CMDConnect => EntryConnect);
pack!(ErrorParseAddressFailed = ());

#[chain]
fn handle_connect(prev: EntryConnect) -> Next {
    let connect: Address =
        route! { prev.pick_or_route((), ErrorParseAddressFailed::default().to_chain()).unpack() };
    connect.to_chain()
}

/// Renders the connected address.
#[renderer]
fn render_address(addr: Address) {
    r_println!("Connected to \"{}\"", addr.to_string());
}

/// Renders the error message when address parsing fails.
#[renderer]
fn render_error_parse_address_failed(_: ErrorParseAddressFailed) {
    r_println!("Failed to parse address");
}

gen_program!();

fn main() {
    let mut program = ThisProgram::new();
    program.with_dispatcher(CMDConnect);
    program.exec_and_exit();
}

// Address conversion

impl TryFrom<String> for Address {
    type Error = String;

    fn try_from(raw: String) -> Result<Self, Self::Error> {
        // Expected format: "192.168.1.1:8080"
        let parts: Vec<&str> = raw.split(':').collect();
        if parts.len() != 2 {
            return Err("Invalid format: expected 'IP:PORT'".to_string());
        }

        let ip_str = parts[0];
        let port_str = parts[1];

        // Parse IP address (4 octets separated by dots)
        let ip_parts: Vec<&str> = ip_str.split('.').collect();
        if ip_parts.len() != 4 {
            return Err("Invalid IP address format".to_string());
        }

        let mut ip = [0u8; 4];
        for (i, part) in ip_parts.iter().enumerate() {
            ip[i] = part
                .parse::<u8>()
                .map_err(|_| format!("Invalid IP octet: {part}"))?;
        }

        // Parse port
        let port = port_str
            .parse::<u16>()
            .map_err(|_| format!("Invalid port: {port_str}"))?;

        Ok(Address { ip, port })
    }
}

impl From<Address> for String {
    fn from(addr: Address) -> String {
        format!(
            "{}.{}.{}.{}:{}",
            addr.ip[0], addr.ip[1], addr.ip[2], addr.ip[3], addr.port
        )
    }
}

impl std::fmt::Display for Address {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}.{}.{}.{}:{}",
            self.ip[0], self.ip[1], self.ip[2], self.ip[3], self.port
        )
    }
}