ccsds_ndm/traits.rs
1// SPDX-FileCopyrightText: 2025 Jochim Maene <jochim.maene+github@gmail.com>
2//
3// SPDX-License-Identifier: MPL-2.0
4
5//! Core traits for CCSDS NDM message handling.
6//!
7//! This module defines the primary traits used for parsing and serializing
8//! NDM messages in both KVN and XML formats.
9
10use crate::error::Result;
11use crate::kvn::ser::KvnWriter;
12
13/// Core trait for NDM message types.
14///
15/// All CCSDS message types (OPM, OEM, CDM, etc.) implement this trait,
16/// providing a uniform interface for parsing and serialization.
17///
18/// # Example
19///
20/// ```no_run
21/// use ccsds_ndm::messages::opm::Opm;
22/// use ccsds_ndm::traits::Ndm;
23///
24/// // Parse from KVN
25/// let opm = Opm::from_kvn("CCSDS_OPM_VERS = 3.0\n...").unwrap();
26///
27/// // Serialize to XML
28/// let xml = opm.to_xml().unwrap();
29/// ```
30/// Trait for types that provide semantic validation.
31pub trait Validate {
32 /// Perform semantic validation on the object.
33 ///
34 /// Checks for logical consistency beyond syntactic correctness.
35 /// For example: `START_TIME <= STOP_TIME`, or `MASS >= 0`.
36 ///
37 /// # Returns
38 ///
39 /// `Ok(())` if valid, or a `ValidationError` if invalid.
40 fn validate(&self) -> Result<()> {
41 Ok(())
42 }
43}
44
45/// Core trait for NDM message types.
46///
47/// All CCSDS message types (OPM, OEM, CDM, etc.) implement this trait,
48/// providing a uniform interface for parsing and serialization.
49///
50/// # Example
51///
52/// ```no_run
53/// use ccsds_ndm::messages::opm::Opm;
54/// use ccsds_ndm::traits::Ndm;
55///
56/// // Parse from KVN
57/// let opm = Opm::from_kvn("CCSDS_OPM_VERS = 3.0\n...").unwrap();
58///
59/// // Serialize to XML
60/// let xml = opm.to_xml().unwrap();
61/// ```
62pub trait Ndm: Sized + serde::Serialize + Validate {
63 /// Serialize the message to KVN (Key-Value Notation) format.
64 ///
65 /// # Returns
66 ///
67 /// A string containing the KVN representation of the message.
68 fn to_kvn(&self) -> Result<String>;
69
70 /// Parse a message from KVN (Key-Value Notation) format.
71 ///
72 /// # Arguments
73 ///
74 /// * `kvn` - The KVN content as a string
75 fn from_kvn(kvn: &str) -> Result<Self>;
76
77 /// Serialize the message to XML format.
78 ///
79 /// # Returns
80 ///
81 /// A string containing the XML representation of the message.
82 fn to_xml(&self) -> Result<String>;
83
84 /// Parse a message from XML format.
85 ///
86 /// # Arguments
87 ///
88 /// * `xml` - The XML content as a string
89 fn from_xml(xml: &str) -> Result<Self>;
90}
91
92/// Trait for types that can be parsed from a KVN value string.
93///
94/// This is automatically implemented for any type that implements `FromStr`.
95pub trait FromKvnValue: Sized {
96 /// Parse a value from its KVN string representation.
97 ///
98 /// # Arguments
99 ///
100 /// * `s` - The value string (without key or unit)
101 fn from_kvn_value(s: &str) -> Result<Self>;
102}
103
104/// Trait to check if a value is considered "null" or "empty" in CCSDS context.
105///
106/// This unifies the logic for XML (nil="true" or empty text) and KVN (empty value).
107pub trait CcsdsNullable {
108 /// Returns true if the value represents a null/empty state.
109 fn is_null(&self) -> bool;
110}
111
112impl CcsdsNullable for String {
113 fn is_null(&self) -> bool {
114 self.trim().is_empty() || self.trim() == "n/a"
115 }
116}
117
118impl CcsdsNullable for str {
119 fn is_null(&self) -> bool {
120 self.trim().is_empty() || self.trim() == "n/a"
121 }
122}
123
124impl<T> FromKvnValue for T
125where
126 T: std::str::FromStr,
127 T::Err: Into<crate::error::CcsdsNdmError>,
128{
129 fn from_kvn_value(s: &str) -> Result<Self> {
130 s.parse::<T>().map_err(Into::into)
131 }
132}
133
134/// Trait for types that can be parsed directly from a float and optional unit.
135///
136/// This avoids the overhead of formatting a float to a string and then parsing it back.
137pub trait FromKvnFloat: Sized {
138 /// Create an instance from a float value and optional unit string.
139 ///
140 /// # Arguments
141 ///
142 /// * `value` - The float value
143 /// * `unit` - The optional unit string
144 fn from_kvn_float(value: f64, unit: Option<&str>) -> Result<Self>;
145}
146
147/// Trait for types that can be serialized to KVN format.
148///
149/// Implementors write their KVN representation to the provided [`KvnWriter`].
150pub trait ToKvn {
151 /// Write the KVN representation to the writer.
152 ///
153 /// # Arguments
154 ///
155 /// * `writer` - The KVN writer to output to
156 fn write_kvn(&self, writer: &mut KvnWriter);
157}