use regex::Regex; use rocket::http::RawStr; use rocket::request::FromFormValue; use serde::ser::{Serialize, SerializeStruct, Serializer}; use std::convert::TryFrom; /// Represents a street in a given city pub struct Street { name: String, city: String, } impl Street { // This constructor just makes my life a bit easier during testing #[cfg(test)] fn new(name: String, city: String) -> Street { Street { name: name, city: city, } } } impl From<&Street> for String { fn from(street: &Street) -> String { format!("{} ({})", street.name, street.city) } } impl Serialize for Street { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut s = serializer.serialize_struct("Street", 2)?; s.serialize_field("name", &self.name)?; s.serialize_field("city", &self.city)?; s.end() } } impl TryFrom<&str> for Street { type Error = (); fn try_from(value: &str) -> Result { if let Some(index) = value.find('(') { Ok(Street { name: (value[0..index - 1].trim()).to_string(), city: (value[index + 1..value.len() - 1].trim()).to_string(), }) } else { Err(()) } } } impl<'v> FromFormValue<'v> for Street { type Error = &'v RawStr; fn from_form_value(form_value: &'v RawStr) -> Result { // This regex is pretty loose tbh, but not sure how I can make it more // strict right now let re = Regex::new(r"^(.+) \((.+)\)$").unwrap(); match re.captures(&form_value.url_decode_lossy()) { None => Err(form_value), Some(caps) => Ok(Street { name: String::from(caps.get(1).unwrap().as_str()), city: String::from(caps.get(2).unwrap().as_str()), }), } } } #[cfg(test)] mod tests { use super::*; /// Tests the conversion to string #[test] fn test_to_string() { let street = Street::new(String::from("testname"), String::from("city")); assert_eq!(String::from("testname (city)"), String::from(&street)); } }