fhir_core/coded.rs
1//! A coded value that is usually a known enum variant but tolerates any code.
2//!
3//! FHIR elements with a `required` binding draw their value from a value set, so
4//! this crate types them as the matching release-specific `codes` enum
5//! ([`r4::codes`](crate::r4::codes), [`r5::codes`](crate::r5::codes)). But real
6//! data occasionally carries a code outside the set (a newer code, a local
7//! extension, or simply invalid data), and a data-exchange library must not fail
8//! to parse it. [`Coded<E>`] wraps the enum with an [`Unknown`](Coded::Unknown)
9//! fallback so every wire value round-trips. See `spec/05-code-systems.md`.
10//!
11//! The wrapper itself is release-independent — it is generic over the enum — so
12//! it is defined once here and re-exported as [`r4::coded`](crate::r4::coded)
13//! and [`r5::coded`](crate::r5::coded).
14//!
15//! ```
16//! use fhir::coded::Coded;
17//!
18//! // Stands in for a generated `codes` enum such as `AdministrativeGender`.
19//! #[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
20//! enum Gender {
21//! #[serde(rename = "female")]
22//! Female,
23//! }
24//!
25//! // A known code parses into the enum variant.
26//! let known: Coded<Gender> =
27//! serde_json::from_value(serde_json::json!("female")).unwrap();
28//! assert_eq!(known, Coded::Known(Gender::Female));
29//!
30//! // An unrecognized code is preserved as-is rather than rejected.
31//! let unknown: Coded<Gender> =
32//! serde_json::from_value(serde_json::json!("robot")).unwrap();
33//! assert_eq!(unknown, Coded::Unknown("robot".to_string()));
34//!
35//! // Both round-trip to their original code string.
36//! assert_eq!(serde_json::to_value(&known).unwrap(), "female");
37//! assert_eq!(serde_json::to_value(&unknown).unwrap(), "robot");
38//! ```
39
40use ::serde::{Deserialize, Serialize};
41
42use crate::validate::{Validate, ValidationIssue};
43
44/// A coded value: a known `codes` enum variant `E`, or any other code string
45/// preserved verbatim.
46///
47/// Deserialization tries `E` first (untagged) and falls back to
48/// [`Unknown`](Self::Unknown); serialization emits the underlying code string in
49/// either case.
50#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
51#[serde(untagged)]
52pub enum Coded<E> {
53 /// A recognized code from the bound value set.
54 Known(E),
55 /// A code outside the value set, preserved for round-tripping.
56 Unknown(String),
57}
58
59impl<E: Default> Default for Coded<E> {
60 fn default() -> Self {
61 Coded::Known(E::default())
62 }
63}
64
65impl<E> Coded<E> {
66 /// The known enum variant, if this value is recognized.
67 pub fn known(&self) -> Option<&E> {
68 match self {
69 Coded::Known(e) => Some(e),
70 Coded::Unknown(_) => None,
71 }
72 }
73
74 /// Whether this value is an unrecognized code.
75 #[must_use]
76 pub fn is_unknown(&self) -> bool {
77 matches!(self, Coded::Unknown(_))
78 }
79}
80
81impl<E: Serialize> Coded<E> {
82 /// The underlying FHIR code string (the enum's canonical code, or the raw
83 /// unknown code).
84 #[must_use]
85 pub fn code(&self) -> String {
86 match self {
87 Coded::Known(e) => ::serde_json::to_value(e)
88 .ok()
89 .and_then(|v| v.as_str().map(str::to_string))
90 .unwrap_or_default(),
91 Coded::Unknown(s) => s.clone(),
92 }
93 }
94}
95
96impl<E> Validate for Coded<E> {
97 // Every `Coded` field has a `required` binding (that is why it was typed as
98 // an enum), so an `Unknown` code is outside the value set (T13).
99 fn validate(&self) -> Vec<ValidationIssue> {
100 match self {
101 Coded::Known(_) => Vec::new(),
102 Coded::Unknown(code) => vec![ValidationIssue::new(
103 "code",
104 format!("code {code:?} is not in the required value set"),
105 )],
106 }
107 }
108}