use std::fmt;
#[cfg(feature = "nvi")]
use secrecy::{ExposeSecret, SecretString};
pub const PSEUDO_BSN_SYSTEM: &str = "http://fhir.nl/fhir/NamingSystem/pseudo-bsn";
pub const URA_SYSTEM: &str = "http://fhir.nl/fhir/NamingSystem/ura";
macro_rules! bsn_oid {
() => {
"2.16.840.1.113883.2.4.6.3"
};
}
pub(crate) const BSN_OID: &str = bsn_oid!();
pub const BSN_SYSTEMS: [&str; 3] = [
"http://fhir.nl/fhir/NamingSystem/bsn",
concat!("urn:oid:", bsn_oid!()),
BSN_OID,
];
#[must_use]
pub fn is_bsn_system(namespace: &str) -> bool {
BSN_SYSTEMS.contains(&namespace)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum IdentifierError {
#[error("the identifier value is empty")]
Empty,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Ura(String);
impl Ura {
pub fn new(value: impl Into<String>) -> Result<Self, IdentifierError> {
let value = value.into();
if value.is_empty() {
return Err(IdentifierError::Empty);
}
Ok(Self(value))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for Ura {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
#[cfg(feature = "nvi")]
#[derive(Clone)]
pub struct PseudoBsn(SecretString);
#[cfg(feature = "nvi")]
impl PseudoBsn {
pub fn new(value: SecretString) -> Result<Self, IdentifierError> {
if value.expose_secret().is_empty() {
return Err(IdentifierError::Empty);
}
Ok(Self(value))
}
#[must_use]
pub fn value(&self) -> &SecretString {
&self.0
}
}
#[cfg(feature = "nvi")]
impl fmt::Debug for PseudoBsn {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("PseudoBsn(***)")
}
}
#[cfg(test)]
mod tests {
use super::{IdentifierError, PSEUDO_BSN_SYSTEM, URA_SYSTEM, Ura, is_bsn_system};
#[test]
fn each_bsn_system_is_one_and_the_pseudonym_and_ura_systems_are_not() {
assert!(is_bsn_system("http://fhir.nl/fhir/NamingSystem/bsn"));
assert!(is_bsn_system("urn:oid:2.16.840.1.113883.2.4.6.3"));
assert!(is_bsn_system("2.16.840.1.113883.2.4.6.3"));
assert!(!is_bsn_system(PSEUDO_BSN_SYSTEM));
assert!(!is_bsn_system(URA_SYSTEM));
assert!(!is_bsn_system("urn:oid:2.999.1"));
assert!(!is_bsn_system(""));
}
#[test]
fn an_empty_ura_is_refused_and_any_other_is_kept_as_written() {
assert_eq!(Ura::new(""), Err(IdentifierError::Empty));
let ura = Ura::new("ura-test-0001").expect("a URA");
assert_eq!(ura.as_str(), "ura-test-0001");
assert_eq!(ura.to_string(), "ura-test-0001");
}
#[cfg(feature = "nvi")]
#[test]
fn a_pseudonym_is_never_rendered() {
use secrecy::SecretString;
use super::PseudoBsn;
assert!(PseudoBsn::new(SecretString::from("")).is_err());
let pseudonym = PseudoBsn::new(SecretString::from("SENTINEL-pbsn")).expect("a pseudonym");
assert!(!format!("{pseudonym:?}").contains("SENTINEL"));
}
}