use crate::NHSNumber;
use crate::parse_error::ParseError;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NHSNumberString(pub NHSNumber);
impl From<NHSNumber> for NHSNumberString {
fn from(n: NHSNumber) -> Self {
NHSNumberString(n)
}
}
impl From<NHSNumberString> for NHSNumber {
fn from(w: NHSNumberString) -> Self {
w.0
}
}
impl fmt::Display for NHSNumberString {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl FromStr for NHSNumberString {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
NHSNumber::from_str(s).map(NHSNumberString)
}
}
impl Serialize for NHSNumberString {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_str(&self.0)
}
}
impl<'de> Deserialize<'de> for NHSNumberString {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct Visitor;
impl serde::de::Visitor<'_> for Visitor {
type Value = NHSNumberString;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("an NHS Number string in \"DDDDDDDDDD\" or \"DDD DDD DDDD\" form")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
NHSNumberString::from_str(v).map_err(|_| E::custom("invalid NHS Number string"))
}
}
deserializer.deserialize_str(Visitor)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_serialize_is_canonical_quoted_string() {
let w = NHSNumberString(NHSNumber::new([9, 9, 9, 1, 0, 0, 0, 0, 0, 3]));
let actual = serde_json::to_string(&w).unwrap();
let expect = r#""999 100 0003""#;
assert_eq!(actual, expect);
}
#[test]
fn test_deserialize_canonical_form() {
let actual: NHSNumberString = serde_json::from_str(r#""999 100 0003""#).unwrap();
let expect = NHSNumberString(NHSNumber::new([9, 9, 9, 1, 0, 0, 0, 0, 0, 3]));
assert_eq!(actual, expect);
}
#[test]
fn test_deserialize_tight_form() {
let actual: NHSNumberString = serde_json::from_str(r#""9991000003""#).unwrap();
let expect = NHSNumberString(NHSNumber::new([9, 9, 9, 1, 0, 0, 0, 0, 0, 3]));
assert_eq!(actual, expect);
}
#[test]
fn test_round_trip() {
for digits in [[0; 10], [9; 10], [9, 4, 3, 4, 7, 6, 5, 9, 1, 9]] {
let w = NHSNumberString(NHSNumber::new(digits));
let json = serde_json::to_string(&w).unwrap();
let back: NHSNumberString = serde_json::from_str(&json).unwrap();
assert_eq!(back, w);
}
}
#[test]
fn test_deserialize_rejects_invalid_strings() {
for bad in [
r#""""#,
r#""999-100-0003""#,
r#"" 999 100 0003""#,
r#""999 100 00030""#,
r#""abc def ghij""#,
] {
assert!(
serde_json::from_str::<NHSNumberString>(bad).is_err(),
"{bad} must be rejected"
);
}
}
#[test]
fn test_deserialize_rejects_non_strings() {
assert!(serde_json::from_str::<NHSNumberString>("9991000003").is_err());
assert!(serde_json::from_str::<NHSNumberString>("null").is_err());
assert!(
serde_json::from_str::<NHSNumberString>(r#"{"digits":[9,9,9,1,0,0,0,0,0,3]}"#).is_err()
);
}
#[test]
fn test_deserialize_error_does_not_echo_input() {
let err = serde_json::from_str::<NHSNumberString>(r#""999-100-0003""#).unwrap_err();
let message = err.to_string();
assert!(
!message.contains("999-100-0003") && !message.contains("9991000003"),
"error message must not echo the input: {message}"
);
}
#[test]
fn test_display_and_from_str_delegate() {
let w = NHSNumberString(NHSNumber::new([9, 9, 9, 1, 0, 0, 0, 0, 0, 3]));
assert_eq!(w.to_string(), "999 100 0003");
let parsed = NHSNumberString::from_str("999 100 0003").unwrap();
assert_eq!(parsed, w);
}
#[test]
fn test_from_conversions_round_trip() {
let n = NHSNumber::new([9, 9, 9, 1, 0, 0, 0, 0, 0, 3]);
let w = NHSNumberString::from(n);
let back = NHSNumber::from(w);
assert_eq!(back, n);
}
}