use core::fmt;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Identifier {
content: String,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
scheme: Option<String>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
scheme_version: Option<String>,
}
impl Identifier {
pub fn new(content: impl Into<String>) -> Self {
Self {
content: content.into(),
scheme: None,
scheme_version: None,
}
}
pub fn schemed(content: impl Into<String>, scheme: impl Into<String>) -> Self {
Self {
content: content.into(),
scheme: Some(scheme.into()),
scheme_version: None,
}
}
pub fn eas(
content: impl Into<String>,
scheme: &str,
) -> Result<Self, crate::codes::guard::CodeError> {
let scheme = crate::codes::guard::eas(scheme)?;
Ok(Self::schemed(content, scheme.as_str()))
}
#[must_use]
pub fn with_scheme_version(mut self, version: impl Into<String>) -> Self {
self.scheme_version = Some(version.into());
self
}
#[must_use]
pub fn content(&self) -> &str {
&self.content
}
#[must_use]
pub fn scheme(&self) -> Option<&str> {
self.scheme.as_deref()
}
#[must_use]
pub fn scheme_version(&self) -> Option<&str> {
self.scheme_version.as_deref()
}
#[must_use]
pub fn is_blank(&self) -> bool {
self.content.trim().is_empty()
}
}
impl fmt::Display for Identifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.scheme {
Some(s) => write!(f, "{s}:{}", self.content),
None => f.write_str(&self.content),
}
}
}
impl From<&str> for Identifier {
fn from(s: &str) -> Self {
Self::new(s)
}
}
impl From<String> for Identifier {
fn from(s: String) -> Self {
Self::new(s)
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DocumentReference(String);
impl DocumentReference {
pub fn new(content: impl Into<String>) -> Self {
Self(content.into())
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn is_blank(&self) -> bool {
self.0.trim().is_empty()
}
}
impl fmt::Display for DocumentReference {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad(&self.0)
}
}
impl From<&str> for DocumentReference {
fn from(s: &str) -> Self {
Self::new(s)
}
}
impl From<String> for DocumentReference {
fn from(s: String) -> Self {
Self::new(s)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn identifier_carries_all_three_components() {
let id = Identifier::schemed("123456789", "0088").with_scheme_version("1.0");
assert_eq!(id.content(), "123456789");
assert_eq!(id.scheme(), Some("0088"));
assert_eq!(id.scheme_version(), Some("1.0"), "the forgotten component");
}
#[test]
fn a_missing_scheme_is_representable_so_it_can_be_reported() {
let bare = Identifier::new("DE123456789");
assert_eq!(bare.scheme(), None);
assert_eq!(bare.to_string(), "DE123456789");
}
#[test]
fn blank_content_is_detectable() {
assert!(Identifier::new(" ").is_blank());
assert!(!Identifier::new("x").is_blank());
assert!(DocumentReference::new("").is_blank());
}
#[test]
fn document_reference_has_no_scheme_by_construction() {
let r = DocumentReference::new("PO-4711");
assert_eq!(r.as_str(), "PO-4711");
assert_eq!(r.to_string(), "PO-4711");
}
#[cfg(feature = "serde")]
#[test]
fn serde_omits_absent_components() {
let bare = serde_json::to_string(&Identifier::new("X")).unwrap();
assert_eq!(bare, r#"{"content":"X"}"#);
let full = serde_json::to_string(&Identifier::schemed("X", "0088")).unwrap();
assert_eq!(full, r#"{"content":"X","scheme":"0088"}"#);
assert_eq!(
serde_json::to_string(&DocumentReference::new("PO-1")).unwrap(),
r#""PO-1""#
);
}
}