bpi_rs/clientinfo/
params.rs1use std::net::IpAddr;
2
3use crate::{BpiError, BpiResult};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
7pub struct ClientInfoIpParams {
8 ip: Option<IpAddr>,
9}
10
11impl ClientInfoIpParams {
12 pub fn new() -> Self {
14 Self::default()
15 }
16
17 pub fn for_ip(ip: IpAddr) -> Self {
19 Self { ip: Some(ip) }
20 }
21
22 pub fn with_ip_str(mut self, ip: &str) -> BpiResult<Self> {
24 self.ip = Some(parse_ip(ip)?);
25 Ok(self)
26 }
27
28 pub(crate) fn query_pairs(&self) -> Vec<(&'static str, String)> {
29 self.ip
30 .map(|ip| vec![("ip", ip.to_string())])
31 .unwrap_or_default()
32 }
33}
34
35fn parse_ip(ip: &str) -> BpiResult<IpAddr> {
36 ip.trim().parse().map_err(|_| {
37 BpiError::invalid_parameter("ip", "value must be a valid IPv4 or IPv6 address")
38 })
39}
40
41#[cfg(test)]
42mod tests {
43 use super::*;
44
45 #[test]
46 fn client_info_ip_params_serializes_empty_defaults() {
47 let params = ClientInfoIpParams::new();
48
49 assert!(params.query_pairs().is_empty());
50 }
51
52 #[test]
53 fn client_info_ip_params_serializes_ipv4() -> BpiResult<()> {
54 let params = ClientInfoIpParams::new().with_ip_str("8.8.8.8")?;
55
56 assert_eq!(params.query_pairs(), vec![("ip", "8.8.8.8".to_string())]);
57 Ok(())
58 }
59
60 #[test]
61 fn client_info_ip_params_serializes_ipv6() -> BpiResult<()> {
62 let params = ClientInfoIpParams::new().with_ip_str("2001:4860:4860::8888")?;
63
64 assert_eq!(
65 params.query_pairs(),
66 vec![("ip", "2001:4860:4860::8888".to_string())]
67 );
68 Ok(())
69 }
70
71 #[test]
72 fn client_info_ip_params_rejects_invalid_ip() {
73 let err = ClientInfoIpParams::new()
74 .with_ip_str("not-an-ip")
75 .unwrap_err();
76
77 assert!(matches!(
78 err,
79 BpiError::InvalidParameter { field: "ip", .. }
80 ));
81 }
82}