use nom::{
branch::alt, bytes::complete::tag, combinator::map, number::complete::float, sequence::tuple,
IResult,
};
pub fn angle_parser(input: &str) -> IResult<&str, f32> {
alt((
map(tuple((float, tag("deg"))), |(val, _)| val.to_radians()),
map(tuple((float, tag("rad"))), |(val, _)| val),
map(float, |val| val),
))(input.trim())
}
pub fn angle_string_parser(input: &str) -> Option<f32> {
angle_parser(input).map(|(_, value)| value).ok()
}
#[cfg(feature = "serde")]
pub fn angle_serde_parser<'de, D>(deserializer: D) -> Result<f32, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;
let s: &str = serde::Deserialize::deserialize(deserializer)?;
angle_string_parser(s).ok_or(D::Error::custom("invalid angle string"))
}
#[cfg(test)]
mod tests {
use super::*;
use test_case::test_case;
#[test_case("180", 180.0 ; "with full integer string")]
#[test_case(".1", 0.1 ; "with prefix period string")]
#[test_case("1.", 1.0 ; "with suffix period string")]
#[test_case("1.1", 1.1 ; "with full float string")]
#[test_case("42.42", 42.42 ; "with two digit full float string")]
#[test_case("0", 0. ; "with zero string")]
#[test_case("0.0", 0. ; "with full zero string")]
#[test_case(".0", 0. ; "with prefix zero string")]
#[test_case("0.", 0. ; "with suffix zero string")]
fn test_angle_string_parser(string: &str, expected: f32) {
assert_eq!(
angle_parser(&format!("{}deg", string)),
Ok(("", expected.to_radians()))
);
assert_eq!(angle_parser(&format!("{}rad", string)), Ok(("", expected)));
assert_eq!(angle_parser(string), Ok(("", expected)));
}
}
#[cfg(all(test, feature = "serde"))]
mod tests_serde {
use serde::Deserialize;
use std::f32::consts::PI;
#[derive(Deserialize)]
pub struct Foo {
#[serde(deserialize_with = "super::angle_serde_parser")]
pub angle: f32,
}
#[test]
fn test_angle_serde_parser() {
let foo: Foo = serde_json::from_str(r#"{"angle": "180deg"}"#).unwrap();
assert_eq!(foo.angle, PI);
}
}