use std::convert::TryFrom;
use std::fmt;
use std::net::{IpAddr, Ipv4Addr};
use structform::{
derive_form_input, impl_numeric_input_with_stringops, impl_text_input_with_stringops,
ParseAndFormat, ParseError, StructForm,
};
#[derive(Debug, PartialEq, Eq)]
struct ConnectionDetails {
ip: IpAddr,
port: Port,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Port(u16);
impl Port {
pub const MIN: u16 = 1;
pub const MAX: u16 = std::u16::MAX;
}
impl fmt::Display for Port {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl TryFrom<u16> for Port {
type Error = String;
fn try_from(value: u16) -> Result<Self, Self::Error> {
if value >= Self::MIN && value <= Self::MAX {
Ok(Self(value))
} else {
Err(format!("Expected a port between {} and {}", Self::MIN, Self::MAX).into())
}
}
}
#[derive(Default, Clone, StructForm)]
#[structform(model = "ConnectionDetails", submit_with = "submit_connection_details")]
struct ConnectionDetailsForm {
ip: FormTextInput<IpAddr>,
port: FormNumberInput<Port>,
}
fn submit_connection_details(
form: &mut ConnectionDetailsForm,
) -> Result<ConnectionDetails, ParseError> {
let ip = form.ip.submit();
let port = form.port.submit();
Ok(ConnectionDetails {
ip: ip?,
port: port?,
})
}
derive_form_input! {FormTextInput}
impl_text_input_with_stringops!(FormTextInput, IpAddr);
derive_form_input! {FormNumberInput}
impl_numeric_input_with_stringops!(FormNumberInput, "a port", Port, u16, Port::MIN, Port::MAX);
#[test]
fn if_our_custom_type_is_not_a_number_a_generic_validation_message() {
let mut form = ConnectionDetailsForm::default();
form.set_input(ConnectionDetailsFormField::Port, "Eighty".to_string());
assert_eq!(
form.port.submit(),
Err(ParseError::NumberOutOfRange {
required_type: "a port".to_string(),
min: "1".to_string(),
max: "65535".to_string()
})
);
assert_eq!(
form.port.validation_error().map(|e| e.to_string()),
Some("Expected a port between 1 and 65535.".to_string())
);
}
#[test]
fn if_our_custom_type_is_out_of_range_we_see_our_validation_message() {
let mut form = ConnectionDetailsForm::default();
form.set_input(ConnectionDetailsFormField::Port, "0".to_string());
assert_eq!(
form.port.submit(),
Err(ParseError::FromStrError(
"Expected a port between 1 and 65535".to_string()
))
);
assert_eq!(
form.port.validation_error().map(|e| e.to_string()),
Some("Expected a port between 1 and 65535.".to_string())
);
}
#[test]
fn filling_in_the_form_correctly_submits_successfully() {
let mut form = ConnectionDetailsForm::default();
form.set_input(ConnectionDetailsFormField::Ip, "127.0.0.1".to_string());
form.set_input(ConnectionDetailsFormField::Port, "80".to_string());
assert_eq!(form.port.submit(), Ok(Port(80)));
assert_eq!(
form.submit(),
Ok(ConnectionDetails {
ip: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
port: Port(80)
})
);
}