+190
src/cli.rs
+190
src/cli.rs
···
1
+
use std::{
2
+
error::Error,
3
+
net::{AddrParseError, SocketAddr},
4
+
path::PathBuf,
5
+
};
6
+
7
+
#[derive(Debug, PartialEq, Eq)]
8
+
pub enum Args {
9
+
Help,
10
+
Host { boats: PathBuf, addr: SocketAddr },
11
+
Join { boats: PathBuf, addr: SocketAddr },
12
+
}
13
+
14
+
#[derive(Debug, PartialEq, Eq)]
15
+
pub enum ArgsParseError {
16
+
NotEnoughArguments,
17
+
InvalidMode(String),
18
+
InvalidAddr(AddrParseError),
19
+
}
20
+
21
+
impl Error for ArgsParseError {
22
+
fn source(&self) -> Option<&(dyn Error + 'static)> {
23
+
match self {
24
+
ArgsParseError::NotEnoughArguments => None,
25
+
ArgsParseError::InvalidMode(_) => None,
26
+
ArgsParseError::InvalidAddr(e) => Some(e),
27
+
}
28
+
}
29
+
}
30
+
31
+
impl std::fmt::Display for ArgsParseError {
32
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33
+
match self {
34
+
ArgsParseError::NotEnoughArguments => write!(f, "not enough arguments"),
35
+
ArgsParseError::InvalidMode(mode) => write!(f, "invalid mode: '{mode}'"),
36
+
ArgsParseError::InvalidAddr(e) => write!(f, "error parsing address: {e}"),
37
+
}
38
+
}
39
+
}
40
+
41
+
impl Args {
42
+
pub fn parse<S: AsRef<str>>(iter: impl IntoIterator<Item = S>) -> Result<Self, ArgsParseError> {
43
+
let mut iter = iter.into_iter();
44
+
let Some(arg) = iter.next() else {
45
+
return Ok(Self::Help);
46
+
};
47
+
48
+
if matches!(arg.as_ref(), "-h" | "--help") {
49
+
return Ok(Self::Help);
50
+
}
51
+
52
+
let boats = PathBuf::from(arg.as_ref());
53
+
let arg = iter.next().ok_or(ArgsParseError::NotEnoughArguments)?;
54
+
55
+
let addr = iter
56
+
.next()
57
+
.ok_or(ArgsParseError::NotEnoughArguments)?
58
+
.as_ref()
59
+
.parse::<SocketAddr>()
60
+
.map_err(ArgsParseError::InvalidAddr)?;
61
+
62
+
match arg.as_ref() {
63
+
"host" => Ok(Self::Host { boats, addr }),
64
+
"join" => Ok(Self::Join { boats, addr }),
65
+
a => Err(ArgsParseError::InvalidMode(a.to_string())),
66
+
}
67
+
}
68
+
}
69
+
70
+
#[cfg(test)]
71
+
mod tests {
72
+
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
73
+
74
+
use super::*;
75
+
76
+
#[test]
77
+
fn parse_nothing() {
78
+
assert_eq!(Args::parse::<String>([]), Ok(Args::Help));
79
+
}
80
+
81
+
#[test]
82
+
fn parse_help() {
83
+
assert_eq!(Args::parse(["-h"]), Ok(Args::Help));
84
+
assert_eq!(Args::parse(["--help"]), Ok(Args::Help));
85
+
}
86
+
87
+
#[test]
88
+
fn parse_host() {
89
+
assert_eq!(
90
+
Args::parse(["./my_boats", "host", "127.0.0.1:8080"]),
91
+
Ok(Args::Host {
92
+
boats: "./my_boats".into(),
93
+
addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8080),
94
+
})
95
+
);
96
+
assert_eq!(
97
+
Args::parse(["./my_boats", "host", "0.0.0.0:8080"]),
98
+
Ok(Args::Host {
99
+
boats: "./my_boats".into(),
100
+
addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 8080),
101
+
})
102
+
);
103
+
assert_eq!(
104
+
Args::parse(["./my_boats", "host", "[::1]:8080"]),
105
+
Ok(Args::Host {
106
+
boats: "./my_boats".into(),
107
+
addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 8080),
108
+
})
109
+
);
110
+
assert_eq!(
111
+
Args::parse(["./my_boats", "host", "[::]:8080"]),
112
+
Ok(Args::Host {
113
+
boats: "./my_boats".into(),
114
+
addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 8080),
115
+
})
116
+
);
117
+
}
118
+
119
+
#[test]
120
+
fn parse_join() {
121
+
assert_eq!(
122
+
Args::parse(["./my_boats", "join", "127.0.0.1:8080"]),
123
+
Ok(Args::Join {
124
+
boats: "./my_boats".into(),
125
+
addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8080),
126
+
})
127
+
);
128
+
assert_eq!(
129
+
Args::parse(["./my_boats", "join", "0.0.0.0:8080"]),
130
+
Ok(Args::Join {
131
+
boats: "./my_boats".into(),
132
+
addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 8080),
133
+
})
134
+
);
135
+
assert_eq!(
136
+
Args::parse(["./my_boats", "join", "[::1]:8080"]),
137
+
Ok(Args::Join {
138
+
boats: "./my_boats".into(),
139
+
addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 8080),
140
+
})
141
+
);
142
+
assert_eq!(
143
+
Args::parse(["./my_boats", "join", "[::]:8080"]),
144
+
Ok(Args::Join {
145
+
boats: "./my_boats".into(),
146
+
addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 8080),
147
+
})
148
+
);
149
+
}
150
+
151
+
#[test]
152
+
fn parse_errors() {
153
+
assert_eq!(
154
+
Args::parse(["abcoabcuobacwa"]),
155
+
Err(ArgsParseError::NotEnoughArguments)
156
+
);
157
+
assert_eq!(
158
+
Args::parse(["./my_boats", "foobar"]),
159
+
Err(ArgsParseError::NotEnoughArguments)
160
+
);
161
+
assert_eq!(
162
+
Args::parse(["./my_boats", "foobar", "127.0.0.1:8080"]),
163
+
Err(ArgsParseError::InvalidMode("foobar".into()))
164
+
);
165
+
assert!(matches!(
166
+
Args::parse(["./my_boats", "host", "blablabla"]),
167
+
Err(ArgsParseError::InvalidAddr(_))
168
+
));
169
+
assert!(matches!(
170
+
Args::parse(["./my_boats", "host", "localhost"]),
171
+
Err(ArgsParseError::InvalidAddr(_))
172
+
));
173
+
assert!(matches!(
174
+
Args::parse(["./my_boats", "host", "0.0.0.0"]),
175
+
Err(ArgsParseError::InvalidAddr(_))
176
+
));
177
+
assert!(matches!(
178
+
Args::parse(["./my_boats", "join", "127.0.0.1"]),
179
+
Err(ArgsParseError::InvalidAddr(_))
180
+
));
181
+
assert!(matches!(
182
+
Args::parse(["./my_boats", "join", "localhost"]),
183
+
Err(ArgsParseError::InvalidAddr(_))
184
+
));
185
+
assert!(matches!(
186
+
Args::parse(["./my_boats", "join", "localhost:8080"]),
187
+
Err(ArgsParseError::InvalidAddr(_))
188
+
));
189
+
}
190
+
}
+16
-1
src/main.rs
+16
-1
src/main.rs
···
1
-
fn main() {
1
+
use std::process::ExitCode;
2
+
3
+
mod cli;
4
+
5
+
fn main() -> ExitCode {
6
+
let args = match cli::Args::parse(std::env::args().skip(1)) {
7
+
Err(e) => {
8
+
eprintln!("Invalid arguments: {e}");
9
+
return ExitCode::FAILURE;
10
+
}
11
+
Ok(args) => args,
12
+
};
13
+
2
14
println!("Hello, world!");
15
+
println!("{args:#?}");
16
+
17
+
ExitCode::SUCCESS
3
18
}