use chrono::NaiveDate;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Gender {
Male,
Female,
Other,
Unknown,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Address {
pub line1: Option<String>,
pub line2: Option<String>,
pub city: Option<String>,
pub county: Option<String>,
pub postcode: Option<String>,
pub country: Option<String>,
}
impl Address {
pub fn new() -> Self {
Self {
line1: None,
line2: None,
city: None,
county: None,
postcode: None,
country: None,
}
}
}
impl Default for Address {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Patient {
pub nhs_number: Option<String>,
pub given_name: Option<String>,
pub middle_name: Option<String>,
pub family_name: Option<String>,
pub date_of_birth: Option<NaiveDate>,
pub gender: Option<Gender>,
pub address: Option<Address>,
pub previous_addresses: Vec<Address>,
pub phone: Option<String>,
pub mobile: Option<String>,
pub email: Option<String>,
pub local_id: Option<String>,
}
impl Patient {
pub fn builder() -> PatientBuilder {
PatientBuilder::default()
}
pub fn validate(&self) -> crate::Result<()> {
if self.given_name.is_none() && self.family_name.is_none() && self.nhs_number.is_none() {
return Err(crate::MatchingError::MissingField(
"At least one of: given_name, family_name, or nhs_number is required".to_string(),
));
}
Ok(())
}
}
#[derive(Default)]
pub struct PatientBuilder {
nhs_number: Option<String>,
given_name: Option<String>,
middle_name: Option<String>,
family_name: Option<String>,
date_of_birth: Option<NaiveDate>,
gender: Option<Gender>,
address: Option<Address>,
previous_addresses: Vec<Address>,
phone: Option<String>,
mobile: Option<String>,
email: Option<String>,
local_id: Option<String>,
}
impl PatientBuilder {
pub fn nhs_number<S: Into<String>>(mut self, value: S) -> Self {
self.nhs_number = Some(value.into());
self
}
pub fn given_name<S: Into<String>>(mut self, value: S) -> Self {
self.given_name = Some(value.into());
self
}
pub fn middle_name<S: Into<String>>(mut self, value: S) -> Self {
self.middle_name = Some(value.into());
self
}
pub fn family_name<S: Into<String>>(mut self, value: S) -> Self {
self.family_name = Some(value.into());
self
}
pub fn date_of_birth(mut self, value: NaiveDate) -> Self {
self.date_of_birth = Some(value);
self
}
pub fn gender(mut self, value: Gender) -> Self {
self.gender = Some(value);
self
}
pub fn address(mut self, value: Address) -> Self {
self.address = Some(value);
self
}
pub fn previous_addresses(mut self, value: Vec<Address>) -> Self {
self.previous_addresses = value;
self
}
pub fn phone<S: Into<String>>(mut self, value: S) -> Self {
self.phone = Some(value.into());
self
}
pub fn mobile<S: Into<String>>(mut self, value: S) -> Self {
self.mobile = Some(value.into());
self
}
pub fn email<S: Into<String>>(mut self, value: S) -> Self {
self.email = Some(value.into());
self
}
pub fn local_id<S: Into<String>>(mut self, value: S) -> Self {
self.local_id = Some(value.into());
self
}
pub fn build(self) -> Patient {
Patient {
nhs_number: self.nhs_number,
given_name: self.given_name,
middle_name: self.middle_name,
family_name: self.family_name,
date_of_birth: self.date_of_birth,
gender: self.gender,
address: self.address,
previous_addresses: self.previous_addresses,
phone: self.phone,
mobile: self.mobile,
email: self.email,
local_id: self.local_id,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn address_new_is_all_none() {
let a = Address::new();
assert!(a.line1.is_none());
assert!(a.line2.is_none());
assert!(a.city.is_none());
assert!(a.county.is_none());
assert!(a.postcode.is_none());
assert!(a.country.is_none());
}
#[test]
fn address_default_matches_new() {
assert_eq!(Address::default(), Address::new());
}
#[test]
fn address_round_trips_through_serde() {
let mut a = Address::new();
a.line1 = Some("123 High Street".into());
a.postcode = Some("CF10 1AA".into());
let json = serde_json::to_string(&a).expect("serialise");
let back: Address = serde_json::from_str(&json).expect("deserialise");
assert_eq!(a, back);
}
#[test]
fn patient_builder_starts_empty() {
let p = Patient::builder().build();
assert!(p.nhs_number.is_none());
assert!(p.given_name.is_none());
assert!(p.family_name.is_none());
assert!(p.date_of_birth.is_none());
assert!(p.gender.is_none());
assert!(p.address.is_none());
assert!(p.previous_addresses.is_empty());
assert!(p.phone.is_none());
assert!(p.mobile.is_none());
assert!(p.email.is_none());
assert!(p.local_id.is_none());
}
#[test]
fn patient_builder_accepts_str_and_string() {
let p = Patient::builder()
.given_name("Owen") .family_name(String::from("Jones")) .build();
assert_eq!(p.given_name.as_deref(), Some("Owen"));
assert_eq!(p.family_name.as_deref(), Some("Jones"));
}
#[test]
fn patient_validate_requires_one_of_three_fields() {
assert!(
Patient::builder()
.given_name("a")
.build()
.validate()
.is_ok()
);
assert!(
Patient::builder()
.family_name("a")
.build()
.validate()
.is_ok()
);
assert!(
Patient::builder()
.nhs_number("9434765919")
.build()
.validate()
.is_ok()
);
let err = Patient::builder()
.build()
.validate()
.expect_err("should be missing");
assert!(matches!(err, crate::MatchingError::MissingField(_)));
}
#[test]
fn patient_round_trips_through_serde() {
let p = Patient::builder()
.nhs_number("9434765919")
.given_name("Carys")
.family_name("Pritchard")
.date_of_birth(chrono::NaiveDate::from_ymd_opt(1990, 6, 1).unwrap())
.gender(Gender::Female)
.build();
let json = serde_json::to_string(&p).expect("serialise");
let back: Patient = serde_json::from_str(&json).expect("deserialise");
assert_eq!(p, back);
}
#[test]
fn gender_is_copy_and_eq() {
let g = Gender::Female;
let h = g; assert_eq!(g, h);
assert_ne!(g, Gender::Male);
}
#[test]
fn previous_addresses_setter_replaces_vec() {
let mut a = Address::new();
a.postcode = Some("CF10 1AA".into());
let p = Patient::builder()
.previous_addresses(vec![a.clone()])
.build();
assert_eq!(p.previous_addresses, vec![a]);
}
}