Use clap to allow selecting output format
[hlquery.git] / src / main.rs
1 use std::fmt;
2 use std::fmt::Display;
3 use std::error::Error;
4 use std::net::{SocketAddr, SocketAddrV4, ToSocketAddrs};
5 use clap::Parser;
6 use serde::{Serialize, Serializer};
7 use a2s::A2SClient;
8 use crate::HLQueryError::{IOError,A2SError};
9
10 #[derive(Parser)]
11 #[command(name = "HLQuery")]
12 #[command(author = "MegaBrutal")]
13 #[command(version)]
14 #[command(about = "Query Half-Life servers", long_about = None)]
15 struct Cli {
16 #[arg(short, long)]
17 json: bool,
18
19 addresses: Vec<String>
20 }
21
22
23 #[derive(Debug, Serialize)]
24 struct HLQueryResult {
25 address: SocketAddrV4,
26 info: Result<a2s::info::Info, HLQueryError>,
27 rules: Result<Vec<a2s::rules::Rule>, HLQueryError>,
28 players: Result<Vec<a2s::players::Player>, HLQueryError>
29 }
30
31 impl HLQueryResult {
32 fn new(a2s_client: &A2SClient, server: SocketAddrV4) -> Self {
33 Self {
34 address: server,
35 info: a2s_client.info(server).map_err(From::from),
36 rules: a2s_client.rules(server).map_err(From::from),
37 players: a2s_client.players(server).map_err(From::from)
38 }
39 }
40 }
41
42 #[derive(Debug, Serialize)]
43 struct HLQuery {
44 input: String,
45 result: Result<Vec<HLQueryResult>, HLQueryError>
46 }
47
48 impl HLQuery {
49 fn new<S: Into<String>>(input: S, result: Result<Vec<HLQueryResult>, HLQueryError>) -> Self {
50 let input = input.into();
51 Self { input, result }
52 }
53 }
54
55 #[derive(Debug)]
56 enum HLQueryError {
57 IOError(std::io::Error),
58 A2SError(a2s::errors::Error)
59 }
60
61 impl Display for HLQueryError {
62 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
63 match self {
64 IOError(e) => write!(f, "{:?}", e),
65 A2SError(e) => write!(f, "{:?}", e)
66 }
67 }
68 }
69
70 impl Error for HLQueryError {}
71
72 impl Serialize for HLQueryError {
73 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
74 where
75 S: Serializer,
76 {
77 serializer.serialize_str(&format!("{self}"))
78 }
79 }
80
81 impl From<std::io::Error> for HLQueryError {
82 fn from(e: std::io::Error) -> Self {
83 Self::IOError(e)
84 }
85 }
86
87 impl From<a2s::errors::Error> for HLQueryError {
88 fn from(e: a2s::errors::Error) -> Self {
89 Self::A2SError(e)
90 }
91 }
92
93
94 fn main() {
95 let cli = Cli::parse();
96
97 let client = A2SClient::new().unwrap();
98 let query_results: Vec<HLQuery> = cli.addresses.iter()
99 .map(|arg| {
100 let addresses = arg.to_socket_addrs();
101 (arg, addresses)
102 })
103 .map(|lookup_result| match lookup_result {
104 (input, Ok(iter_addr)) => {
105 (input, Ok(iter_addr.filter_map(|sa| match sa {
106 SocketAddr::V4(sa4) => Some(sa4),
107 _ => None
108 })))
109 },
110 (input, Err(e)) => (input, Err(HLQueryError::IOError(e)))
111 })
112 .map(|(input, address_group)| HLQuery::new(input, address_group.map(
113 |addresses| addresses.map(|addr| HLQueryResult::new(&client, addr)).collect())))
114 .collect();
115
116 if cli.json {
117 println!("{}", serde_json::to_string_pretty(&query_results).unwrap());
118 }
119 else {
120 println!("{:?}", query_results);
121 }
122 }