Skip to main content

ccsds_ndm/
types.rs

1// SPDX-FileCopyrightText: 2025 Jochim Maene <jochim.maene+github@gmail.com>
2//
3// SPDX-License-Identifier: MPL-2.0
4
5use crate::error::{CcsdsNdmError, Result};
6use crate::traits::{FromKvnFloat, FromKvnValue};
7use fast_float;
8use serde::{Deserialize, Serialize};
9use std::str::FromStr;
10use thiserror::Error;
11use winnow::ascii::{digit0, digit1};
12use winnow::combinator::{alt, eof, opt, seq, terminated};
13use winnow::token::one_of;
14use winnow::Parser;
15
16// Base Types
17//----------------------------------------------------------------------
18
19/// Represents the `epochType` from the XSD (e.g., "2023-11-13T12:00:00.123Z").
20///
21/// This struct uses a stack-allocated buffer to avoid heap allocations
22/// during parsing of large NDM files.
23#[derive(Debug, PartialEq, Eq, Clone, Copy)]
24pub struct Epoch {
25    bytes: [u8; 64],
26    len: u8,
27}
28
29impl Serialize for Epoch {
30    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
31    where
32        S: serde::Serializer,
33    {
34        serializer.serialize_str(self.as_str())
35    }
36}
37
38impl<'de> Deserialize<'de> for Epoch {
39    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
40    where
41        D: serde::Deserializer<'de>,
42    {
43        let s = String::deserialize(deserializer)?;
44        Epoch::try_from(s).map_err(serde::de::Error::custom)
45    }
46}
47
48#[derive(Error, Debug, PartialEq, Clone)]
49pub enum EpochError {
50    #[error("invalid epoch format: '{0}'")]
51    InvalidFormat(String),
52}
53
54fn is_valid_epoch(s: &str) -> bool {
55    fn parser(input: &mut &str) -> winnow::Result<()> {
56        alt((
57            // Calendar/Ordinal format: YYYY-MM-DDThh:mm:ss.sssZ
58            seq!(
59                opt('-'),
60                digit1.verify(|s: &str| s.len() >= 4),
61                '-',
62                alt((
63                    seq!(
64                        digit1.verify(|s: &str| s.len() == 2),
65                        '-',
66                        digit1.verify(|s: &str| s.len() == 2)
67                    )
68                    .void(),
69                    seq!(digit1.verify(|s: &str| s.len() == 3)).void(),
70                )),
71                'T',
72                digit1.verify(|s: &str| s.len() == 2),
73                ':',
74                digit1.verify(|s: &str| s.len() == 2),
75                ':',
76                digit1.verify(|s: &str| s.len() == 2),
77                opt(seq!('.', digit0).void()),
78                opt(alt((
79                    "Z".void(),
80                    seq!(
81                        one_of(['+', '-']),
82                        digit1.verify(|s: &str| s.len() == 2),
83                        ':',
84                        digit1.verify(|s: &str| s.len() == 2)
85                    )
86                    .void()
87                )))
88            )
89            .void(),
90            // Numeric format: [+-]?\d*(\.\d*)?
91            seq!(
92                opt(one_of(['+', '-'])),
93                digit0,
94                opt(seq!('.', digit0).void())
95            )
96            .void(),
97        ))
98        .parse_next(input)
99    }
100
101    terminated(parser, eof).parse(s).is_ok()
102}
103
104impl Epoch {
105    pub fn new(value: &str) -> std::result::Result<Self, EpochError> {
106        // Fast path for empty or very short strings which are common in some tests
107        // and allowed by the regex [+-]?\d*(\.\d*)?
108        if value.len() > 64 {
109            return Err(EpochError::InvalidFormat(value.to_string()));
110        }
111
112        if value.is_empty() || is_valid_epoch(value) {
113            let mut bytes = [0u8; 64];
114            bytes[..value.len()].copy_from_slice(value.as_bytes());
115            Ok(Epoch {
116                bytes,
117                len: value.len() as u8,
118            })
119        } else {
120            Err(EpochError::InvalidFormat(value.to_string()))
121        }
122    }
123    pub fn as_str(&self) -> &str {
124        // Bytes are validated to be ASCII/UTF-8 during creation.
125        std::str::from_utf8(&self.bytes[..self.len as usize])
126            .expect("Epoch bytes must be valid UTF-8")
127    }
128
129    /// Returns true if the epoch is empty.
130    pub fn is_empty(&self) -> bool {
131        self.len == 0
132    }
133}
134
135impl std::fmt::Display for Epoch {
136    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137        write!(f, "{}", self.as_str())
138    }
139}
140
141impl FromStr for Epoch {
142    type Err = EpochError;
143    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
144        Self::new(s)
145    }
146}
147
148impl TryFrom<String> for Epoch {
149    type Error = EpochError;
150    fn try_from(value: String) -> std::result::Result<Self, Self::Error> {
151        Self::new(&value)
152    }
153}
154
155//----------------------------------------------------------------------
156// Generic Unit/Value Types
157//----------------------------------------------------------------------
158
159/// A trait for types that can be deserialized from a KVN value and optional unit.
160///
161/// This trait provides a standardized way to parse key-value pairs from KVN files,
162/// where a value might have an associated unit in brackets (e.g., `KEY = 123.45 [km]`).
163pub trait FromKvn: Sized {
164    /// Creates an instance from a KVN value string and an optional unit string.
165    ///
166    /// # Arguments
167    /// * `value` - The string representation of the value.
168    /// * `unit` - An optional string representation of the unit.
169    ///
170    /// # Returns
171    /// A `Result` containing the parsed type or a `CcsdsNdmError`.
172    fn from_kvn(value: &str, unit: Option<&str>) -> Result<Self>;
173}
174
175/// A generic container for a value and its associated unit.
176///
177/// This struct is used throughout the library to represent measurements
178/// like position, velocity, etc., which have a numerical value and an
179/// optional unit enum.
180///
181/// # Type Parameters
182/// * `V`: The type of the value (e.g., `f64`, `i32`).
183/// * `U`: The type of the unit enum (e.g., `PositionUnits`).
184#[derive(Serialize, Debug, PartialEq, Clone, Default)]
185pub struct UnitValue<V, U> {
186    #[serde(rename = "$value")]
187    pub value: V,
188    #[serde(rename = "@units", default, skip_serializing_if = "Option::is_none")]
189    pub units: Option<U>,
190}
191
192impl<V, U> FromStr for UnitValue<V, U>
193where
194    UnitValue<V, U>: FromKvn,
195{
196    type Err = crate::error::CcsdsNdmError;
197
198    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
199        Self::from_kvn(s, None)
200    }
201}
202
203impl<'de, V, U> serde::Deserialize<'de> for UnitValue<V, U>
204where
205    V: serde::Deserialize<'de> + std::str::FromStr,
206    V::Err: std::fmt::Display,
207    U: serde::Deserialize<'de>,
208{
209    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
210    where
211        D: serde::Deserializer<'de>,
212    {
213        struct UnitValueVisitor<V, U>(std::marker::PhantomData<(V, U)>);
214
215        impl<'de, V, U> serde::de::Visitor<'de> for UnitValueVisitor<V, U>
216        where
217            V: serde::Deserialize<'de> + std::str::FromStr,
218            V::Err: std::fmt::Display,
219            U: serde::Deserialize<'de>,
220        {
221            type Value = UnitValue<V, U>;
222
223            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
224                formatter.write_str("a primitive value or a map with $value and optionally @units")
225            }
226
227            fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
228            where
229                E: serde::de::Error,
230            {
231                let value = v.parse::<V>().map_err(E::custom)?;
232                Ok(UnitValue { value, units: None })
233            }
234
235            fn visit_map<A>(self, mut map: A) -> std::result::Result<Self::Value, A::Error>
236            where
237                A: serde::de::MapAccess<'de>,
238            {
239                let mut value = None;
240                let mut units = None;
241
242                while let Some(key) = map.next_key::<String>()? {
243                    if key == "$value" || key == "$text" {
244                        if value.is_some() {
245                            return Err(serde::de::Error::duplicate_field("$value"));
246                        }
247                        value = Some(map.next_value()?);
248                    } else if key == "@units" {
249                        if units.is_some() {
250                            return Err(serde::de::Error::duplicate_field("@units"));
251                        }
252                        units = Some(map.next_value()?);
253                    } else {
254                        let _: serde::de::IgnoredAny = map.next_value()?;
255                    }
256                }
257
258                let value = value.ok_or_else(|| serde::de::Error::missing_field("$value"))?;
259                Ok(UnitValue { value, units })
260            }
261        }
262
263        deserializer.deserialize_any(UnitValueVisitor(std::marker::PhantomData))
264    }
265}
266
267impl<V: std::fmt::Display, U> std::fmt::Display for UnitValue<V, U> {
268    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
269        write!(f, "{}", self.value)
270    }
271}
272
273impl<V, U> UnitValue<V, U> {
274    /// Creates a new UnitValue with the given value and optional units.
275    pub fn new(value: V, units: Option<U>) -> Self {
276        Self { value, units }
277    }
278}
279
280impl<V, U> FromKvn for UnitValue<V, U>
281where
282    V: FromStr,
283    CcsdsNdmError: From<V::Err>,
284    U: FromStr,
285    CcsdsNdmError: From<U::Err>,
286{
287    /// Parses a `UnitValue` from a value string and an optional unit string.
288    ///
289    /// The value is parsed using its `FromStr` implementation. If a unit string
290    /// is provided, it is parsed using the unit type's `FromStr` implementation.
291    fn from_kvn(value: &str, unit: Option<&str>) -> Result<Self> {
292        let value = value.parse::<V>()?;
293
294        let units = match unit {
295            Some(u_str) => Some(u_str.parse::<U>().map_err(CcsdsNdmError::from)?),
296            None => None,
297        };
298
299        Ok(UnitValue { value, units })
300    }
301}
302
303impl<U> FromKvnFloat for UnitValue<f64, U>
304where
305    U: FromStr,
306    CcsdsNdmError: From<U::Err>,
307{
308    fn from_kvn_float(value: f64, unit: Option<&str>) -> Result<Self> {
309        let units = match unit {
310            Some(u_str) => Some(u_str.parse::<U>().map_err(CcsdsNdmError::from)?),
311            None => None,
312        };
313        Ok(UnitValue { value, units })
314    }
315}
316
317//----------------------------------------------------------------------
318// Macros to reduce boilerplate for unit enums and wrappers
319//----------------------------------------------------------------------
320
321/// Defines a unit enum with serde renames, plus Display, Default, and FromStr,
322/// and a `UnitValue<f64, UnitEnum>` type alias with the provided name.
323///
324/// Usage:
325/// define_unit_type!(
326///     Position, PositionUnits, Km, { Km => "km" }
327/// );
328macro_rules! define_unit_type {
329    ($type_alias:ident, $unit_enum:ident, $default_variant:ident, { $($variant:ident => $str_rep:expr),+ $(,)? }) => {
330        #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
331        pub enum $unit_enum {
332            $(#[serde(rename = $str_rep)] $variant),+
333        }
334
335        impl Default for $unit_enum {
336            fn default() -> Self { Self::$default_variant }
337        }
338
339        impl std::fmt::Display for $unit_enum {
340            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
341                match self {
342                    $(Self::$variant => write!(f, $str_rep)),+
343                }
344            }
345        }
346
347        impl std::str::FromStr for $unit_enum {
348            type Err = crate::error::EnumParseError;
349            fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
350                match s {
351                    $($str_rep => Ok(Self::$variant)),+,
352                    _ => Err(crate::error::EnumParseError {
353                        field: "unit",
354                        value: s.to_string(),
355                        expected: stringify!($($str_rep),+),
356                    })
357                }
358            }
359        }
360
361        pub type $type_alias = UnitValue<f64, $unit_enum>;
362    };
363}
364
365/// Defines a "required" wrapper struct that always carries units (no Option)
366/// and constructs with the provided default unit variant.
367///
368/// Example:
369/// define_required_type!(PositionRequired, PositionUnits, Km);
370macro_rules! define_required_type {
371    ($name:ident, $unit_enum:ident, $default_unit:ident) => {
372        #[derive(Serialize, Debug, PartialEq, Clone, Default)]
373        pub struct $name {
374            #[serde(rename = "$value")]
375            pub value: f64,
376            #[serde(rename = "@units")]
377            pub units: $unit_enum,
378        }
379
380        impl<'de> serde::Deserialize<'de> for $name {
381            fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
382            where
383                D: serde::Deserializer<'de>,
384            {
385                struct Visitor;
386                impl<'de> serde::de::Visitor<'de> for Visitor {
387                    type Value = $name;
388                    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
389                        formatter.write_str("a float value or map with $value and @units")
390                    }
391
392                    fn visit_f64<E>(self, v: f64) -> std::result::Result<Self::Value, E>
393                    where
394                        E: serde::de::Error,
395                    {
396                        Ok($name::new(v))
397                    }
398
399                    fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
400                    where
401                        E: serde::de::Error,
402                    {
403                        let val = v.parse::<f64>().map_err(E::custom)?;
404                        Ok($name::new(val))
405                    }
406
407                    fn visit_map<A>(self, mut map: A) -> std::result::Result<Self::Value, A::Error>
408                    where
409                        A: serde::de::MapAccess<'de>,
410                    {
411                        let mut value = None;
412                        let mut units = None;
413                        while let Some(key) = map.next_key::<String>()? {
414                            if key == "$value" || key == "$text" {
415                                value = Some(map.next_value::<f64>()?);
416                            } else if key == "@units" {
417                                units = Some(map.next_value::<$unit_enum>()?);
418                            } else {
419                                let _ = map.next_value::<serde::de::IgnoredAny>()?;
420                            }
421                        }
422                        let value =
423                            value.ok_or_else(|| serde::de::Error::missing_field("$value"))?;
424                        let mut s = $name::new(value);
425                        if let Some(u) = units {
426                            s.units = u;
427                        }
428                        Ok(s)
429                    }
430                }
431                deserializer.deserialize_any(Visitor)
432            }
433        }
434        impl $name {
435            pub fn new(value: f64) -> Self {
436                Self {
437                    value,
438                    units: $unit_enum::$default_unit,
439                }
440            }
441            pub fn to_unit_value(&self) -> UnitValue<f64, $unit_enum> {
442                UnitValue {
443                    value: self.value,
444                    units: Some(self.units.clone()),
445                }
446            }
447        }
448        impl std::fmt::Display for $name {
449            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
450                write!(f, "{}", self.value)
451            }
452        }
453        impl FromKvnFloat for $name {
454            fn from_kvn_float(value: f64, _unit: Option<&str>) -> Result<Self> {
455                Ok(Self::new(value))
456            }
457        }
458    };
459}
460
461// Local macro to define only unit enums with serde/Default/Display/FromStr
462macro_rules! define_unit_enum {
463    ($unit_enum:ident, $default_variant:ident, { $($variant:ident => $str_rep:expr),+ $(,)? }) => {
464        #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
465        pub enum $unit_enum { $(#[serde(rename = $str_rep)] $variant),+ }
466        impl Default for $unit_enum { fn default() -> Self { Self::$default_variant } }
467        impl std::fmt::Display for $unit_enum {
468            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
469                match self { $(Self::$variant => write!(f, $str_rep)),+ }
470            }
471        }
472        impl std::str::FromStr for $unit_enum {
473            type Err = crate::error::EnumParseError;
474            fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
475                match s { $($str_rep => Ok(Self::$variant)),+, _ => Err(crate::error::EnumParseError {
476                    field: "unit",
477                    value: s.to_string(),
478                    expected: stringify!($($str_rep),+),
479                }) }
480            }
481        }
482    };
483}
484
485/// Defines a unit enum and a required wrapper struct in one go.
486macro_rules! define_required_unit_type {
487    ($name:ident, $unit_enum:ident, $default_variant:ident, { $($variant:ident => $str_rep:expr),+ $(,)? }) => {
488        define_unit_enum!($unit_enum, $default_variant, { $($variant => $str_rep),+ });
489        define_required_type!($name, $unit_enum, $default_variant);
490    };
491}
492
493//----------------------------------------------------------------------
494// Unit/Value Types
495//----------------------------------------------------------------------
496
497// Unit for Acceleration: `accUnits` and alias `Acc`
498define_unit_type!(
499    Acc,
500    AccUnits,
501    KmPerS2,
502    { KmPerS2 => "km/s**2" }
503);
504
505// --- Position ---
506define_unit_type!(
507    Position,
508    PositionUnits,
509    Km,
510    { Km => "km" }
511);
512
513define_required_type!(PositionRequired, PositionUnits, Km);
514// --- Velocity ---
515
516define_unit_type!(
517    Velocity,
518    VelocityUnits,
519    KmPerS,
520    { KmPerS => "km/s" }
521);
522
523define_required_type!(VelocityRequired, VelocityUnits, KmPerS);
524// Type alias for Distance used in Keplerian elements
525pub type Distance = Position;
526
527// --- Angle ---
528
529define_unit_enum!(AngleUnits, Deg, { Deg => "deg" });
530
531#[derive(Serialize, Debug, PartialEq, Clone)]
532pub struct Angle {
533    #[serde(rename = "$value")]
534    pub value: f64,
535    #[serde(rename = "@units", default, skip_serializing_if = "Option::is_none")]
536    pub units: Option<AngleUnits>,
537}
538
539impl std::str::FromStr for Angle {
540    type Err = crate::error::CcsdsNdmError;
541    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
542        let val = s.parse::<f64>()?;
543        Self::new(val, None)
544    }
545}
546
547impl<'de> serde::Deserialize<'de> for Angle {
548    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
549    where
550        D: serde::Deserializer<'de>,
551    {
552        struct AngleVisitor;
553
554        impl<'de> serde::de::Visitor<'de> for AngleVisitor {
555            type Value = Angle;
556
557            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
558                formatter.write_str("a primitive value or a map with $value and optionally @units")
559            }
560
561            fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
562            where
563                E: serde::de::Error,
564            {
565                v.parse::<Angle>().map_err(E::custom)
566            }
567
568            fn visit_map<A>(self, mut map: A) -> std::result::Result<Self::Value, A::Error>
569            where
570                A: serde::de::MapAccess<'de>,
571            {
572                let mut value = None;
573                let mut units = None;
574
575                while let Some(key) = map.next_key::<String>()? {
576                    if key == "$value" || key == "$text" {
577                        value = Some(map.next_value()?);
578                    } else if key == "@units" {
579                        units = Some(map.next_value()?);
580                    } else {
581                        let _: serde::de::IgnoredAny = map.next_value()?;
582                    }
583                }
584
585                let value = value.ok_or_else(|| serde::de::Error::missing_field("$value"))?;
586                Angle::new(value, units).map_err(serde::de::Error::custom)
587            }
588        }
589
590        deserializer.deserialize_any(AngleVisitor)
591    }
592}
593
594impl Angle {
595    /// XSD angleRange: -360.0 <= value < 360.0
596    pub fn new(value: f64, units: Option<AngleUnits>) -> Result<Self> {
597        if !(-360.0..360.0).contains(&value) {
598            return Err(crate::error::ValidationError::OutOfRange {
599                name: "Angle".into(),
600                value: value.to_string(),
601                expected: "[-360, 360)".into(),
602                line: None,
603            }
604            .into());
605        }
606        Ok(Self { value, units })
607    }
608    pub fn to_unit_value(&self) -> UnitValue<f64, AngleUnits> {
609        UnitValue {
610            value: self.value,
611            units: self.units.clone(),
612        }
613    }
614}
615impl FromKvnFloat for Angle {
616    fn from_kvn_float(value: f64, unit: Option<&str>) -> Result<Self> {
617        let uv = UnitValue::<f64, AngleUnits>::from_kvn_float(value, unit)?;
618        Self::new(uv.value, uv.units)
619    }
620}
621// --- Angle Rate ---
622
623define_unit_enum!(AngleRateUnits, DegPerS, { DegPerS => "deg/s" });
624
625pub type AngleRate = UnitValue<f64, AngleRateUnits>;
626
627// --- Angular Momentum ---
628define_unit_type!(AngMomentum, AngMomentumUnits, NmS, { NmS => "N*m*s" });
629
630// --- Day Interval ---
631
632define_unit_enum!(DayIntervalUnits, D, { D => "d" });
633
634#[derive(Serialize, Debug, PartialEq, Clone)]
635pub struct DayInterval {
636    #[serde(rename = "$value")]
637    pub value: f64,
638    #[serde(rename = "@units", default, skip_serializing_if = "Option::is_none")]
639    pub units: Option<DayIntervalUnits>,
640}
641
642impl std::str::FromStr for DayInterval {
643    type Err = crate::error::CcsdsNdmError;
644    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
645        let val = s.parse::<f64>()?;
646        Self::new(val, None)
647    }
648}
649
650impl<'de> serde::Deserialize<'de> for DayInterval {
651    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
652    where
653        D: serde::Deserializer<'de>,
654    {
655        struct DayIntervalVisitor;
656
657        impl<'de> serde::de::Visitor<'de> for DayIntervalVisitor {
658            type Value = DayInterval;
659
660            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
661                formatter.write_str("a primitive value or a map with $value and optionally @units")
662            }
663
664            fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
665            where
666                E: serde::de::Error,
667            {
668                v.parse::<DayInterval>().map_err(E::custom)
669            }
670
671            fn visit_map<A>(self, mut map: A) -> std::result::Result<Self::Value, A::Error>
672            where
673                A: serde::de::MapAccess<'de>,
674            {
675                let mut value = None;
676                let mut units = None;
677
678                while let Some(key) = map.next_key::<String>()? {
679                    if key == "$value" || key == "$text" {
680                        value = Some(map.next_value::<f64>()?);
681                    } else if key == "@units" {
682                        units = Some(map.next_value::<DayIntervalUnits>()?);
683                    } else {
684                        let _: serde::de::IgnoredAny = map.next_value()?;
685                    }
686                }
687
688                let value = value.ok_or_else(|| serde::de::Error::missing_field("$value"))?;
689                DayInterval::new(value, units).map_err(serde::de::Error::custom)
690            }
691        }
692
693        deserializer.deserialize_any(DayIntervalVisitor)
694    }
695}
696
697impl DayInterval {
698    /// dayIntervalTypeUO: nonNegativeDouble
699    pub fn new(value: f64, units: Option<DayIntervalUnits>) -> Result<Self> {
700        if value < 0.0 {
701            return Err(crate::error::ValidationError::OutOfRange {
702                name: "DayInterval".into(),
703                value: value.to_string(),
704                expected: ">= 0".into(),
705                line: None,
706            }
707            .into());
708        }
709        Ok(Self { value, units })
710    }
711    pub fn to_unit_value(&self) -> UnitValue<f64, DayIntervalUnits> {
712        UnitValue {
713            value: self.value,
714            units: self.units.clone(),
715        }
716    }
717}
718impl FromKvnFloat for DayInterval {
719    fn from_kvn_float(value: f64, unit: Option<&str>) -> Result<Self> {
720        let uv = UnitValue::<f64, DayIntervalUnits>::from_kvn_float(value, unit)?;
721        Self::new(uv.value, uv.units)
722    }
723}
724impl std::fmt::Display for DayInterval {
725    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
726        write!(f, "{}", self.value)
727    }
728}
729#[derive(Serialize, Debug, PartialEq, Clone)]
730pub struct Percentage {
731    #[serde(rename = "$value")]
732    pub value: f64,
733    #[serde(rename = "@units", default, skip_serializing_if = "Option::is_none")]
734    pub units: Option<PercentageUnits>,
735}
736
737impl<'de> serde::Deserialize<'de> for Percentage {
738    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
739    where
740        D: serde::Deserializer<'de>,
741    {
742        struct Visitor;
743        impl<'de> serde::de::Visitor<'de> for Visitor {
744            type Value = Percentage;
745            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
746                formatter.write_str("a float value or map with $value and optionally @units")
747            }
748
749            fn visit_f64<E>(self, v: f64) -> std::result::Result<Self::Value, E>
750            where
751                E: serde::de::Error,
752            {
753                Percentage::new(v, None).map_err(E::custom)
754            }
755
756            fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
757            where
758                E: serde::de::Error,
759            {
760                let val = v.parse::<f64>().map_err(E::custom)?;
761                Percentage::new(val, None).map_err(E::custom)
762            }
763
764            fn visit_map<A>(self, mut map: A) -> std::result::Result<Self::Value, A::Error>
765            where
766                A: serde::de::MapAccess<'de>,
767            {
768                let mut value = None;
769                let mut units = None;
770                while let Some(key) = map.next_key::<String>()? {
771                    if key == "$value" || key == "$text" {
772                        value = Some(map.next_value::<f64>()?);
773                    } else if key == "@units" {
774                        units = Some(map.next_value::<PercentageUnits>()?);
775                    } else {
776                        let _ = map.next_value::<serde::de::IgnoredAny>()?;
777                    }
778                }
779                let value = value.ok_or_else(|| serde::de::Error::missing_field("$value"))?;
780                Percentage::new(value, units).map_err(serde::de::Error::custom)
781            }
782        }
783        deserializer.deserialize_any(Visitor)
784    }
785}
786
787impl Percentage {
788    pub fn new(value: f64, units: Option<PercentageUnits>) -> Result<Self> {
789        if !(0.0..=100.0).contains(&value) {
790            return Err(crate::error::ValidationError::OutOfRange {
791                name: "Percentage".into(),
792                value: value.to_string(),
793                expected: "[0, 100]".into(),
794                line: None,
795            }
796            .into());
797        }
798        Ok(Self { value, units })
799    }
800    pub fn to_unit_value(&self) -> UnitValue<f64, PercentageUnits> {
801        UnitValue {
802            value: self.value,
803            units: self.units.clone(),
804        }
805    }
806}
807impl FromKvnFloat for Percentage {
808    fn from_kvn_float(value: f64, unit: Option<&str>) -> Result<Self> {
809        let uv = UnitValue::<f64, PercentageUnits>::from_kvn_float(value, unit)?;
810        Self::new(uv.value, uv.units)
811    }
812}
813impl std::fmt::Display for Percentage {
814    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
815        write!(f, "{}", self.value)
816    }
817}
818#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
819pub struct DayIntervalRequired {
820    #[serde(rename = "$value")]
821    pub value: f64,
822    #[serde(rename = "@units")]
823    pub units: DayIntervalUnits,
824}
825impl DayIntervalRequired {
826    /// dayIntervalTypeUR: positiveDouble (>0, units required)
827    pub fn new(value: f64) -> Result<Self> {
828        if value <= 0.0 {
829            return Err(crate::error::ValidationError::OutOfRange {
830                name: "DayIntervalRequired".into(),
831                value: value.to_string(),
832                expected: "> 0".into(),
833                line: None,
834            }
835            .into());
836        }
837        Ok(Self {
838            value,
839            units: DayIntervalUnits::D,
840        })
841    }
842    pub fn to_unit_value(&self) -> UnitValue<f64, DayIntervalUnits> {
843        UnitValue {
844            value: self.value,
845            units: Some(self.units.clone()),
846        }
847    }
848}
849impl FromKvnFloat for DayIntervalRequired {
850    fn from_kvn_float(value: f64, _unit: Option<&str>) -> Result<Self> {
851        Self::new(value)
852    }
853}
854impl std::fmt::Display for DayIntervalRequired {
855    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
856        write!(f, "{}", self.value)
857    }
858}
859// --- Frequency ---
860
861define_unit_enum!(FrequencyUnits, Hz, { Hz => "Hz" });
862
863#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
864pub struct Frequency {
865    #[serde(rename = "$value")]
866    pub value: f64,
867    #[serde(rename = "@units", default, skip_serializing_if = "Option::is_none")]
868    pub units: Option<FrequencyUnits>,
869}
870impl Frequency {
871    /// frequencyType: positiveDouble (>0)
872    pub fn new(value: f64, units: Option<FrequencyUnits>) -> Result<Self> {
873        if value <= 0.0 {
874            return Err(crate::error::ValidationError::OutOfRange {
875                name: "Frequency".into(),
876                value: value.to_string(),
877                expected: "> 0".into(),
878                line: None,
879            }
880            .into());
881        }
882        Ok(Self { value, units })
883    }
884    pub fn to_unit_value(&self) -> UnitValue<f64, FrequencyUnits> {
885        UnitValue {
886            value: self.value,
887            units: self.units.clone(),
888        }
889    }
890}
891impl FromKvnFloat for Frequency {
892    fn from_kvn_float(value: f64, unit: Option<&str>) -> Result<Self> {
893        let uv = UnitValue::<f64, FrequencyUnits>::from_kvn_float(value, unit)?;
894        Self::new(uv.value, uv.units)
895    }
896}
897// --- Covariance Types ---
898
899define_unit_type!(PositionCovariance, PositionCovarianceUnits, Km2, { Km2 => "km**2" });
900
901define_unit_type!(VelocityCovariance, VelocityCovarianceUnits, Km2PerS2, { Km2PerS2 => "km**2/s**2" });
902
903define_unit_type!(PositionVelocityCovariance, PositionVelocityCovarianceUnits, Km2PerS, { Km2PerS => "km**2/s" });
904
905// --- GM ---
906
907define_unit_enum!(GmUnits, Km3PerS2, { Km3PerS2 => "km**3/s**2", KM3PerS2 => "KM**3/S**2" });
908
909#[derive(Serialize, Debug, PartialEq, Clone)]
910pub struct Gm {
911    #[serde(rename = "$value")]
912    pub value: f64,
913    #[serde(rename = "@units", default, skip_serializing_if = "Option::is_none")]
914    pub units: Option<GmUnits>,
915}
916
917impl std::str::FromStr for Gm {
918    type Err = crate::error::CcsdsNdmError;
919    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
920        let val = s.parse::<f64>()?;
921        Self::new(val, None)
922    }
923}
924
925impl<'de> serde::Deserialize<'de> for Gm {
926    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
927    where
928        D: serde::Deserializer<'de>,
929    {
930        struct GmVisitor;
931
932        impl<'de> serde::de::Visitor<'de> for GmVisitor {
933            type Value = Gm;
934
935            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
936                formatter.write_str("a primitive value or a map with $value and optionally @units")
937            }
938
939            fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
940            where
941                E: serde::de::Error,
942            {
943                v.parse::<Gm>().map_err(E::custom)
944            }
945
946            fn visit_map<A>(self, mut map: A) -> std::result::Result<Self::Value, A::Error>
947            where
948                A: serde::de::MapAccess<'de>,
949            {
950                let mut value = None;
951                let mut units = None;
952
953                while let Some(key) = map.next_key::<String>()? {
954                    if key == "$value" || key == "$text" {
955                        value = Some(map.next_value::<f64>()?);
956                    } else if key == "@units" {
957                        units = Some(map.next_value::<GmUnits>()?);
958                    } else {
959                        let _: serde::de::IgnoredAny = map.next_value()?;
960                    }
961                }
962
963                let value = value.ok_or_else(|| serde::de::Error::missing_field("$value"))?;
964                Gm::new(value, units).map_err(serde::de::Error::custom)
965            }
966        }
967
968        deserializer.deserialize_any(GmVisitor)
969    }
970}
971
972impl Gm {
973    /// gmType: positiveDouble (>0)
974    pub fn new(value: f64, units: Option<GmUnits>) -> Result<Self> {
975        if value <= 0.0 {
976            return Err(crate::error::ValidationError::OutOfRange {
977                name: "GM".into(),
978                value: value.to_string(),
979                expected: "> 0".into(),
980                line: None,
981            }
982            .into());
983        }
984        Ok(Self { value, units })
985    }
986    pub fn to_unit_value(&self) -> UnitValue<f64, GmUnits> {
987        UnitValue {
988            value: self.value,
989            units: self.units.clone(),
990        }
991    }
992}
993impl FromKvnFloat for Gm {
994    fn from_kvn_float(value: f64, unit: Option<&str>) -> Result<Self> {
995        let uv = UnitValue::<f64, GmUnits>::from_kvn_float(value, unit)?;
996        Self::new(uv.value, uv.units)
997    }
998}
999
1000// --- Length ---
1001
1002define_unit_type!(
1003    Length,
1004    LengthUnits,
1005    M,
1006    { M => "m" }
1007);
1008
1009#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
1010pub struct AltitudeRequired {
1011    #[serde(rename = "$value")]
1012    pub value: f64,
1013    #[serde(rename = "@units")]
1014    pub units: LengthUnits,
1015}
1016impl AltitudeRequired {
1017    /// altRange: -430.5 ..= 8848
1018    pub fn new(value: f64) -> Result<Self> {
1019        if !(-430.5..=8848.0).contains(&value) {
1020            return Err(crate::error::ValidationError::OutOfRange {
1021                name: "Altitude".into(),
1022                value: value.to_string(),
1023                expected: "[-430.5, 8848]".into(),
1024                line: None,
1025            }
1026            .into());
1027        }
1028        Ok(Self {
1029            value,
1030            units: LengthUnits::M,
1031        })
1032    }
1033    pub fn to_unit_value(&self) -> UnitValue<f64, LengthUnits> {
1034        UnitValue {
1035            value: self.value,
1036            units: Some(self.units.clone()),
1037        }
1038    }
1039}
1040impl FromKvnFloat for AltitudeRequired {
1041    fn from_kvn_float(value: f64, _unit: Option<&str>) -> Result<Self> {
1042        Self::new(value)
1043    }
1044}
1045impl std::fmt::Display for AltitudeRequired {
1046    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1047        write!(f, "{}", self.value)
1048    }
1049}
1050
1051// --- Power/Mass Ratio ---
1052
1053define_required_unit_type!(Wkg, WkgUnits, WPerKg, { WPerKg => "W/kg" });
1054
1055// --- Mass ---
1056
1057define_unit_enum!(MassUnits, Kg, { Kg => "kg" });
1058
1059#[derive(Serialize, Debug, PartialEq, Clone)]
1060pub struct Mass {
1061    #[serde(rename = "$value")]
1062    pub value: f64,
1063    #[serde(rename = "@units", default, skip_serializing_if = "Option::is_none")]
1064    pub units: Option<MassUnits>,
1065}
1066
1067impl std::str::FromStr for Mass {
1068    type Err = crate::error::CcsdsNdmError;
1069    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
1070        let val = s.parse::<f64>()?;
1071        Self::new(val, None)
1072    }
1073}
1074
1075impl<'de> serde::Deserialize<'de> for Mass {
1076    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
1077    where
1078        D: serde::Deserializer<'de>,
1079    {
1080        struct MassVisitor;
1081
1082        impl<'de> serde::de::Visitor<'de> for MassVisitor {
1083            type Value = Mass;
1084
1085            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
1086                formatter.write_str("a primitive value or a map with $value and optionally @units")
1087            }
1088
1089            fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
1090            where
1091                E: serde::de::Error,
1092            {
1093                v.parse::<Mass>().map_err(E::custom)
1094            }
1095
1096            fn visit_map<A>(self, mut map: A) -> std::result::Result<Self::Value, A::Error>
1097            where
1098                A: serde::de::MapAccess<'de>,
1099            {
1100                let mut value = None;
1101                let mut units = None;
1102
1103                while let Some(key) = map.next_key::<String>()? {
1104                    if key == "$value" || key == "$text" {
1105                        value = Some(map.next_value()?);
1106                    } else if key == "@units" {
1107                        units = Some(map.next_value()?);
1108                    } else {
1109                        let _: serde::de::IgnoredAny = map.next_value()?;
1110                    }
1111                }
1112
1113                let value = value.ok_or_else(|| serde::de::Error::missing_field("$value"))?;
1114                Mass::new(value, units).map_err(serde::de::Error::custom)
1115            }
1116        }
1117
1118        deserializer.deserialize_any(MassVisitor)
1119    }
1120}
1121
1122impl Mass {
1123    /// XSD massType: nonNegativeDouble
1124    pub fn new(value: f64, units: Option<MassUnits>) -> Result<Self> {
1125        if value < 0.0 {
1126            return Err(crate::error::ValidationError::OutOfRange {
1127                name: "Mass".into(),
1128                value: value.to_string(),
1129                expected: ">= 0".into(),
1130                line: None,
1131            }
1132            .into());
1133        }
1134        Ok(Self { value, units })
1135    }
1136    pub fn to_unit_value(&self) -> UnitValue<f64, MassUnits> {
1137        UnitValue {
1138            value: self.value,
1139            units: self.units.clone(),
1140        }
1141    }
1142}
1143
1144impl FromKvnFloat for Mass {
1145    fn from_kvn_float(value: f64, unit: Option<&str>) -> Result<Self> {
1146        let uv = UnitValue::<f64, MassUnits>::from_kvn_float(value, unit)?;
1147        Self::new(uv.value, uv.units)
1148    }
1149}
1150impl std::fmt::Display for Mass {
1151    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1152        write!(f, "{}", self.value)
1153    }
1154}
1155
1156define_unit_enum!(AreaUnits, M2, { M2 => "m**2" });
1157
1158#[derive(Serialize, Debug, PartialEq, Clone)]
1159pub struct Area {
1160    #[serde(rename = "$value")]
1161    pub value: f64,
1162    #[serde(rename = "@units", default, skip_serializing_if = "Option::is_none")]
1163    pub units: Option<AreaUnits>,
1164}
1165
1166impl std::str::FromStr for Area {
1167    type Err = crate::error::CcsdsNdmError;
1168    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
1169        let val = s.parse::<f64>()?;
1170        Self::new(val, None)
1171    }
1172}
1173
1174impl<'de> serde::Deserialize<'de> for Area {
1175    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
1176    where
1177        D: serde::Deserializer<'de>,
1178    {
1179        struct AreaVisitor;
1180
1181        impl<'de> serde::de::Visitor<'de> for AreaVisitor {
1182            type Value = Area;
1183
1184            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
1185                formatter.write_str("a primitive value or a map with $value and optionally @units")
1186            }
1187
1188            fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
1189            where
1190                E: serde::de::Error,
1191            {
1192                v.parse::<Area>().map_err(E::custom)
1193            }
1194
1195            fn visit_map<A>(self, mut map: A) -> std::result::Result<Self::Value, A::Error>
1196            where
1197                A: serde::de::MapAccess<'de>,
1198            {
1199                let mut value = None;
1200                let mut units = None;
1201
1202                while let Some(key) = map.next_key::<String>()? {
1203                    if key == "$value" || key == "$text" {
1204                        value = Some(map.next_value()?);
1205                    } else if key == "@units" {
1206                        units = Some(map.next_value()?);
1207                    } else {
1208                        let _: serde::de::IgnoredAny = map.next_value()?;
1209                    }
1210                }
1211
1212                let value = value.ok_or_else(|| serde::de::Error::missing_field("$value"))?;
1213                Area::new(value, units).map_err(serde::de::Error::custom)
1214            }
1215        }
1216
1217        deserializer.deserialize_any(AreaVisitor)
1218    }
1219}
1220
1221impl Area {
1222    /// XSD areaType: nonNegativeDouble
1223    pub fn new(value: f64, units: Option<AreaUnits>) -> Result<Self> {
1224        if value < 0.0 {
1225            return Err(crate::error::ValidationError::OutOfRange {
1226                name: "Area".into(),
1227                value: value.to_string(),
1228                expected: ">= 0".into(),
1229                line: None,
1230            }
1231            .into());
1232        }
1233        Ok(Self { value, units })
1234    }
1235    pub fn to_unit_value(&self) -> UnitValue<f64, AreaUnits> {
1236        UnitValue {
1237            value: self.value,
1238            units: self.units.clone(),
1239        }
1240    }
1241}
1242impl FromKvnFloat for Area {
1243    fn from_kvn_float(value: f64, unit: Option<&str>) -> Result<Self> {
1244        let uv = UnitValue::<f64, AreaUnits>::from_kvn_float(value, unit)?;
1245        Self::new(uv.value, uv.units)
1246    }
1247}
1248impl std::fmt::Display for Area {
1249    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1250        write!(f, "{}", self.value)
1251    }
1252}
1253define_required_unit_type!(Ms2, Ms2Units, MPerS2, { MPerS2 => "m/s**2" });
1254
1255impl std::str::FromStr for Ms2 {
1256    type Err = std::num::ParseFloatError;
1257    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
1258        let v: f64 = s.parse()?;
1259        Ok(Self::new(v))
1260    }
1261}
1262
1263define_unit_type!(Km2, Km2Units, Km2, { Km2 => "km**2" });
1264
1265define_unit_type!(Km2s, Km2sUnits, Km2PerS, { Km2PerS => "km**2/s" });
1266
1267define_unit_type!(Km2s2, Km2s2Units, Km2PerS2, { Km2PerS2 => "km**2/s**2" });
1268
1269define_unit_type!(ManeuverFreq, NumPerYearUnits, PerYear, { PerYear => "#/yr" });
1270
1271define_unit_type!(Thrust, ThrustUnits, N, { N => "N" });
1272
1273define_unit_type!(Geomag, GeomagUnits, NanoTesla, { NanoTesla => "nT" });
1274
1275define_unit_type!(
1276    SolarFlux,
1277    SolarFluxUnits,
1278    Sfu,
1279    {
1280        Sfu => "SFU",
1281        JanskyScaled => "10**4 Jansky",
1282        WPerM2Hz => "10**-22 W/(m**2/Hz)",
1283        ErgPerSCm2Hz => "10**-19 erg/(s*cm**2*Hz)"
1284    }
1285);
1286
1287// --- Moment --- (restore)
1288define_unit_type!(Moment, MomentUnits, KgM2, { KgM2 => "kg*m**2" });
1289
1290define_unit_type!(BallisticCoeff, BallisticCoeffUnits, KgPerM2, { KgPerM2 => "kg/m**2" });
1291
1292define_unit_enum!(PercentageUnits, Percent, { Percent => "%" });
1293
1294#[derive(Serialize, Debug, PartialEq, Clone)]
1295pub struct PercentageRequired {
1296    #[serde(rename = "$value")]
1297    pub value: f64,
1298    #[serde(rename = "@units")]
1299    pub units: PercentageUnits,
1300}
1301
1302impl<'de> serde::Deserialize<'de> for PercentageRequired {
1303    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
1304    where
1305        D: serde::Deserializer<'de>,
1306    {
1307        struct Visitor;
1308        impl<'de> serde::de::Visitor<'de> for Visitor {
1309            type Value = PercentageRequired;
1310            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
1311                formatter.write_str("a float value or map with $value and @units")
1312            }
1313
1314            fn visit_f64<E>(self, v: f64) -> std::result::Result<Self::Value, E>
1315            where
1316                E: serde::de::Error,
1317            {
1318                PercentageRequired::new(v).map_err(E::custom)
1319            }
1320
1321            fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
1322            where
1323                E: serde::de::Error,
1324            {
1325                let val = v.parse::<f64>().map_err(E::custom)?;
1326                PercentageRequired::new(val).map_err(E::custom)
1327            }
1328
1329            fn visit_map<A>(self, mut map: A) -> std::result::Result<Self::Value, A::Error>
1330            where
1331                A: serde::de::MapAccess<'de>,
1332            {
1333                let mut value = None;
1334                let mut units = None;
1335                while let Some(key) = map.next_key::<String>()? {
1336                    if key == "$value" || key == "$text" {
1337                        value = Some(map.next_value::<f64>()?);
1338                    } else if key == "@units" {
1339                        units = Some(map.next_value::<PercentageUnits>()?);
1340                    } else {
1341                        let _ = map.next_value::<serde::de::IgnoredAny>()?;
1342                    }
1343                }
1344                let value = value.ok_or_else(|| serde::de::Error::missing_field("$value"))?;
1345                let mut s = PercentageRequired::new(value).map_err(serde::de::Error::custom)?;
1346                if let Some(u) = units {
1347                    s.units = u;
1348                }
1349                Ok(s)
1350            }
1351        }
1352        deserializer.deserialize_any(Visitor)
1353    }
1354}
1355impl PercentageRequired {
1356    pub fn new(value: f64) -> Result<Self> {
1357        if !(0.0..=100.0).contains(&value) {
1358            return Err(crate::error::ValidationError::OutOfRange {
1359                name: "PercentageRequired".into(),
1360                value: value.to_string(),
1361                expected: "[0, 100]".into(),
1362                line: None,
1363            }
1364            .into());
1365        }
1366        Ok(Self {
1367            value,
1368            units: PercentageUnits::Percent,
1369        })
1370    }
1371}
1372impl std::fmt::Display for PercentageRequired {
1373    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1374        write!(f, "{}", self.value)
1375    }
1376}
1377impl FromKvnFloat for PercentageRequired {
1378    fn from_kvn_float(value: f64, _unit: Option<&str>) -> Result<Self> {
1379        Self::new(value)
1380    }
1381}
1382
1383#[derive(Serialize, Debug, PartialEq, Clone)]
1384pub struct Probability {
1385    #[serde(rename = "$value")]
1386    pub value: f64,
1387}
1388
1389impl std::str::FromStr for Probability {
1390    type Err = crate::error::CcsdsNdmError;
1391    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
1392        let val = s.parse::<f64>()?;
1393        Self::new(val)
1394    }
1395}
1396
1397impl<'de> serde::Deserialize<'de> for Probability {
1398    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
1399    where
1400        D: serde::Deserializer<'de>,
1401    {
1402        struct ProbabilityVisitor;
1403
1404        impl<'de> serde::de::Visitor<'de> for ProbabilityVisitor {
1405            type Value = Probability;
1406
1407            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
1408                formatter.write_str("a primitive value or a map with $value")
1409            }
1410
1411            fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
1412            where
1413                E: serde::de::Error,
1414            {
1415                v.parse::<Probability>().map_err(E::custom)
1416            }
1417
1418            fn visit_map<A>(self, mut map: A) -> std::result::Result<Self::Value, A::Error>
1419            where
1420                A: serde::de::MapAccess<'de>,
1421            {
1422                let mut value = None;
1423
1424                while let Some(key) = map.next_key::<String>()? {
1425                    if key == "$value" || key == "$text" {
1426                        value = Some(map.next_value()?);
1427                    } else {
1428                        let _: serde::de::IgnoredAny = map.next_value()?;
1429                    }
1430                }
1431
1432                let value = value.ok_or_else(|| serde::de::Error::missing_field("$value"))?;
1433                Probability::new(value).map_err(serde::de::Error::custom)
1434            }
1435        }
1436
1437        deserializer.deserialize_any(ProbabilityVisitor)
1438    }
1439}
1440
1441impl Probability {
1442    pub fn new(value: f64) -> Result<Self> {
1443        if !(0.0..=1.0).contains(&value) {
1444            return Err(crate::error::ValidationError::OutOfRange {
1445                name: "Probability".into(),
1446                value: value.to_string(),
1447                expected: "[0, 1]".into(),
1448                line: None,
1449            }
1450            .into());
1451        }
1452        Ok(Self { value })
1453    }
1454}
1455
1456impl std::fmt::Display for Probability {
1457    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1458        write!(f, "{}", self.value)
1459    }
1460}
1461
1462impl FromKvnFloat for Probability {
1463    fn from_kvn_float(value: f64, _unit: Option<&str>) -> Result<Self> {
1464        Self::new(value)
1465    }
1466}
1467
1468/// XSD nonNegativeDouble - value must be >= 0
1469#[derive(Serialize, Debug, PartialEq, Clone, Copy)]
1470pub struct NonNegativeDouble {
1471    #[serde(rename = "$value")]
1472    pub value: f64,
1473}
1474
1475impl<'de> serde::Deserialize<'de> for NonNegativeDouble {
1476    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
1477    where
1478        D: serde::Deserializer<'de>,
1479    {
1480        struct NonNegativeDoubleVisitor;
1481
1482        impl<'de> serde::de::Visitor<'de> for NonNegativeDoubleVisitor {
1483            type Value = NonNegativeDouble;
1484
1485            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
1486                formatter.write_str("a primitive value or a map with $value")
1487            }
1488
1489            fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
1490            where
1491                E: serde::de::Error,
1492            {
1493                v.parse::<NonNegativeDouble>().map_err(E::custom)
1494            }
1495
1496            fn visit_map<A>(self, mut map: A) -> std::result::Result<Self::Value, A::Error>
1497            where
1498                A: serde::de::MapAccess<'de>,
1499            {
1500                let mut value = None;
1501
1502                while let Some(key) = map.next_key::<String>()? {
1503                    if key == "$value" || key == "$text" {
1504                        value = Some(map.next_value()?);
1505                    } else {
1506                        let _: serde::de::IgnoredAny = map.next_value()?;
1507                    }
1508                }
1509
1510                let value = value.ok_or_else(|| serde::de::Error::missing_field("$value"))?;
1511                NonNegativeDouble::new(value).map_err(serde::de::Error::custom)
1512            }
1513        }
1514
1515        deserializer.deserialize_any(NonNegativeDoubleVisitor)
1516    }
1517}
1518
1519impl NonNegativeDouble {
1520    pub fn new(value: f64) -> Result<Self> {
1521        if value < 0.0 {
1522            return Err(crate::error::ValidationError::OutOfRange {
1523                name: "NonNegativeDouble".into(),
1524                value: value.to_string(),
1525                expected: ">= 0".into(),
1526                line: None,
1527            }
1528            .into());
1529        }
1530        Ok(Self { value })
1531    }
1532}
1533
1534impl std::str::FromStr for NonNegativeDouble {
1535    type Err = CcsdsNdmError;
1536    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
1537        let v: f64 = s.parse().map_err(CcsdsNdmError::from)?;
1538        Self::new(v)
1539    }
1540}
1541
1542impl std::fmt::Display for NonNegativeDouble {
1543    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1544        write!(f, "{}", self.value)
1545    }
1546}
1547
1548impl FromKvnFloat for NonNegativeDouble {
1549    fn from_kvn_float(value: f64, _unit: Option<&str>) -> Result<Self> {
1550        Self::new(value)
1551    }
1552}
1553
1554/// XSD positiveInteger - value must be > 0
1555#[derive(Serialize, Debug, PartialEq, Clone, Copy)]
1556pub struct PositiveInteger {
1557    #[serde(rename = "$value")]
1558    pub value: u32,
1559}
1560
1561impl<'de> serde::Deserialize<'de> for PositiveInteger {
1562    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
1563    where
1564        D: serde::Deserializer<'de>,
1565    {
1566        struct PositiveIntegerVisitor;
1567
1568        impl<'de> serde::de::Visitor<'de> for PositiveIntegerVisitor {
1569            type Value = PositiveInteger;
1570
1571            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
1572                formatter.write_str("a primitive value or a map with $value")
1573            }
1574
1575            fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
1576            where
1577                E: serde::de::Error,
1578            {
1579                v.parse::<PositiveInteger>().map_err(E::custom)
1580            }
1581
1582            fn visit_map<A>(self, mut map: A) -> std::result::Result<Self::Value, A::Error>
1583            where
1584                A: serde::de::MapAccess<'de>,
1585            {
1586                let mut value = None;
1587
1588                while let Some(key) = map.next_key::<String>()? {
1589                    if key == "$value" || key == "$text" {
1590                        value = Some(map.next_value()?);
1591                    } else {
1592                        let _: serde::de::IgnoredAny = map.next_value()?;
1593                    }
1594                }
1595
1596                let value = value.ok_or_else(|| serde::de::Error::missing_field("$value"))?;
1597                PositiveInteger::new(value).map_err(serde::de::Error::custom)
1598            }
1599        }
1600
1601        deserializer.deserialize_any(PositiveIntegerVisitor)
1602    }
1603}
1604
1605impl PositiveInteger {
1606    pub fn new(value: u32) -> Result<Self> {
1607        if value == 0 {
1608            return Err(crate::error::ValidationError::OutOfRange {
1609                name: "PositiveInteger".into(),
1610                value: value.to_string(),
1611                expected: "> 0".into(),
1612                line: None,
1613            }
1614            .into());
1615        }
1616        Ok(Self { value })
1617    }
1618}
1619
1620impl std::str::FromStr for PositiveInteger {
1621    type Err = CcsdsNdmError;
1622    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
1623        let v: u32 = s.parse().map_err(CcsdsNdmError::from)?;
1624        Self::new(v)
1625    }
1626}
1627
1628/// A non-zero degree for interpolation.
1629#[derive(Serialize, Debug, PartialEq, Clone, Copy)]
1630pub struct InterpolationDegree(pub std::num::NonZeroU32);
1631
1632impl std::str::FromStr for InterpolationDegree {
1633    type Err = crate::error::CcsdsNdmError;
1634    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
1635        let val = s.parse::<u32>()?;
1636        let nz = std::num::NonZeroU32::new(val).ok_or_else(|| {
1637            crate::error::ValidationError::OutOfRange {
1638                name: "InterpolationDegree".into(),
1639                value: val.to_string(),
1640                expected: "> 0".into(),
1641                line: None,
1642            }
1643        })?;
1644        Ok(Self(nz))
1645    }
1646}
1647
1648impl std::fmt::Display for InterpolationDegree {
1649    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1650        write!(f, "{}", self.0)
1651    }
1652}
1653
1654impl<'de> serde::Deserialize<'de> for InterpolationDegree {
1655    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
1656    where
1657        D: serde::Deserializer<'de>,
1658    {
1659        struct InterpolationDegreeVisitor;
1660
1661        impl<'de> serde::de::Visitor<'de> for InterpolationDegreeVisitor {
1662            type Value = InterpolationDegree;
1663
1664            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
1665                formatter.write_str("a primitive value or a map with $value")
1666            }
1667
1668            fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
1669            where
1670                E: serde::de::Error,
1671            {
1672                v.parse::<InterpolationDegree>().map_err(E::custom)
1673            }
1674
1675            fn visit_u32<E>(self, v: u32) -> std::result::Result<Self::Value, E>
1676            where
1677                E: serde::de::Error,
1678            {
1679                std::num::NonZeroU32::new(v)
1680                    .map(InterpolationDegree)
1681                    .ok_or_else(|| E::custom("expected non-zero u32"))
1682            }
1683
1684            fn visit_map<A>(self, mut map: A) -> std::result::Result<Self::Value, A::Error>
1685            where
1686                A: serde::de::MapAccess<'de>,
1687            {
1688                let mut value = None;
1689
1690                while let Some(key) = map.next_key::<String>()? {
1691                    if key == "$value" || key == "$text" {
1692                        value = Some(map.next_value::<u32>()?);
1693                    } else {
1694                        let _: serde::de::IgnoredAny = map.next_value()?;
1695                    }
1696                }
1697
1698                let value = value.ok_or_else(|| serde::de::Error::missing_field("$value"))?;
1699                std::num::NonZeroU32::new(value)
1700                    .map(InterpolationDegree)
1701                    .ok_or_else(|| serde::de::Error::custom("expected non-zero u32"))
1702            }
1703        }
1704
1705        deserializer.deserialize_any(InterpolationDegreeVisitor)
1706    }
1707}
1708
1709impl From<std::num::NonZeroU32> for InterpolationDegree {
1710    fn from(val: std::num::NonZeroU32) -> Self {
1711        Self(val)
1712    }
1713}
1714
1715impl From<InterpolationDegree> for std::num::NonZeroU32 {
1716    fn from(val: InterpolationDegree) -> Self {
1717        val.0
1718    }
1719}
1720
1721impl std::fmt::Display for PositiveInteger {
1722    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1723        write!(f, "{}", self.value)
1724    }
1725}
1726
1727impl From<u32> for PositiveInteger {
1728    fn from(value: u32) -> Self {
1729        Self { value }
1730    }
1731}
1732
1733/// XSD elementSetNoType - value must be between 0 and 9999
1734#[derive(Serialize, Debug, PartialEq, Clone, Copy)]
1735pub struct ElementSetNo {
1736    #[serde(rename = "$value")]
1737    pub value: u32,
1738}
1739
1740impl std::str::FromStr for ElementSetNo {
1741    type Err = crate::error::CcsdsNdmError;
1742    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
1743        let v: u32 = s.parse().map_err(CcsdsNdmError::from)?;
1744        Self::new(v)
1745    }
1746}
1747
1748impl<'de> serde::Deserialize<'de> for ElementSetNo {
1749    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
1750    where
1751        D: serde::Deserializer<'de>,
1752    {
1753        struct ElementSetNoVisitor;
1754
1755        impl<'de> serde::de::Visitor<'de> for ElementSetNoVisitor {
1756            type Value = ElementSetNo;
1757
1758            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
1759                formatter.write_str("a primitive value or a map with $value")
1760            }
1761
1762            fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
1763            where
1764                E: serde::de::Error,
1765            {
1766                v.parse::<ElementSetNo>().map_err(E::custom)
1767            }
1768
1769            fn visit_map<A>(self, mut map: A) -> std::result::Result<Self::Value, A::Error>
1770            where
1771                A: serde::de::MapAccess<'de>,
1772            {
1773                let mut value = None;
1774
1775                while let Some(key) = map.next_key::<String>()? {
1776                    if key == "$value" || key == "$text" {
1777                        value = Some(map.next_value()?);
1778                    } else {
1779                        let _: serde::de::IgnoredAny = map.next_value()?;
1780                    }
1781                }
1782
1783                let value = value.ok_or_else(|| serde::de::Error::missing_field("$value"))?;
1784                ElementSetNo::new(value).map_err(serde::de::Error::custom)
1785            }
1786        }
1787
1788        deserializer.deserialize_any(ElementSetNoVisitor)
1789    }
1790}
1791
1792impl ElementSetNo {
1793    pub fn new(value: u32) -> Result<Self> {
1794        if value > 9999 {
1795            return Err(crate::error::ValidationError::OutOfRange {
1796                name: "ElementSetNo".into(),
1797                value: value.to_string(),
1798                expected: "[0, 9999]".into(),
1799                line: None,
1800            }
1801            .into());
1802        }
1803        Ok(Self { value })
1804    }
1805}
1806
1807impl std::fmt::Display for ElementSetNo {
1808    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1809        write!(f, "{}", self.value)
1810    }
1811}
1812
1813impl From<u32> for ElementSetNo {
1814    fn from(value: u32) -> Self {
1815        Self { value }
1816    }
1817}
1818
1819// Delta mass types (negative or non-positive)
1820#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
1821pub struct DeltaMass {
1822    #[serde(rename = "$value")]
1823    pub value: f64,
1824    #[serde(rename = "@units", default, skip_serializing_if = "Option::is_none")]
1825    pub units: Option<MassUnits>,
1826}
1827impl DeltaMass {
1828    pub fn new(value: f64, units: Option<MassUnits>) -> Result<Self> {
1829        if value >= 0.0 {
1830            return Err(crate::error::ValidationError::OutOfRange {
1831                name: "DeltaMass".into(),
1832                value: value.to_string(),
1833                expected: "< 0".into(),
1834                line: None,
1835            }
1836            .into());
1837        }
1838        Ok(Self { value, units })
1839    }
1840}
1841
1842impl FromKvnFloat for DeltaMass {
1843    fn from_kvn_float(value: f64, unit: Option<&str>) -> Result<Self> {
1844        let uv = UnitValue::<f64, MassUnits>::from_kvn_float(value, unit)?;
1845        Self::new(uv.value, uv.units)
1846    }
1847}
1848
1849#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
1850pub struct DeltaMassZ {
1851    #[serde(rename = "$value")]
1852    pub value: f64,
1853    #[serde(rename = "@units", default, skip_serializing_if = "Option::is_none")]
1854    pub units: Option<MassUnits>,
1855}
1856impl DeltaMassZ {
1857    pub fn new(value: f64, units: Option<MassUnits>) -> Result<Self> {
1858        if value > 0.0 {
1859            return Err(crate::error::ValidationError::OutOfRange {
1860                name: "DeltaMassZ".into(),
1861                value: value.to_string(),
1862                expected: "<= 0".into(),
1863                line: None,
1864            }
1865            .into());
1866        }
1867        Ok(Self { value, units })
1868    }
1869
1870    pub fn to_unit_value(&self) -> UnitValue<f64, MassUnits> {
1871        UnitValue {
1872            value: self.value,
1873            units: self.units.clone(),
1874        }
1875    }
1876}
1877
1878impl FromKvnFloat for DeltaMassZ {
1879    fn from_kvn_float(value: f64, unit: Option<&str>) -> Result<Self> {
1880        let uv = UnitValue::<f64, MassUnits>::from_kvn_float(value, unit)?;
1881        Self::new(uv.value, uv.units)
1882    }
1883}
1884
1885// Quaternion dot component units (1/s)
1886define_unit_type!(QuaternionDotComponent, QuaternionDotUnits, PerS, { PerS => "1/s" });
1887
1888// Latitude / Longitude / Altitude
1889define_unit_enum!(LatLonUnits, Deg, { Deg => "deg" });
1890pub type Latitude = UnitValue<f64, LatLonUnits>;
1891pub type Longitude = UnitValue<f64, LatLonUnits>;
1892pub type Altitude = UnitValue<f64, LengthUnits>;
1893
1894#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
1895pub struct LatitudeRequired {
1896    #[serde(rename = "$value")]
1897    pub value: f64,
1898    #[serde(rename = "@units")]
1899    pub units: LatLonUnits,
1900}
1901impl LatitudeRequired {
1902    pub fn new(value: f64) -> Result<Self> {
1903        if !(-90.0..=90.0).contains(&value) {
1904            return Err(crate::error::ValidationError::OutOfRange {
1905                name: "Latitude".into(),
1906                value: value.to_string(),
1907                expected: "[-90, 90]".into(),
1908                line: None,
1909            }
1910            .into());
1911        }
1912        Ok(Self {
1913            value,
1914            units: LatLonUnits::Deg,
1915        })
1916    }
1917}
1918impl std::fmt::Display for LatitudeRequired {
1919    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1920        write!(f, "{}", self.value)
1921    }
1922}
1923
1924impl std::str::FromStr for LatitudeRequired {
1925    type Err = CcsdsNdmError;
1926    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
1927        let v: f64 = s.parse().map_err(CcsdsNdmError::from)?;
1928        Self::new(v)
1929    }
1930}
1931
1932impl FromKvnFloat for LatitudeRequired {
1933    fn from_kvn_float(value: f64, _unit: Option<&str>) -> Result<Self> {
1934        Self::new(value)
1935    }
1936}
1937
1938#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
1939pub struct LongitudeRequired {
1940    #[serde(rename = "$value")]
1941    pub value: f64,
1942    #[serde(rename = "@units")]
1943    pub units: LatLonUnits,
1944}
1945impl LongitudeRequired {
1946    pub fn new(value: f64) -> Result<Self> {
1947        if !(-180.0..=180.0).contains(&value) {
1948            return Err(crate::error::ValidationError::OutOfRange {
1949                name: "Longitude".into(),
1950                value: value.to_string(),
1951                expected: "[-180, 180]".into(),
1952                line: None,
1953            }
1954            .into());
1955        }
1956        Ok(Self {
1957            value,
1958            units: LatLonUnits::Deg,
1959        })
1960    }
1961}
1962impl std::fmt::Display for LongitudeRequired {
1963    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1964        write!(f, "{}", self.value)
1965    }
1966}
1967
1968impl std::str::FromStr for LongitudeRequired {
1969    type Err = CcsdsNdmError;
1970    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
1971        let v: f64 = s.parse().map_err(CcsdsNdmError::from)?;
1972        Self::new(v)
1973    }
1974}
1975
1976impl FromKvnFloat for LongitudeRequired {
1977    fn from_kvn_float(value: f64, _unit: Option<&str>) -> Result<Self> {
1978        Self::new(value)
1979    }
1980}
1981
1982// Torque
1983define_unit_type!(Torque, TorqueUnits, Nm, { Nm => "N*m" });
1984
1985// Vector helper for cpType / targetMomentumType
1986#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
1987pub struct Vector3 {
1988    #[serde(rename = "$value", with = "crate::utils::vec_f64_space_sep")]
1989    pub elements: Vec<f64>, // Expect length 3
1990    #[serde(rename = "@units", default, skip_serializing_if = "Option::is_none")]
1991    pub units: Option<LengthUnits>,
1992}
1993impl Vector3 {
1994    pub fn new(elements: [f64; 3], units: Option<LengthUnits>) -> Self {
1995        Self {
1996            elements: elements.to_vec(),
1997            units,
1998        }
1999    }
2000}
2001
2002// Target momentum vector (uses angular momentum units)
2003#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
2004pub struct TargetMomentum {
2005    #[serde(rename = "$value", with = "crate::utils::vec_f64_space_sep")]
2006    pub elements: Vec<f64>, // length 3
2007    #[serde(rename = "@units", default, skip_serializing_if = "Option::is_none")]
2008    pub units: Option<AngMomentumUnits>,
2009}
2010impl TargetMomentum {
2011    pub fn new(elements: [f64; 3], units: Option<AngMomentumUnits>) -> Self {
2012        Self {
2013            elements: elements.to_vec(),
2014            units,
2015        }
2016    }
2017}
2018
2019// Categorical Enums
2020//----------------------------------------------------------------------
2021
2022#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
2023pub enum ObjectDescription {
2024    #[serde(rename = "PAYLOAD")]
2025    Payload,
2026    #[serde(rename = "payload")]
2027    PayloadLower,
2028    #[serde(rename = "ROCKET BODY")]
2029    RocketBody,
2030    #[serde(rename = "rocket body")]
2031    RocketBodyLower,
2032    #[serde(rename = "DEBRIS")]
2033    Debris,
2034    #[serde(rename = "debris")]
2035    DebrisLower,
2036    #[serde(rename = "UNKNOWN")]
2037    Unknown,
2038    #[serde(rename = "unknown")]
2039    UnknownLower,
2040    #[serde(rename = "OTHER")]
2041    Other,
2042    #[serde(rename = "other")]
2043    OtherLower,
2044}
2045
2046impl std::str::FromStr for ObjectDescription {
2047    type Err = crate::error::EnumParseError;
2048    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
2049        match s.to_uppercase().as_str() {
2050            "PAYLOAD" => Ok(Self::Payload),
2051            "ROCKET BODY" => Ok(Self::RocketBody),
2052            "DEBRIS" => Ok(Self::Debris),
2053            "UNKNOWN" => Ok(Self::Unknown),
2054            "OTHER" => Ok(Self::Other),
2055            _ => Ok(Self::Other),
2056        }
2057    }
2058}
2059impl std::fmt::Display for ObjectDescription {
2060    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2061        let s = match self {
2062            ObjectDescription::Payload | ObjectDescription::PayloadLower => "PAYLOAD",
2063            ObjectDescription::RocketBody | ObjectDescription::RocketBodyLower => "ROCKET BODY",
2064            ObjectDescription::Debris | ObjectDescription::DebrisLower => "DEBRIS",
2065            ObjectDescription::Unknown | ObjectDescription::UnknownLower => "UNKNOWN",
2066            ObjectDescription::Other | ObjectDescription::OtherLower => "OTHER",
2067        };
2068        write!(f, "{}", s)
2069    }
2070}
2071
2072#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
2073pub enum RotSeq {
2074    #[serde(rename = "XYX")]
2075    XYX,
2076    #[serde(rename = "XYZ")]
2077    XYZ,
2078    #[serde(rename = "XZX")]
2079    XZX,
2080    #[serde(rename = "XZY")]
2081    XZY,
2082    #[serde(rename = "YXY")]
2083    YXY,
2084    #[serde(rename = "YXZ")]
2085    YXZ,
2086    #[serde(rename = "YZX")]
2087    YZX,
2088    #[serde(rename = "YZY")]
2089    YZY,
2090    #[serde(rename = "ZXY")]
2091    ZXY,
2092    #[serde(rename = "ZXZ")]
2093    ZXZ,
2094    #[serde(rename = "ZYX")]
2095    ZYX,
2096    #[serde(rename = "ZYZ")]
2097    ZYZ,
2098}
2099
2100impl std::str::FromStr for RotSeq {
2101    type Err = crate::error::EnumParseError;
2102    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
2103        match s {
2104            "XYX" => Ok(Self::XYX),
2105            "XYZ" => Ok(Self::XYZ),
2106            "XZX" => Ok(Self::XZX),
2107            "XZY" => Ok(Self::XZY),
2108            "YXY" => Ok(Self::YXY),
2109            "YXZ" => Ok(Self::YXZ),
2110            "YZX" => Ok(Self::YZX),
2111            "YZY" => Ok(Self::YZY),
2112            "ZXY" => Ok(Self::ZXY),
2113            "ZXZ" => Ok(Self::ZXZ),
2114            "ZYX" => Ok(Self::ZYX),
2115            "ZYZ" => Ok(Self::ZYZ),
2116            "121" => Ok(Self::XYX),
2117            "123" => Ok(Self::XYZ),
2118            "131" => Ok(Self::XZX),
2119            "132" => Ok(Self::XZY),
2120            "212" => Ok(Self::YXY),
2121            "213" => Ok(Self::YXZ),
2122            "231" => Ok(Self::YZX),
2123            "232" => Ok(Self::YZY),
2124            "312" => Ok(Self::ZXY),
2125            "313" => Ok(Self::ZXZ),
2126            "321" => Ok(Self::ZYX),
2127            "323" => Ok(Self::ZYZ),
2128            _ => Err(crate::error::EnumParseError {
2129                field: "EULER_ROT_SEQ",
2130                value: s.to_string(),
2131                expected: "XYX, XYZ, XZX, XZY, YXY, YXZ, YZX, YZY, ZXY, ZXZ, ZYX, ZYZ, or numeric equivalents",
2132            }),
2133        }
2134    }
2135}
2136
2137impl std::fmt::Display for RotSeq {
2138    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2139        match self {
2140            Self::XYX => write!(f, "XYX"),
2141            Self::XYZ => write!(f, "XYZ"),
2142            Self::XZX => write!(f, "XZX"),
2143            Self::XZY => write!(f, "XZY"),
2144            Self::YXY => write!(f, "YXY"),
2145            Self::YXZ => write!(f, "YXZ"),
2146            Self::YZX => write!(f, "YZX"),
2147            Self::YZY => write!(f, "YZY"),
2148            Self::ZXY => write!(f, "ZXY"),
2149            Self::ZXZ => write!(f, "ZXZ"),
2150            Self::ZYX => write!(f, "ZYX"),
2151            Self::ZYZ => write!(f, "ZYZ"),
2152        }
2153    }
2154}
2155
2156#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
2157pub enum AdMethod {
2158    #[serde(rename = "EKF")]
2159    Ekf,
2160    #[serde(rename = "ekf")]
2161    EkfLower,
2162    #[serde(rename = "TRIAD")]
2163    Triad,
2164    #[serde(rename = "triad")]
2165    TriadLower,
2166    #[serde(rename = "QUEST")]
2167    Quest,
2168    #[serde(rename = "quest")]
2169    QuestLower,
2170    #[serde(rename = "BATCH")]
2171    Batch,
2172    #[serde(rename = "batch")]
2173    BatchLower,
2174    #[serde(rename = "Q_METHOD")]
2175    QMethod,
2176    #[serde(rename = "q_method")]
2177    QMethodLower,
2178    #[serde(rename = "FILTER_SMOOTHER")]
2179    FilterSmoother,
2180    #[serde(rename = "filter_smoother")]
2181    FilterSmootherLower,
2182    #[serde(rename = "OTHER")]
2183    Other,
2184    #[serde(rename = "other")]
2185    OtherLower,
2186}
2187
2188#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
2189pub enum YesNo {
2190    #[serde(rename = "YES")]
2191    Yes,
2192    #[serde(rename = "yes")]
2193    YesLower,
2194    #[serde(rename = "NO")]
2195    No,
2196    #[serde(rename = "no")]
2197    NoLower,
2198}
2199impl std::fmt::Display for YesNo {
2200    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2201        let s = match self {
2202            YesNo::Yes | YesNo::YesLower => "YES",
2203            YesNo::No | YesNo::NoLower => "NO",
2204        };
2205        write!(f, "{}", s)
2206    }
2207}
2208impl std::str::FromStr for YesNo {
2209    type Err = crate::error::EnumParseError;
2210    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
2211        match s {
2212            "YES" | "yes" => Ok(YesNo::Yes),
2213            "NO" | "no" => Ok(YesNo::No),
2214            _ => Err(crate::error::EnumParseError {
2215                field: "YES/NO",
2216                value: s.to_string(),
2217                expected: "YES or NO",
2218            }),
2219        }
2220    }
2221}
2222
2223/// Basis of the trajectory state time history data.
2224#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
2225pub enum TrajBasis {
2226    /// Basis of this trajectory state time history data is 'PREDICTED'.
2227    #[serde(rename = "PREDICTED")]
2228    Predicted,
2229    /// Basis of this trajectory state time history data is 'DETERMINED' when estimated from
2230    /// observation-based orbit determination, reconstruction, and/or calibration. For
2231    /// definitive OD performed onboard spacecraft whose solutions have been telemetered to the
2232    /// ground for inclusion in an OCM, the TRAJ_BASIS shall be DETERMINED.
2233    #[serde(rename = "DETERMINED")]
2234    Determined,
2235    /// Basis of this trajectory state time history data is 'TELEMETRY' when the trajectory
2236    /// states are read directly from telemetry, for example, based on inertial navigation
2237    /// systems or GNSS data.
2238    #[serde(rename = "TELEMETRY")]
2239    Telemetry,
2240    /// Basis of this trajectory state time history data is 'SIMULATED' for generic
2241    /// simulations, future mission design studies, and optimization studies.
2242    #[serde(rename = "SIMULATED")]
2243    Simulated,
2244    /// Basis of this trajectory state time history data is 'OTHER' for other bases of this data.
2245    #[serde(rename = "OTHER")]
2246    Other,
2247}
2248
2249impl std::fmt::Display for TrajBasis {
2250    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2251        match self {
2252            Self::Predicted => write!(f, "PREDICTED"),
2253            Self::Determined => write!(f, "DETERMINED"),
2254            Self::Telemetry => write!(f, "TELEMETRY"),
2255            Self::Simulated => write!(f, "SIMULATED"),
2256            Self::Other => write!(f, "OTHER"),
2257        }
2258    }
2259}
2260
2261impl std::str::FromStr for TrajBasis {
2262    type Err = crate::error::EnumParseError;
2263    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
2264        match s.to_uppercase().as_str() {
2265            "PREDICTED" => Ok(Self::Predicted),
2266            "DETERMINED" => Ok(Self::Determined),
2267            "TELEMETRY" => Ok(Self::Telemetry),
2268            "SIMULATED" => Ok(Self::Simulated),
2269            "OTHER" => Ok(Self::Other),
2270            _ => Err(crate::error::EnumParseError {
2271                field: "TRAJ_BASIS",
2272                value: s.to_string(),
2273                expected: "PREDICTED, DETERMINED, TELEMETRY, SIMULATED, or OTHER",
2274            }),
2275        }
2276    }
2277}
2278
2279#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
2280pub enum RevNumBasis {
2281    #[serde(rename = "0")]
2282    Zero,
2283    #[serde(rename = "1")]
2284    One,
2285}
2286
2287impl std::fmt::Display for RevNumBasis {
2288    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2289        match self {
2290            Self::Zero => write!(f, "0"),
2291            Self::One => write!(f, "1"),
2292        }
2293    }
2294}
2295
2296impl std::str::FromStr for RevNumBasis {
2297    type Err = crate::error::EnumParseError;
2298    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
2299        match s {
2300            "0" => Ok(Self::Zero),
2301            "1" => Ok(Self::One),
2302            _ => Err(crate::error::EnumParseError {
2303                field: "ORB_REVNUM_BASIS",
2304                value: s.to_string(),
2305                expected: "0 or 1",
2306            }),
2307        }
2308    }
2309}
2310
2311/// Basis of the covariance time history data.
2312#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
2313pub enum CovBasis {
2314    /// Basis of this covariance time history data is 'PREDICTED'.
2315    #[serde(rename = "PREDICTED")]
2316    Predicted,
2317    /// Basis of this covariance time history data is 'DETERMINED' when estimated from
2318    /// observation-based orbit determination, reconstruction and/or calibration. For
2319    /// definitive OD performed onboard spacecraft whose solutions have been telemetered to the ground for
2320    /// inclusion in an OCM, the COV_BASIS shall be considered to be DETERMINED.
2321    #[serde(rename = "DETERMINED")]
2322    Determined,
2323    /// Basis of this covariance time history data is 'EMPIRICAL' (for empirically determined
2324    /// such as overlap analyses).
2325    #[serde(rename = "EMPIRICAL")]
2326    Empirical,
2327    /// Basis of this covariance time history data is 'SIMULATED' for simulation-based
2328    /// (including Monte Carlo) estimations, future mission design studies, and optimization
2329    /// studies.
2330    #[serde(rename = "SIMULATED")]
2331    Simulated,
2332    /// Basis of this covariance time history data is 'OTHER' for other bases of this data.
2333    #[serde(rename = "OTHER")]
2334    Other,
2335}
2336
2337impl std::fmt::Display for CovBasis {
2338    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2339        match self {
2340            Self::Predicted => write!(f, "PREDICTED"),
2341            Self::Determined => write!(f, "DETERMINED"),
2342            Self::Empirical => write!(f, "EMPIRICAL"),
2343            Self::Simulated => write!(f, "SIMULATED"),
2344            Self::Other => write!(f, "OTHER"),
2345        }
2346    }
2347}
2348
2349impl std::str::FromStr for CovBasis {
2350    type Err = crate::error::EnumParseError;
2351    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
2352        match s.to_uppercase().as_str() {
2353            "PREDICTED" => Ok(Self::Predicted),
2354            "DETERMINED" => Ok(Self::Determined),
2355            "EMPIRICAL" => Ok(Self::Empirical),
2356            "SIMULATED" => Ok(Self::Simulated),
2357            "OTHER" => Ok(Self::Other),
2358            _ => Err(crate::error::EnumParseError {
2359                field: "COV_BASIS",
2360                value: s.to_string(),
2361                expected: "PREDICTED, DETERMINED, EMPIRICAL, SIMULATED, or OTHER",
2362            }),
2363        }
2364    }
2365}
2366
2367/// Basis of the maneuver time history data.
2368#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
2369pub enum ManBasis {
2370    /// Basis of this maneuver time history data is 'CANDIDATE' for a proposed operational or a
2371    /// hypothetical (i.e., mission design and optimization studies) future maneuver.
2372    #[serde(rename = "CANDIDATE")]
2373    Candidate,
2374    /// Basis of this maneuver time history data is 'PLANNED' for a currently planned future
2375    /// maneuver.
2376    #[serde(rename = "PLANNED")]
2377    Planned,
2378    /// Basis of this maneuver time history data is 'ANTICIPATED' for a non-cooperative future
2379    /// maneuver that is anticipated (i.e., likely) to occur (e.g., based upon patterns-of-life
2380    /// analysis).
2381    #[serde(rename = "ANTICIPATED")]
2382    Anticipated,
2383    /// Basis of this maneuver time history data is 'TELEMETRY' when the maneuver is determined
2384    /// directly from telemetry (e.g., based on inertial navigation systems or
2385    /// accelerometers).
2386    #[serde(rename = "TELEMETRY")]
2387    Telemetry,
2388    /// Basis of this maneuver time history data is 'DETERMINED' when a past maneuver is
2389    /// estimated from observation-based orbit determination reconstruction and/or
2390    /// calibration.
2391    #[serde(rename = "DETERMINED")]
2392    Determined,
2393    /// Basis of this maneuver time history data is 'SIMULATED' for generic maneuver
2394    /// simulations, future mission design studies, and optimization studies.
2395    #[serde(rename = "SIMULATED")]
2396    Simulated,
2397    /// Basis of this maneuver time history data is 'OTHER' for other bases of this data.
2398    #[serde(rename = "OTHER")]
2399    Other,
2400}
2401
2402impl std::fmt::Display for ManBasis {
2403    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2404        match self {
2405            Self::Candidate => write!(f, "CANDIDATE"),
2406            Self::Planned => write!(f, "PLANNED"),
2407            Self::Anticipated => write!(f, "ANTICIPATED"),
2408            Self::Telemetry => write!(f, "TELEMETRY"),
2409            Self::Determined => write!(f, "DETERMINED"),
2410            Self::Simulated => write!(f, "SIMULATED"),
2411            Self::Other => write!(f, "OTHER"),
2412        }
2413    }
2414}
2415
2416impl std::str::FromStr for ManBasis {
2417    type Err = crate::error::EnumParseError;
2418    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
2419        match s.to_uppercase().as_str() {
2420            "CANDIDATE" => Ok(Self::Candidate),
2421            "PLANNED" => Ok(Self::Planned),
2422            "ANTICIPATED" => Ok(Self::Anticipated),
2423            "TELEMETRY" => Ok(Self::Telemetry),
2424            "DETERMINED" => Ok(Self::Determined),
2425            "SIMULATED" => Ok(Self::Simulated),
2426            "OTHER" => Ok(Self::Other),
2427            _ => Err(crate::error::EnumParseError {
2428                field: "MAN_BASIS",
2429                value: s.to_string(),
2430                expected:
2431                    "CANDIDATE, PLANNED, ANTICIPATED, TELEMETRY, DETERMINED, SIMULATED, or OTHER",
2432            }),
2433        }
2434    }
2435}
2436
2437/// Maneuver duty cycle type per XSD dcTypeType.
2438#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)]
2439pub enum ManDc {
2440    /// Duty cycle type 'CONTINUOUS' denotes full/continuous thrust.
2441    #[default]
2442    #[serde(rename = "CONTINUOUS")]
2443    Continuous,
2444    /// Duty cycle type 'TIME' denotes a time-based duty cycle driven by time past a reference
2445    /// time and the duty cycle ON and OFF durations.
2446    #[serde(rename = "TIME")]
2447    Time,
2448    /// Duty cycle type 'TIME_AND_ANGLE' denotes a duty cycle driven by the phasing/clocking of
2449    /// a space object body frame 'trigger' direction past a reference direction.
2450    #[serde(rename = "TIME_AND_ANGLE")]
2451    TimeAndAngle,
2452}
2453
2454impl std::fmt::Display for ManDc {
2455    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2456        match self {
2457            Self::Continuous => write!(f, "CONTINUOUS"),
2458            Self::Time => write!(f, "TIME"),
2459            Self::TimeAndAngle => write!(f, "TIME_AND_ANGLE"),
2460        }
2461    }
2462}
2463
2464impl std::str::FromStr for ManDc {
2465    type Err = crate::error::EnumParseError;
2466    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
2467        match s.to_uppercase().as_str() {
2468            "CONTINUOUS" => Ok(Self::Continuous),
2469            "TIME" => Ok(Self::Time),
2470            "TIME_AND_ANGLE" => Ok(Self::TimeAndAngle),
2471            _ => Err(crate::error::EnumParseError {
2472                field: "DC_TYPE",
2473                value: s.to_string(),
2474                expected: "CONTINUOUS, TIME, or TIME_AND_ANGLE",
2475            }),
2476        }
2477    }
2478}
2479
2480/// Covariance ordering.
2481#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)]
2482pub enum CovOrder {
2483    /// Covariance ordering is Lower Triangular Matrix (LTM).
2484    #[default]
2485    #[serde(rename = "LTM")]
2486    Ltm,
2487    /// Covariance ordering is Upper Triangular Matrix (UTM).
2488    #[serde(rename = "UTM")]
2489    Utm,
2490    /// Covariance ordering is Full covariance matrix.
2491    #[serde(rename = "FULL")]
2492    Full,
2493    /// Covariance ordering is LTM covariance with cross-correlation information provided in
2494    /// upper triangle off-diagonal terms (LTMWCC).
2495    #[serde(rename = "LTMWCC")]
2496    LtmWcc,
2497    /// Covariance ordering is UTM covariance with cross-correlation information provided in
2498    /// lower triangle off-diagonal terms (UTMWCC).
2499    #[serde(rename = "UTMWCC")]
2500    UtmWcc,
2501}
2502
2503impl std::fmt::Display for CovOrder {
2504    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2505        match self {
2506            Self::Ltm => write!(f, "LTM"),
2507            Self::Utm => write!(f, "UTM"),
2508            Self::Full => write!(f, "FULL"),
2509            Self::LtmWcc => write!(f, "LTMWCC"),
2510            Self::UtmWcc => write!(f, "UTMWCC"),
2511        }
2512    }
2513}
2514
2515impl std::str::FromStr for CovOrder {
2516    type Err = crate::error::EnumParseError;
2517    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
2518        match s.to_uppercase().as_str() {
2519            "LTM" => Ok(Self::Ltm),
2520            "UTM" => Ok(Self::Utm),
2521            "FULL" => Ok(Self::Full),
2522            "LTMWCC" => Ok(Self::LtmWcc),
2523            "UTMWCC" => Ok(Self::UtmWcc),
2524            _ => Err(crate::error::EnumParseError {
2525                field: "COV_ORDERING",
2526                value: s.to_string(),
2527                expected: "LTM, UTM, FULL, LTMWCC, or UTMWCC",
2528            }),
2529        }
2530    }
2531}
2532
2533#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
2534pub enum ControlledType {
2535    #[serde(rename = "YES")]
2536    Yes,
2537    #[serde(rename = "yes")]
2538    YesLower,
2539    #[serde(rename = "NO")]
2540    No,
2541    #[serde(rename = "no")]
2542    NoLower,
2543    #[serde(rename = "UNKNOWN")]
2544    Unknown,
2545    #[serde(rename = "unknown")]
2546    UnknownLower,
2547}
2548impl std::fmt::Display for ControlledType {
2549    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2550        let s = match self {
2551            ControlledType::Yes | ControlledType::YesLower => "YES",
2552            ControlledType::No | ControlledType::NoLower => "NO",
2553            ControlledType::Unknown | ControlledType::UnknownLower => "UNKNOWN",
2554        };
2555        write!(f, "{}", s)
2556    }
2557}
2558impl std::str::FromStr for ControlledType {
2559    type Err = crate::error::EnumParseError;
2560    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
2561        match s {
2562            "YES" | "yes" => Ok(ControlledType::Yes),
2563            "NO" | "no" => Ok(ControlledType::No),
2564            "UNKNOWN" | "unknown" => Ok(ControlledType::Unknown),
2565            _ => Err(crate::error::EnumParseError {
2566                field: "CONTROLLED_TYPE",
2567                value: s.to_string(),
2568                expected: "YES, NO, or UNKNOWN",
2569            }),
2570        }
2571    }
2572}
2573
2574// Time units ("s") plus Duration / RelTime / TimeOffset (optional units per XSD)
2575define_unit_enum!(TimeUnits, Seconds, { Seconds => "s", Day => "d" });
2576
2577#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
2578pub struct Duration {
2579    #[serde(rename = "$value")]
2580    pub value: f64, // nonNegativeDouble
2581    #[serde(rename = "@units", default, skip_serializing_if = "Option::is_none")]
2582    pub units: Option<TimeUnits>,
2583}
2584impl Duration {
2585    pub fn new(value: f64, units: Option<TimeUnits>) -> Result<Self> {
2586        if value < 0.0 {
2587            return Err(crate::error::ValidationError::OutOfRange {
2588                name: "Duration".into(),
2589                value: value.to_string(),
2590                expected: ">= 0".into(),
2591                line: None,
2592            }
2593            .into());
2594        }
2595        Ok(Self { value, units })
2596    }
2597    pub fn to_unit_value(&self) -> UnitValue<f64, TimeUnits> {
2598        UnitValue {
2599            value: self.value,
2600            units: self.units.clone(),
2601        }
2602    }
2603}
2604impl FromKvnFloat for Duration {
2605    fn from_kvn_float(value: f64, unit: Option<&str>) -> Result<Self> {
2606        let uv = UnitValue::<f64, TimeUnits>::from_kvn_float(value, unit)?;
2607        Self::new(uv.value, uv.units)
2608    }
2609}
2610#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
2611pub struct RelTime {
2612    #[serde(rename = "$value")]
2613    pub value: f64, // double (can be negative)
2614    #[serde(rename = "@units", default, skip_serializing_if = "Option::is_none")]
2615    pub units: Option<TimeUnits>,
2616}
2617
2618#[derive(Serialize, Debug, PartialEq, Clone)]
2619pub struct TimeOffset {
2620    #[serde(rename = "$value")]
2621    pub value: f64, // double
2622    #[serde(rename = "@units", default, skip_serializing_if = "Option::is_none")]
2623    pub units: Option<TimeUnits>,
2624}
2625
2626impl std::str::FromStr for TimeOffset {
2627    type Err = crate::error::CcsdsNdmError;
2628    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
2629        let val = s.parse::<f64>()?;
2630        Ok(Self {
2631            value: val,
2632            units: None,
2633        })
2634    }
2635}
2636
2637impl<'de> serde::Deserialize<'de> for TimeOffset {
2638    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
2639    where
2640        D: serde::Deserializer<'de>,
2641    {
2642        struct TimeOffsetVisitor;
2643
2644        impl<'de> serde::de::Visitor<'de> for TimeOffsetVisitor {
2645            type Value = TimeOffset;
2646
2647            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
2648                formatter.write_str("a primitive value or a map with $value and optionally @units")
2649            }
2650
2651            fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
2652            where
2653                E: serde::de::Error,
2654            {
2655                v.parse::<TimeOffset>().map_err(E::custom)
2656            }
2657
2658            fn visit_map<A>(self, mut map: A) -> std::result::Result<Self::Value, A::Error>
2659            where
2660                A: serde::de::MapAccess<'de>,
2661            {
2662                let mut value = None;
2663                let mut units = None;
2664
2665                while let Some(key) = map.next_key::<String>()? {
2666                    if key == "$value" || key == "$text" {
2667                        value = Some(map.next_value()?);
2668                    } else if key == "@units" {
2669                        units = Some(map.next_value()?);
2670                    } else {
2671                        let _: serde::de::IgnoredAny = map.next_value()?;
2672                    }
2673                }
2674
2675                let value = value.ok_or_else(|| serde::de::Error::missing_field("$value"))?;
2676                Ok(TimeOffset { value, units })
2677            }
2678        }
2679
2680        deserializer.deserialize_any(TimeOffsetVisitor)
2681    }
2682}
2683
2684impl FromKvnFloat for TimeOffset {
2685    fn from_kvn_float(value: f64, unit: Option<&str>) -> Result<Self> {
2686        let uv = UnitValue::<f64, TimeUnits>::from_kvn_float(value, unit)?;
2687        Ok(TimeOffset {
2688            value: uv.value,
2689            units: uv.units,
2690        })
2691    }
2692}
2693impl TimeOffset {
2694    pub fn to_unit_value(&self) -> UnitValue<f64, TimeUnits> {
2695        UnitValue {
2696            value: self.value,
2697            units: self.units.clone(),
2698        }
2699    }
2700}
2701
2702// Inclination (0 ..= 180 deg)
2703#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
2704#[serde(transparent)]
2705pub struct Inclination {
2706    pub angle: Angle, // uses AngleUnits (deg)
2707}
2708impl Inclination {
2709    pub fn new(value: f64, units: Option<AngleUnits>) -> Result<Self> {
2710        if !(0.0..=180.0).contains(&value) {
2711            return Err(crate::error::ValidationError::OutOfRange {
2712                name: "Inclination".into(),
2713                value: value.to_string(),
2714                expected: "[0, 180]".into(),
2715                line: None,
2716            }
2717            .into());
2718        }
2719        Ok(Self {
2720            angle: Angle { value, units },
2721        })
2722    }
2723    pub fn to_unit_value(&self) -> UnitValue<f64, AngleUnits> {
2724        UnitValue {
2725            value: self.angle.value,
2726            units: self.angle.units.clone(),
2727        }
2728    }
2729}
2730impl FromKvnFloat for Inclination {
2731    fn from_kvn_float(value: f64, unit: Option<&str>) -> Result<Self> {
2732        let uv = UnitValue::<f64, AngleUnits>::from_kvn_float(value, unit)?;
2733        Self::new(uv.value, uv.units)
2734    }
2735}
2736
2737// Attitude related enums (acmAttitudeType, attRateType, attBasisType, acmCovarianceLineType, attitudeTypeType)
2738#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
2739pub enum AcmAttitudeType {
2740    #[serde(rename = "QUATERNION")]
2741    Quaternion,
2742    #[serde(rename = "quaternion")]
2743    QuaternionLower,
2744    #[serde(rename = "EULER_ANGLES")]
2745    EulerAngles,
2746    #[serde(rename = "euler_angles")]
2747    EulerAnglesLower,
2748    #[serde(rename = "DCM")]
2749    Dcm,
2750    #[serde(rename = "dcm")]
2751    DcmLower,
2752    #[serde(rename = "ANGVEL")]
2753    AngVel,
2754    #[serde(rename = "angvel")]
2755    AngVelLower,
2756    #[serde(rename = "Q_DOT")]
2757    QDot,
2758    #[serde(rename = "q_dot")]
2759    QDotLower,
2760    #[serde(rename = "EULER_RATE")]
2761    EulerRate,
2762    #[serde(rename = "euler_rate")]
2763    EulerRateLower,
2764    #[serde(rename = "GYRO_BIAS")]
2765    GyroBias,
2766    #[serde(rename = "gyro_bias")]
2767    GyroBiasLower,
2768}
2769
2770impl std::str::FromStr for AcmAttitudeType {
2771    type Err = crate::error::EnumParseError;
2772    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
2773        match s {
2774            "QUATERNION" => Ok(Self::Quaternion),
2775            "quaternion" => Ok(Self::QuaternionLower),
2776            "EULER_ANGLES" => Ok(Self::EulerAngles),
2777            "euler_angles" => Ok(Self::EulerAnglesLower),
2778            "DCM" => Ok(Self::Dcm),
2779            "dcm" => Ok(Self::DcmLower),
2780            "ANGVEL" => Ok(Self::AngVel),
2781            "angvel" => Ok(Self::AngVelLower),
2782            "Q_DOT" => Ok(Self::QDot),
2783            "q_dot" => Ok(Self::QDotLower),
2784            "EULER_RATE" => Ok(Self::EulerRate),
2785            "euler_rate" => Ok(Self::EulerRateLower),
2786            "GYRO_BIAS" => Ok(Self::GyroBias),
2787            "gyro_bias" => Ok(Self::GyroBiasLower),
2788            _ => Err(crate::error::EnumParseError {
2789                field: "ATT_TYPE",
2790                value: s.to_string(),
2791                expected: "QUATERNION, EULER_ANGLES, DCM, ANGVEL, Q_DOT, EULER_RATE, or GYRO_BIAS",
2792            }),
2793        }
2794    }
2795}
2796
2797impl std::fmt::Display for AcmAttitudeType {
2798    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2799        let value = match self {
2800            Self::Quaternion | Self::QuaternionLower => "QUATERNION",
2801            Self::EulerAngles | Self::EulerAnglesLower => "EULER_ANGLES",
2802            Self::Dcm | Self::DcmLower => "DCM",
2803            Self::AngVel | Self::AngVelLower => "ANGVEL",
2804            Self::QDot | Self::QDotLower => "Q_DOT",
2805            Self::EulerRate | Self::EulerRateLower => "EULER_RATE",
2806            Self::GyroBias | Self::GyroBiasLower => "GYRO_BIAS",
2807        };
2808        write!(f, "{}", value)
2809    }
2810}
2811
2812#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
2813pub enum AttRateType {
2814    #[serde(rename = "ANGVEL")]
2815    AngVel,
2816    #[serde(rename = "angvel")]
2817    AngVelLower,
2818    #[serde(rename = "Q_DOT")]
2819    QDot,
2820    #[serde(rename = "q_dot")]
2821    QDotLower,
2822    #[serde(rename = "EULER_RATE")]
2823    EulerRate,
2824    #[serde(rename = "euler_rate")]
2825    EulerRateLower,
2826    #[serde(rename = "GYRO_BIAS")]
2827    GyroBias,
2828    #[serde(rename = "gyro_bias")]
2829    GyroBiasLower,
2830}
2831
2832impl std::str::FromStr for AttRateType {
2833    type Err = crate::error::EnumParseError;
2834    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
2835        match s {
2836            "ANGVEL" => Ok(Self::AngVel),
2837            "angvel" => Ok(Self::AngVelLower),
2838            "Q_DOT" => Ok(Self::QDot),
2839            "q_dot" => Ok(Self::QDotLower),
2840            "EULER_RATE" => Ok(Self::EulerRate),
2841            "euler_rate" => Ok(Self::EulerRateLower),
2842            "GYRO_BIAS" => Ok(Self::GyroBias),
2843            "gyro_bias" => Ok(Self::GyroBiasLower),
2844            _ => Err(crate::error::EnumParseError {
2845                field: "RATE_TYPE",
2846                value: s.to_string(),
2847                expected: "ANGVEL, Q_DOT, EULER_RATE, or GYRO_BIAS",
2848            }),
2849        }
2850    }
2851}
2852
2853impl std::fmt::Display for AttRateType {
2854    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2855        let value = match self {
2856            Self::AngVel | Self::AngVelLower => "ANGVEL",
2857            Self::QDot | Self::QDotLower => "Q_DOT",
2858            Self::EulerRate | Self::EulerRateLower => "EULER_RATE",
2859            Self::GyroBias | Self::GyroBiasLower => "GYRO_BIAS",
2860        };
2861        write!(f, "{}", value)
2862    }
2863}
2864
2865#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
2866pub enum AttBasisType {
2867    #[serde(rename = "PREDICTED")]
2868    Predicted,
2869    #[serde(rename = "predicted")]
2870    PredictedLower,
2871    #[serde(rename = "DETERMINED_GND")]
2872    DeterminedGnd,
2873    #[serde(rename = "determined_gnd")]
2874    DeterminedGndLower,
2875    #[serde(rename = "DETERMINED_OBC")]
2876    DeterminedObc,
2877    #[serde(rename = "determined_obc")]
2878    DeterminedObcLower,
2879    #[serde(rename = "SIMULATED")]
2880    Simulated,
2881    #[serde(rename = "simulated")]
2882    SimulatedLower,
2883}
2884
2885impl std::str::FromStr for AttBasisType {
2886    type Err = crate::error::EnumParseError;
2887    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
2888        match s {
2889            "PREDICTED" => Ok(Self::Predicted),
2890            "predicted" => Ok(Self::PredictedLower),
2891            "DETERMINED_GND" => Ok(Self::DeterminedGnd),
2892            "determined_gnd" => Ok(Self::DeterminedGndLower),
2893            "DETERMINED_OBC" => Ok(Self::DeterminedObc),
2894            "determined_obc" => Ok(Self::DeterminedObcLower),
2895            "SIMULATED" => Ok(Self::Simulated),
2896            "simulated" => Ok(Self::SimulatedLower),
2897            _ => Err(crate::error::EnumParseError {
2898                field: "ATT_BASIS",
2899                value: s.to_string(),
2900                expected: "PREDICTED, DETERMINED_GND, DETERMINED_OBC, or SIMULATED",
2901            }),
2902        }
2903    }
2904}
2905
2906impl std::fmt::Display for AttBasisType {
2907    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2908        let value = match self {
2909            Self::Predicted | Self::PredictedLower => "PREDICTED",
2910            Self::DeterminedGnd | Self::DeterminedGndLower => "DETERMINED_GND",
2911            Self::DeterminedObc | Self::DeterminedObcLower => "DETERMINED_OBC",
2912            Self::Simulated | Self::SimulatedLower => "SIMULATED",
2913        };
2914        write!(f, "{}", value)
2915    }
2916}
2917
2918#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
2919pub enum AcmCovarianceLineType {
2920    #[serde(rename = "ANGLE")]
2921    Angle,
2922    #[serde(rename = "angle")]
2923    AngleLower,
2924    #[serde(rename = "ANGLE_GYROBIAS")]
2925    AngleGyroBias,
2926    #[serde(rename = "angle_gyrobias")]
2927    AngleGyroBiasLower,
2928    #[serde(rename = "ANGLE_ANGVEL")]
2929    AngleAngVel,
2930    #[serde(rename = "angle_angvel")]
2931    AngleAngVelLower,
2932    #[serde(rename = "QUATERNION")]
2933    Quaternion,
2934    #[serde(rename = "quaternion")]
2935    QuaternionLower,
2936    #[serde(rename = "QUATERNION_GYROBIAS")]
2937    QuaternionGyroBias,
2938    #[serde(rename = "quaternion_gyrobias")]
2939    QuaternionGyroBiasLower,
2940    #[serde(rename = "QUATERNION_ANGVEL")]
2941    QuaternionAngVel,
2942    #[serde(rename = "quaternion_angvel")]
2943    QuaternionAngVelLower,
2944}
2945
2946impl std::str::FromStr for AcmCovarianceLineType {
2947    type Err = crate::error::EnumParseError;
2948    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
2949        match s {
2950            "ANGLE" => Ok(Self::Angle),
2951            "angle" => Ok(Self::AngleLower),
2952            "ANGLE_GYROBIAS" => Ok(Self::AngleGyroBias),
2953            "angle_gyrobias" => Ok(Self::AngleGyroBiasLower),
2954            "ANGLE_ANGVEL" => Ok(Self::AngleAngVel),
2955            "angle_angvel" => Ok(Self::AngleAngVelLower),
2956            "QUATERNION" => Ok(Self::Quaternion),
2957            "quaternion" => Ok(Self::QuaternionLower),
2958            "QUATERNION_GYROBIAS" => Ok(Self::QuaternionGyroBias),
2959            "quaternion_gyrobias" => Ok(Self::QuaternionGyroBiasLower),
2960            "QUATERNION_ANGVEL" => Ok(Self::QuaternionAngVel),
2961            "quaternion_angvel" => Ok(Self::QuaternionAngVelLower),
2962            _ => Err(crate::error::EnumParseError {
2963                field: "COV_TYPE",
2964                value: s.to_string(),
2965                expected: "ANGLE, ANGLE_GYROBIAS, ANGLE_ANGVEL, QUATERNION, QUATERNION_GYROBIAS, or QUATERNION_ANGVEL",
2966            }),
2967        }
2968    }
2969}
2970
2971impl std::fmt::Display for AcmCovarianceLineType {
2972    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2973        let value = match self {
2974            Self::Angle | Self::AngleLower => "ANGLE",
2975            Self::AngleGyroBias | Self::AngleGyroBiasLower => "ANGLE_GYROBIAS",
2976            Self::AngleAngVel | Self::AngleAngVelLower => "ANGLE_ANGVEL",
2977            Self::Quaternion | Self::QuaternionLower => "QUATERNION",
2978            Self::QuaternionGyroBias | Self::QuaternionGyroBiasLower => "QUATERNION_GYROBIAS",
2979            Self::QuaternionAngVel | Self::QuaternionAngVelLower => "QUATERNION_ANGVEL",
2980        };
2981        write!(f, "{}", value)
2982    }
2983}
2984
2985#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
2986pub enum AttitudeTypeType {
2987    #[serde(rename = "quaternion")]
2988    Quaternion,
2989    #[serde(rename = "QUATERNION")]
2990    QuaternionUpper,
2991    #[serde(rename = "quaternion/derivative")]
2992    QuaternionDerivative,
2993    #[serde(rename = "QUATERNION/DERIVATIVE")]
2994    QuaternionDerivativeUpper,
2995    #[serde(rename = "quaternion/angvel")]
2996    QuaternionAngVel,
2997    #[serde(rename = "QUATERNION/ANGVEL")]
2998    QuaternionAngVelUpper,
2999    #[serde(rename = "euler_angle")]
3000    EulerAngle,
3001    #[serde(rename = "EULER_ANGLE")]
3002    EulerAngleUpper,
3003    #[serde(rename = "euler_angle/derivative")]
3004    EulerAngleDerivative,
3005    #[serde(rename = "EULER_ANGLE/DERIVATIVE")]
3006    EulerAngleDerivativeUpper,
3007    #[serde(rename = "euler_angle/angvel")]
3008    EulerAngleAngVel,
3009    #[serde(rename = "EULER_ANGLE/ANGVEL")]
3010    EulerAngleAngVelUpper,
3011    #[serde(rename = "spin")]
3012    Spin,
3013    #[serde(rename = "SPIN")]
3014    SpinUpper,
3015    #[serde(rename = "spin/nutation")]
3016    SpinNutation,
3017    #[serde(rename = "SPIN/NUTATION")]
3018    SpinNutationUpper,
3019    #[serde(rename = "spin/nutation_mom")]
3020    SpinNutationMom,
3021    #[serde(rename = "SPIN/NUTATION_MOM")]
3022    SpinNutationMomUpper,
3023}
3024
3025impl std::str::FromStr for AttitudeTypeType {
3026    type Err = crate::error::EnumParseError;
3027    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
3028        match s {
3029            "quaternion" => Ok(Self::Quaternion),
3030            "QUATERNION" => Ok(Self::QuaternionUpper),
3031            "quaternion/derivative" => Ok(Self::QuaternionDerivative),
3032            "QUATERNION/DERIVATIVE" => Ok(Self::QuaternionDerivativeUpper),
3033            "quaternion/angvel" => Ok(Self::QuaternionAngVel),
3034            "QUATERNION/ANGVEL" => Ok(Self::QuaternionAngVelUpper),
3035            "euler_angle" => Ok(Self::EulerAngle),
3036            "EULER_ANGLE" => Ok(Self::EulerAngleUpper),
3037            "euler_angle/derivative" => Ok(Self::EulerAngleDerivative),
3038            "EULER_ANGLE/DERIVATIVE" => Ok(Self::EulerAngleDerivativeUpper),
3039            "euler_angle/angvel" => Ok(Self::EulerAngleAngVel),
3040            "EULER_ANGLE/ANGVEL" => Ok(Self::EulerAngleAngVelUpper),
3041            "spin" => Ok(Self::Spin),
3042            "SPIN" => Ok(Self::SpinUpper),
3043            "spin/nutation" => Ok(Self::SpinNutation),
3044            "SPIN/NUTATION" => Ok(Self::SpinNutationUpper),
3045            "spin/nutation_mom" => Ok(Self::SpinNutationMom),
3046            "SPIN/NUTATION_MOM" => Ok(Self::SpinNutationMomUpper),
3047            _ => Err(crate::error::EnumParseError {
3048                field: "ATTITUDE_TYPE",
3049                value: s.to_string(),
3050                expected: "QUATERNION, QUATERNION/DERIVATIVE, QUATERNION/ANGVEL, EULER_ANGLE, EULER_ANGLE/DERIVATIVE, EULER_ANGLE/ANGVEL, SPIN, SPIN/NUTATION, or SPIN/NUTATION_MOM",
3051            }),
3052        }
3053    }
3054}
3055
3056impl std::fmt::Display for AttitudeTypeType {
3057    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3058        let value = match self {
3059            Self::Quaternion | Self::QuaternionUpper => "QUATERNION",
3060            Self::QuaternionDerivative | Self::QuaternionDerivativeUpper => "QUATERNION/DERIVATIVE",
3061            Self::QuaternionAngVel | Self::QuaternionAngVelUpper => "QUATERNION/ANGVEL",
3062            Self::EulerAngle | Self::EulerAngleUpper => "EULER_ANGLE",
3063            Self::EulerAngleDerivative | Self::EulerAngleDerivativeUpper => {
3064                "EULER_ANGLE/DERIVATIVE"
3065            }
3066            Self::EulerAngleAngVel | Self::EulerAngleAngVelUpper => "EULER_ANGLE/ANGVEL",
3067            Self::Spin | Self::SpinUpper => "SPIN",
3068            Self::SpinNutation | Self::SpinNutationUpper => "SPIN/NUTATION",
3069            Self::SpinNutationMom | Self::SpinNutationMomUpper => "SPIN/NUTATION_MOM",
3070        };
3071        write!(f, "{}", value)
3072    }
3073}
3074
3075// APM rate frame
3076#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
3077pub enum ApmRateFrame {
3078    #[serde(rename = "EULER_FRAME_A")]
3079    EulerFrameA,
3080    #[serde(rename = "EULER_FRAME_B")]
3081    EulerFrameB,
3082}
3083
3084// SigmaU / SigmaV units and types
3085define_unit_enum!(SigmaUUnits, DegPerS15, { DegPerS15 => "deg/s**1.5" });
3086pub type SigmaU = UnitValue<f64, SigmaUUnits>;
3087
3088define_unit_enum!(SigmaVUnits, DegPerS05, { DegPerS05 => "deg/s**0.5" });
3089pub type SigmaV = UnitValue<f64, SigmaVUnits>;
3090
3091// Sensor noise (string with optional angle units)
3092#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
3093pub struct SensorNoise {
3094    #[serde(rename = "$value", default, with = "crate::utils::vec_f64_space_sep")]
3095    pub values: Vec<f64>,
3096    #[serde(rename = "@units", default, skip_serializing_if = "Option::is_none")]
3097    pub units: Option<AngleUnits>,
3098}
3099
3100/// Re-entry disintegration type.
3101#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
3102pub enum DisintegrationType {
3103    /// No disintegration considered.
3104    #[serde(rename = "NONE")]
3105    None,
3106    /// Mass loss considered.
3107    #[serde(rename = "MASS-LOSS")]
3108    MassLoss,
3109    /// Break-up considered.
3110    #[serde(rename = "BREAK-UP")]
3111    BreakUp,
3112    /// Both mass loss and break-up considered.
3113    #[serde(rename = "MASS-LOSS + BREAK-UP", alias = "MASS-LOSS + BREAKUP")]
3114    MassLossAndBreakUp,
3115}
3116
3117impl std::str::FromStr for DisintegrationType {
3118    type Err = crate::error::EnumParseError;
3119    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
3120        match s {
3121            "NONE" => Ok(Self::None),
3122            "MASS-LOSS" => Ok(Self::MassLoss),
3123            "BREAK-UP" => Ok(Self::BreakUp),
3124            "MASS-LOSS + BREAK-UP" | "MASS-LOSS + BREAKUP" => Ok(Self::MassLossAndBreakUp),
3125            _ => Err(crate::error::EnumParseError {
3126                field: "REENTRY_DISINTEGRATION",
3127                value: s.to_string(),
3128                expected: "NONE, MASS-LOSS, BREAK-UP, or MASS-LOSS + BREAK-UP",
3129            }),
3130        }
3131    }
3132}
3133
3134impl std::fmt::Display for DisintegrationType {
3135    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3136        match self {
3137            Self::None => write!(f, "NONE"),
3138            Self::MassLoss => write!(f, "MASS-LOSS"),
3139            Self::BreakUp => write!(f, "BREAK-UP"),
3140            Self::MassLossAndBreakUp => write!(f, "MASS-LOSS + BREAK-UP"),
3141        }
3142    }
3143}
3144
3145/// Impact uncertainty method.
3146#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
3147pub enum ImpactUncertaintyType {
3148    /// No uncertainty method.
3149    #[serde(rename = "NONE")]
3150    None,
3151    /// Analytical uncertainty method.
3152    #[serde(rename = "ANALYTICAL")]
3153    Analytical,
3154    /// Stochastic uncertainty method.
3155    #[serde(rename = "STOCHASTIC")]
3156    Stochastic,
3157    /// Empirical uncertainty method.
3158    #[serde(rename = "EMPIRICAL")]
3159    Empirical,
3160    /// Covariance uncertainty method.
3161    #[serde(rename = "COVARIANCE")]
3162    Covariance,
3163    /// Statistical uncertainty method.
3164    #[serde(rename = "STATISTICAL")]
3165    Statistical,
3166}
3167
3168impl std::str::FromStr for ImpactUncertaintyType {
3169    type Err = crate::error::EnumParseError;
3170    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
3171        match s {
3172            "NONE" => Ok(Self::None),
3173            "ANALYTICAL" => Ok(Self::Analytical),
3174            "STOCHASTIC" => Ok(Self::Stochastic),
3175            "EMPIRICAL" => Ok(Self::Empirical),
3176            "COVARIANCE" => Ok(Self::Covariance),
3177            "STATISTICAL" => Ok(Self::Statistical),
3178            _ => Err(crate::error::EnumParseError {
3179                field: "IMPACT_UNCERTAINTY_METHOD",
3180                value: s.to_string(),
3181                expected: "NONE, ANALYTICAL, STOCHASTIC, EMPIRICAL, COVARIANCE, or STATISTICAL",
3182            }),
3183        }
3184    }
3185}
3186
3187impl std::fmt::Display for ImpactUncertaintyType {
3188    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3189        match self {
3190            Self::None => write!(f, "NONE"),
3191            Self::Analytical => write!(f, "ANALYTICAL"),
3192            Self::Stochastic => write!(f, "STOCHASTIC"),
3193            Self::Empirical => write!(f, "EMPIRICAL"),
3194            Self::Covariance => write!(f, "COVARIANCE"),
3195            Self::Statistical => write!(f, "STATISTICAL"),
3196        }
3197    }
3198}
3199
3200/// Re-entry uncertainty method.
3201#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
3202pub enum ReentryUncertaintyMethodType {
3203    /// No uncertainty method.
3204    #[serde(rename = "NONE")]
3205    None,
3206    /// Analytical uncertainty method.
3207    #[serde(rename = "ANALYTICAL")]
3208    Analytical,
3209    /// Stochastic uncertainty method.
3210    #[serde(rename = "STOCHASTIC")]
3211    Stochastic,
3212    /// Empirical uncertainty method.
3213    #[serde(rename = "EMPIRICAL")]
3214    Empirical,
3215    /// Covariance uncertainty method.
3216    #[serde(rename = "COVARIANCE")]
3217    Covariance,
3218    /// Statistical uncertainty method.
3219    #[serde(rename = "STATISTICAL")]
3220    Statistical,
3221}
3222
3223impl std::str::FromStr for ReentryUncertaintyMethodType {
3224    type Err = crate::error::EnumParseError;
3225    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
3226        match s {
3227            "NONE" => Ok(Self::None),
3228            "ANALYTICAL" => Ok(Self::Analytical),
3229            "STOCHASTIC" => Ok(Self::Stochastic),
3230            "EMPIRICAL" => Ok(Self::Empirical),
3231            "COVARIANCE" => Ok(Self::Covariance),
3232            "STATISTICAL" => Ok(Self::Statistical),
3233            _ => Err(crate::error::EnumParseError {
3234                field: "REENTRY_UNCERTAINTY_METHOD",
3235                value: s.to_string(),
3236                expected: "NONE, ANALYTICAL, STOCHASTIC, EMPIRICAL, COVARIANCE, or STATISTICAL",
3237            }),
3238        }
3239    }
3240}
3241
3242impl std::fmt::Display for ReentryUncertaintyMethodType {
3243    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3244        match self {
3245            Self::None => write!(f, "NONE"),
3246            Self::Analytical => write!(f, "ANALYTICAL"),
3247            Self::Stochastic => write!(f, "STOCHASTIC"),
3248            Self::Empirical => write!(f, "EMPIRICAL"),
3249            Self::Covariance => write!(f, "COVARIANCE"),
3250            Self::Statistical => write!(f, "STATISTICAL"),
3251        }
3252    }
3253}
3254
3255// TimeSystemType: XSD has empty restriction; represent as a string newtype.
3256/// Time system string constrained externally by schema usage (e.g., TDB, UTC).
3257#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
3258pub struct TimeSystemType(pub String);
3259
3260impl std::fmt::Display for TimeSystemType {
3261    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3262        write!(f, "{}", self.0)
3263    }
3264}
3265
3266// AngVelFrameType: XSD empty restriction (free-form string), used in APM angVelStateType.
3267/// Angular velocity frame identifier (schema leaves unrestricted).
3268#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)]
3269pub struct AngVelFrameType(pub String);
3270
3271impl std::str::FromStr for AngVelFrameType {
3272    type Err = std::convert::Infallible;
3273    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
3274        Ok(Self(s.to_string()))
3275    }
3276}
3277
3278impl std::fmt::Display for AngVelFrameType {
3279    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3280        write!(f, "{}", self.0)
3281    }
3282}
3283
3284/// USER DEFINED PARAMETERS block (`userDefinedType`).
3285/// User-defined parameters.
3286///
3287/// Allow for the exchange of any desired orbital data not already provided in the message.
3288///
3289/// **CCSDS Reference**: 502.0-B-3, Section 3.2.4 (OPM), Section 4.2.4 (OMM), Section 6.2.9 (OCM).
3290#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)]
3291#[serde(rename_all = "camelCase")]
3292pub struct UserDefined {
3293    /// Comments (see 7.8 for formatting rules).
3294    ///
3295    /// **CCSDS Reference**: 502.0-B-3, Section 6.2.9.
3296    #[serde(rename = "COMMENT", default, skip_serializing_if = "Vec::is_empty")]
3297    pub comment: Vec<String>,
3298    /// List of user-defined parameters.
3299    #[serde(
3300        rename = "USER_DEFINED",
3301        default,
3302        skip_serializing_if = "Vec::is_empty"
3303    )]
3304    pub user_defined: Vec<UserDefinedParameter>,
3305}
3306
3307/// Single USER_DEFINED parameter.
3308///
3309/// **CCSDS Reference**: 502.0-B-3, Section 6.2.9.
3310#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
3311pub struct UserDefinedParameter {
3312    /// Value of the user-defined parameter.
3313    #[serde(rename = "$value", default)]
3314    pub value: String,
3315    /// Name of the user-defined parameter.
3316    #[serde(rename = "@parameter")]
3317    pub parameter: String,
3318}
3319
3320// -------------------- CDM TYPES --------------------
3321
3322// Velocity delta-v units (m/s) and type (`dvType`)
3323define_required_unit_type!(Dv, DvUnits, MPerS, { MPerS => "m/s" });
3324
3325// m**2 units and type (`m2Type`)
3326define_required_unit_type!(M2, M2Units, M2, { M2 => "m**2" });
3327
3328// m**2/s units and type (`m2sType`)
3329define_required_unit_type!(M2s, M2sUnits, M2PerS, { M2PerS => "m**2/s" });
3330
3331// m**2/s**2 units and type (`m2s2Type`)
3332define_required_unit_type!(M2s2, M2s2Units, M2PerS2, { M2PerS2 => "m**2/s**2" });
3333
3334// m**3/kg units and type (`m3kgType`)
3335define_required_unit_type!(M3kg, M3kgUnits, M3PerKg, { M3PerKg => "m**3/kg" });
3336
3337// m**3/(kg*s) units and type (`m3kgsType`)
3338define_required_unit_type!(M3kgs, M3kgsUnits, M3PerKgS, { M3PerKgS => "m**3/(kg*s)" });
3339
3340// m**4/kg**2 units and type (`m4kg2Type`)
3341define_required_unit_type!(M4kg2, M4kg2Units, M4PerKg2, { M4PerKg2 => "m**4/kg**2" });
3342
3343// m**2/s**3 units and type (`m2s3Type`)
3344define_required_unit_type!(M2s3, M2s3Units, M2PerS3, { M2PerS3 => "m**2/s**3" });
3345
3346// m**3/(kg*s**2) units and type (`m3kgs2Type`)
3347define_required_unit_type!(M3kgs2, M3kgs2Units, M3PerKgS2, { M3PerKgS2 => "m**3/(kg*s**2)" });
3348
3349// m**2/s**4 units and type (`m2s4Type`)
3350define_required_unit_type!(M2s4, M2s4Units, M2PerS4, { M2PerS4 => "m**2/s**4" });
3351
3352// m**2/kg units and type (`m2kgType`)
3353define_unit_type!(M2kg, M2kgUnits, M2PerKg, { M2PerKg => "m**2/kg" });
3354define_required_type!(M2kgRequired, M2kgUnits, M2PerKg);
3355
3356// CDM categorical simple types
3357/// CDM Object type (OBJECT1 or OBJECT2).
3358#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
3359pub enum CdmObjectType {
3360    /// The object to which the metadata and data apply is OBJECT1.
3361    #[serde(rename = "OBJECT1")]
3362    Object1,
3363    /// The object to which the metadata and data apply is OBJECT2.
3364    #[serde(rename = "OBJECT2")]
3365    Object2,
3366}
3367
3368impl std::str::FromStr for CdmObjectType {
3369    type Err = crate::error::EnumParseError;
3370    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
3371        match s.to_uppercase().as_str() {
3372            "OBJECT1" => Ok(Self::Object1),
3373            "OBJECT2" => Ok(Self::Object2),
3374            _ => Err(crate::error::EnumParseError {
3375                field: "OBJECT",
3376                value: s.to_string(),
3377                expected: "OBJECT1 or OBJECT2",
3378            }),
3379        }
3380    }
3381}
3382
3383/// Screening volume frame type.
3384#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
3385pub enum ScreenVolumeFrameType {
3386    /// Radial, Transverse, and Normal (RTN) coordinate frame.
3387    #[serde(rename = "RTN")]
3388    Rtn,
3389    /// Transverse, Velocity, and Normal (TVN) coordinate frame.
3390    #[serde(rename = "TVN")]
3391    Tvn,
3392}
3393
3394impl std::fmt::Display for ScreenVolumeFrameType {
3395    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3396        match self {
3397            Self::Rtn => write!(f, "RTN"),
3398            Self::Tvn => write!(f, "TVN"),
3399        }
3400    }
3401}
3402
3403impl std::str::FromStr for ScreenVolumeFrameType {
3404    type Err = crate::error::EnumParseError;
3405    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
3406        match s.to_uppercase().as_str() {
3407            "RTN" => Ok(Self::Rtn),
3408            "TVN" => Ok(Self::Tvn),
3409            _ => Err(crate::error::EnumParseError {
3410                field: "SCREEN_VOLUME_FRAME",
3411                value: s.to_string(),
3412                expected: "RTN or TVN",
3413            }),
3414        }
3415    }
3416}
3417
3418/// Screening volume shape type.
3419#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
3420pub enum ScreenVolumeShapeType {
3421    /// Ellipsoid screening volume.
3422    #[serde(rename = "ELLIPSOID")]
3423    Ellipsoid,
3424    /// Box screening volume.
3425    #[serde(rename = "BOX")]
3426    Box,
3427}
3428
3429impl std::fmt::Display for ScreenVolumeShapeType {
3430    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3431        match self {
3432            Self::Ellipsoid => write!(f, "ELLIPSOID"),
3433            Self::Box => write!(f, "BOX"),
3434        }
3435    }
3436}
3437
3438impl std::str::FromStr for ScreenVolumeShapeType {
3439    type Err = crate::error::EnumParseError;
3440    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
3441        match s.to_uppercase().as_str() {
3442            "ELLIPSOID" => Ok(Self::Ellipsoid),
3443            "BOX" => Ok(Self::Box),
3444            _ => Err(crate::error::EnumParseError {
3445                field: "SCREEN_VOLUME_SHAPE",
3446                value: s.to_string(),
3447                expected: "ELLIPSOID or BOX",
3448            }),
3449        }
3450    }
3451}
3452
3453/// CDM reference frame type.
3454#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
3455pub enum ReferenceFrameType {
3456    /// Geocentric Celestial Reference Frame.
3457    #[serde(rename = "GCRF")]
3458    Gcrf,
3459    /// Earth Mean Equinox and Equator of J2000.
3460    #[serde(rename = "EME2000")]
3461    Eme2000,
3462    /// International Terrestrial Reference Frame.
3463    #[serde(rename = "ITRF")]
3464    Itrf,
3465}
3466
3467impl std::fmt::Display for ReferenceFrameType {
3468    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3469        match self {
3470            Self::Gcrf => write!(f, "GCRF"),
3471            Self::Eme2000 => write!(f, "EME2000"),
3472            Self::Itrf => write!(f, "ITRF"),
3473        }
3474    }
3475}
3476
3477impl std::str::FromStr for ReferenceFrameType {
3478    type Err = crate::error::EnumParseError;
3479    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
3480        match s.to_uppercase().as_str() {
3481            "GCRF" => Ok(Self::Gcrf),
3482            "EME2000" => Ok(Self::Eme2000),
3483            "ITRF" => Ok(Self::Itrf),
3484            _ => Err(crate::error::EnumParseError {
3485                field: "REF_FRAME",
3486                value: s.to_string(),
3487                expected: "GCRF, EME2000, or ITRF",
3488            }),
3489        }
3490    }
3491}
3492
3493/// Covariance method type.
3494#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
3495pub enum CovarianceMethodType {
3496    /// Covariance was calculated during the OD.
3497    #[serde(rename = "CALCULATED")]
3498    Calculated,
3499    /// An arbitrary, non-calculated default value was used.
3500    #[serde(rename = "DEFAULT")]
3501    Default,
3502}
3503
3504impl std::fmt::Display for CovarianceMethodType {
3505    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3506        match self {
3507            Self::Calculated => write!(f, "CALCULATED"),
3508            Self::Default => write!(f, "DEFAULT"),
3509        }
3510    }
3511}
3512
3513impl std::str::FromStr for CovarianceMethodType {
3514    type Err = crate::error::EnumParseError;
3515    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
3516        match s.to_uppercase().as_str() {
3517            "CALCULATED" => Ok(Self::Calculated),
3518            "DEFAULT" => Ok(Self::Default),
3519            _ => Err(crate::error::EnumParseError {
3520                field: "COVARIANCE_METHOD",
3521                value: s.to_string(),
3522                expected: "CALCULATED or DEFAULT",
3523            }),
3524        }
3525    }
3526}
3527
3528/// Maneuverable type.
3529#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
3530pub enum ManeuverableType {
3531    /// Object is maneuverable.
3532    #[serde(rename = "YES")]
3533    Yes,
3534    /// Object is not maneuverable.
3535    #[serde(rename = "NO")]
3536    No,
3537    /// Maneuverability is not applicable or unknown.
3538    #[serde(rename = "N/A")]
3539    NA,
3540}
3541
3542impl std::fmt::Display for ManeuverableType {
3543    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3544        match self {
3545            Self::Yes => write!(f, "YES"),
3546            Self::No => write!(f, "NO"),
3547            Self::NA => write!(f, "N/A"),
3548        }
3549    }
3550}
3551
3552impl std::str::FromStr for ManeuverableType {
3553    type Err = crate::error::EnumParseError;
3554    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
3555        match s.to_uppercase().as_str() {
3556            "YES" => Ok(Self::Yes),
3557            "NO" => Ok(Self::No),
3558            "N/A" => Ok(Self::NA),
3559            _ => Err(crate::error::EnumParseError {
3560                field: "MANEUVERABLE",
3561                value: s.to_string(),
3562                expected: "YES, NO, or N/A",
3563            }),
3564        }
3565    }
3566}
3567
3568//----------------------------------------------------------------------
3569// Vector Types
3570//----------------------------------------------------------------------
3571
3572/// A 3-element vector of doubles (XSD vec3Double)
3573#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
3574pub struct Vec3Double {
3575    pub x: f64,
3576    pub y: f64,
3577    pub z: f64,
3578}
3579
3580impl Vec3Double {
3581    pub fn new(x: f64, y: f64, z: f64) -> Self {
3582        Self { x, y, z }
3583    }
3584}
3585
3586impl FromKvnValue for Vec3Double {
3587    fn from_kvn_value(val: &str) -> Result<Self> {
3588        let parts: Vec<&str> = val.split_whitespace().collect();
3589        if parts.len() != 3 {
3590            return Err(crate::error::FormatError::InvalidFormat(format!(
3591                "Vec3Double requires 3 values, got {}: {}",
3592                parts.len(),
3593                val
3594            ))
3595            .into());
3596        }
3597        let x = fast_float::parse(parts[0]).map_err(|_| {
3598            CcsdsNdmError::Validation(Box::new(crate::error::ValidationError::Generic {
3599                message: "Invalid X component".into(),
3600                line: None,
3601            }))
3602        })?;
3603        let y = fast_float::parse(parts[1]).map_err(|_| {
3604            CcsdsNdmError::Validation(Box::new(crate::error::ValidationError::Generic {
3605                message: "Invalid Y component".into(),
3606                line: None,
3607            }))
3608        })?;
3609        let z = fast_float::parse(parts[2]).map_err(|_| {
3610            CcsdsNdmError::Validation(Box::new(crate::error::ValidationError::Generic {
3611                message: "Invalid Z component".into(),
3612                line: None,
3613            }))
3614        })?;
3615        Ok(Self { x, y, z })
3616    }
3617}
3618
3619impl std::fmt::Display for Vec3Double {
3620    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3621        write!(f, "{} {} {}", self.x, self.y, self.z)
3622    }
3623}
3624
3625/// A 4-element vector of doubles (XSD vec4Double)
3626#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
3627pub struct Vec4Double {
3628    #[serde(rename = "$value", with = "crate::utils::vec_f64_space_sep")]
3629    pub values: Vec<f64>,
3630}
3631
3632impl Vec4Double {
3633    pub fn new(a: f64, b: f64, c: f64, d: f64) -> Self {
3634        Self {
3635            values: vec![a, b, c, d],
3636        }
3637    }
3638}
3639
3640impl FromKvnValue for Vec4Double {
3641    fn from_kvn_value(val: &str) -> Result<Self> {
3642        let parts: Vec<&str> = val.split_whitespace().collect();
3643        if parts.len() != 4 {
3644            return Err(crate::error::FormatError::InvalidFormat(format!(
3645                "Vec4Double requires 4 values, got {}: {}",
3646                parts.len(),
3647                val
3648            ))
3649            .into());
3650        }
3651        let mut values = Vec::with_capacity(4);
3652        for p in parts {
3653            let v = fast_float::parse(p).map_err(|_| {
3654                crate::error::FormatError::InvalidFormat(format!(
3655                    "Vec4Double value parse failed: {}",
3656                    p
3657                ))
3658            })?;
3659            values.push(v);
3660        }
3661        Ok(Self { values })
3662    }
3663}
3664
3665impl std::fmt::Display for Vec4Double {
3666    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3667        let mut iter = self.values.iter();
3668        if let Some(first) = iter.next() {
3669            write!(f, "{}", first)?;
3670        }
3671        for v in iter {
3672            write!(f, " {}", v)?;
3673        }
3674        Ok(())
3675    }
3676}
3677
3678// -------------------- TDM TYPES --------------------
3679
3680/// TDM angle type.
3681#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
3682pub enum TdmAngleType {
3683    /// Azimuth, elevation (local horizontal).
3684    #[serde(rename = "AZEL")]
3685    Azel,
3686    /// Right ascension, declination or hour angle, declination (must be referenced to an
3687    /// inertial frame).
3688    #[serde(rename = "RADEC")]
3689    Radec,
3690    /// x-east, y-north.
3691    #[serde(rename = "XEYN")]
3692    Xeyn,
3693    /// x-south, y-east.
3694    #[serde(rename = "XSYE")]
3695    Xsye,
3696}
3697
3698impl std::str::FromStr for TdmAngleType {
3699    type Err = crate::error::EnumParseError;
3700    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
3701        match s.to_uppercase().as_str() {
3702            "AZEL" => Ok(Self::Azel),
3703            "RADEC" => Ok(Self::Radec),
3704            "XEYN" => Ok(Self::Xeyn),
3705            "XSYE" => Ok(Self::Xsye),
3706            _ => Err(crate::error::EnumParseError {
3707                field: "ANGLE_TYPE",
3708                value: s.to_string(),
3709                expected: "AZEL, RADEC, XEYN, or XSYE",
3710            }),
3711        }
3712    }
3713}
3714
3715impl std::fmt::Display for TdmAngleType {
3716    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3717        match self {
3718            Self::Azel => write!(f, "AZEL"),
3719            Self::Radec => write!(f, "RADEC"),
3720            Self::Xeyn => write!(f, "XEYN"),
3721            Self::Xsye => write!(f, "XSYE"),
3722        }
3723    }
3724}
3725
3726/// TDM data quality.
3727#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
3728pub enum TdmDataQuality {
3729    /// No quality check of the data has occurred.
3730    #[serde(rename = "RAW")]
3731    Raw,
3732    /// Data quality has been checked, and passed tests.
3733    #[serde(rename = "VALIDATED")]
3734    Validated,
3735    /// Data quality has been checked and quality issues exist.
3736    #[serde(rename = "DEGRADED")]
3737    Degraded,
3738}
3739
3740impl std::str::FromStr for TdmDataQuality {
3741    type Err = crate::error::EnumParseError;
3742    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
3743        match s.to_uppercase().as_str() {
3744            "RAW" => Ok(Self::Raw),
3745            "VALIDATED" => Ok(Self::Validated),
3746            "DEGRADED" => Ok(Self::Degraded),
3747            _ => Err(crate::error::EnumParseError {
3748                field: "DATA_QUALITY",
3749                value: s.to_string(),
3750                expected: "RAW, VALIDATED, or DEGRADED",
3751            }),
3752        }
3753    }
3754}
3755
3756impl std::fmt::Display for TdmDataQuality {
3757    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3758        match self {
3759            Self::Raw => write!(f, "RAW"),
3760            Self::Validated => write!(f, "VALIDATED"),
3761            Self::Degraded => write!(f, "DEGRADED"),
3762        }
3763    }
3764}
3765
3766/// Indicates the relationship between the INTEGRATION_INTERVAL and the timetag.
3767#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
3768pub enum TdmIntegrationRef {
3769    /// Timetag represents the start of the integration period.
3770    #[serde(rename = "START")]
3771    Start,
3772    /// Timetag represents the middle of the integration period.
3773    #[serde(rename = "MIDDLE")]
3774    Middle,
3775    /// Timetag represents the end of the integration period.
3776    #[serde(rename = "END")]
3777    End,
3778}
3779
3780impl std::str::FromStr for TdmIntegrationRef {
3781    type Err = crate::error::EnumParseError;
3782    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
3783        match s.to_uppercase().as_str() {
3784            "START" => Ok(Self::Start),
3785            "MIDDLE" => Ok(Self::Middle),
3786            "END" => Ok(Self::End),
3787            _ => Err(crate::error::EnumParseError {
3788                field: "INTEGRATION_REF",
3789                value: s.to_string(),
3790                expected: "START, MIDDLE, or END",
3791            }),
3792        }
3793    }
3794}
3795
3796impl std::fmt::Display for TdmIntegrationRef {
3797    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3798        match self {
3799            Self::Start => write!(f, "START"),
3800            Self::Middle => write!(f, "MIDDLE"),
3801            Self::End => write!(f, "END"),
3802        }
3803    }
3804}
3805
3806/// TDM tracking mode.
3807#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
3808pub enum TdmMode {
3809    /// The value ‘SEQUENTIAL’ applies for frequencies, phase, range, Doppler, carrier power,
3810    /// carrier-power-to-noise spectral density, ranging-power-to-noise spectral density,
3811    /// optical, angles, and line-of-sight ionosphere calibrations; the name implies a
3812    /// sequential signal path between tracking participants.
3813    #[serde(rename = "SEQUENTIAL")]
3814    Sequential,
3815    /// The value ‘SINGLE_DIFF’ applies only for differenced data.
3816    #[serde(rename = "SINGLE_DIFF")]
3817    SingleDiff,
3818}
3819
3820impl std::str::FromStr for TdmMode {
3821    type Err = crate::error::EnumParseError;
3822    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
3823        match s.to_uppercase().as_str() {
3824            "SEQUENTIAL" => Ok(Self::Sequential),
3825            "SINGLE_DIFF" => Ok(Self::SingleDiff),
3826            _ => Err(crate::error::EnumParseError {
3827                field: "MODE",
3828                value: s.to_string(),
3829                expected: "SEQUENTIAL or SINGLE_DIFF",
3830            }),
3831        }
3832    }
3833}
3834
3835impl std::fmt::Display for TdmMode {
3836    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3837        match self {
3838            Self::Sequential => write!(f, "SEQUENTIAL"),
3839            Self::SingleDiff => write!(f, "SINGLE_DIFF"),
3840        }
3841    }
3842}
3843
3844/// TDM range mode.
3845#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
3846pub enum TdmRangeMode {
3847    /// Range tones are coherent with the uplink carrier.
3848    #[serde(rename = "COHERENT")]
3849    Coherent,
3850    /// Range tones have a constant frequency.
3851    #[serde(rename = "CONSTANT")]
3852    Constant,
3853    /// Used in Delta-DOR.
3854    #[serde(rename = "ONE_WAY")]
3855    OneWay,
3856}
3857
3858impl std::str::FromStr for TdmRangeMode {
3859    type Err = crate::error::EnumParseError;
3860    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
3861        match s.to_uppercase().as_str() {
3862            "COHERENT" => Ok(Self::Coherent),
3863            "CONSTANT" => Ok(Self::Constant),
3864            "ONE_WAY" => Ok(Self::OneWay),
3865            _ => Err(crate::error::EnumParseError {
3866                field: "RANGE_MODE",
3867                value: s.to_string(),
3868                expected: "COHERENT, CONSTANT, or ONE_WAY",
3869            }),
3870        }
3871    }
3872}
3873
3874impl std::fmt::Display for TdmRangeMode {
3875    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3876        match self {
3877            Self::Coherent => write!(f, "COHERENT"),
3878            Self::Constant => write!(f, "CONSTANT"),
3879            Self::OneWay => write!(f, "ONE_WAY"),
3880        }
3881    }
3882}
3883
3884/// TDM range units.
3885#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
3886pub enum TdmRangeUnits {
3887    /// Range is measured in kilometers.
3888    #[serde(rename = "km")]
3889    Km,
3890    /// Range is measured in seconds.
3891    #[serde(rename = "s")]
3892    Seconds,
3893    /// Range units where the transmit frequency is changing.
3894    #[serde(rename = "RU")]
3895    Ru,
3896}
3897
3898impl std::str::FromStr for TdmRangeUnits {
3899    type Err = crate::error::EnumParseError;
3900    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
3901        match s.to_lowercase().as_str() {
3902            "km" => Ok(Self::Km),
3903            "s" => Ok(Self::Seconds),
3904            "ru" => Ok(Self::Ru),
3905            _ => Err(crate::error::EnumParseError {
3906                field: "RANGE_UNITS",
3907                value: s.to_string(),
3908                expected: "km, s, or ru",
3909            }),
3910        }
3911    }
3912}
3913
3914impl std::fmt::Display for TdmRangeUnits {
3915    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3916        match self {
3917            Self::Km => write!(f, "km"),
3918            Self::Seconds => write!(f, "s"),
3919            Self::Ru => write!(f, "ru"),
3920        }
3921    }
3922}
3923
3924#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
3925pub enum TdmReferenceFrame {
3926    #[serde(rename = "EME2000")]
3927    Eme2000,
3928    #[serde(rename = "ICRF")]
3929    Icrf,
3930    #[serde(rename = "ITRF2000")]
3931    Itrf2000,
3932    #[serde(rename = "ITRF-93")]
3933    Itrf93,
3934    #[serde(rename = "ITRF-97")]
3935    Itrf97,
3936    #[serde(rename = "TOD")]
3937    Tod,
3938}
3939
3940impl std::str::FromStr for TdmReferenceFrame {
3941    type Err = crate::error::EnumParseError;
3942    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
3943        match s.to_uppercase().as_str() {
3944            "EME2000" => Ok(Self::Eme2000),
3945            "ICRF" => Ok(Self::Icrf),
3946            "ITRF2000" | "ITRF-2000" => Ok(Self::Itrf2000),
3947            "ITRF-93" | "ITRF1993" | "ITRF93" => Ok(Self::Itrf93),
3948            "ITRF-97" => Ok(Self::Itrf97),
3949            "TOD" | "TOD_EARTH" => Ok(Self::Tod),
3950            _ => Err(crate::error::EnumParseError {
3951                field: "REFERENCE_FRAME",
3952                value: s.to_string(),
3953                expected: "EME2000, ICRF, ITRF2000, ITRF-93, ITRF-97, or TOD",
3954            }),
3955        }
3956    }
3957}
3958
3959impl std::fmt::Display for TdmReferenceFrame {
3960    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3961        match self {
3962            Self::Eme2000 => write!(f, "EME2000"),
3963            Self::Icrf => write!(f, "ICRF"),
3964            Self::Itrf2000 => write!(f, "ITRF2000"),
3965            Self::Itrf93 => write!(f, "ITRF-93"),
3966            Self::Itrf97 => write!(f, "ITRF-97"),
3967            Self::Tod => write!(f, "TOD"),
3968        }
3969    }
3970}
3971
3972/// Reference for time tags in the tracking data.
3973#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
3974pub enum TdmTimetagRef {
3975    /// Timetag is the transmit time.
3976    #[serde(rename = "TRANSMIT")]
3977    Transmit,
3978    /// Timetag is the receive time.
3979    #[serde(rename = "RECEIVE")]
3980    Receive,
3981}
3982
3983impl std::str::FromStr for TdmTimetagRef {
3984    type Err = crate::error::EnumParseError;
3985    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
3986        match s.to_uppercase().as_str() {
3987            "TRANSMIT" => Ok(Self::Transmit),
3988            "RECEIVE" => Ok(Self::Receive),
3989            _ => Err(crate::error::EnumParseError {
3990                field: "TIMETAG_REF",
3991                value: s.to_string(),
3992                expected: "TRANSMIT or RECEIVE",
3993            }),
3994        }
3995    }
3996}
3997
3998impl std::fmt::Display for TdmTimetagRef {
3999    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4000        match self {
4001            Self::Transmit => write!(f, "TRANSMIT"),
4002            Self::Receive => write!(f, "RECEIVE"),
4003        }
4004    }
4005}
4006
4007/// Represents the signal path in a TDM (e.g., "1,2,1").
4008#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
4009#[serde(transparent)]
4010pub struct TdmPath(pub String);
4011
4012impl std::str::FromStr for TdmPath {
4013    type Err = crate::error::ValidationError;
4014    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
4015        // Simple regex-like validation: \d{1},\d{1}(,\d{1})*
4016        let parts: Vec<&str> = s.split(',').collect();
4017        if parts.len() < 2 {
4018            return Err(crate::error::ValidationError::InvalidValue {
4019                field: "PATH".into(),
4020                value: s.to_string(),
4021                expected: "at least two participants (e.g., 1,2)".into(),
4022                line: None,
4023            });
4024        }
4025        for part in &parts {
4026            if part.len() != 1 || !part.chars().next().unwrap().is_ascii_digit() {
4027                return Err(crate::error::ValidationError::InvalidValue {
4028                    field: "PATH".into(),
4029                    value: s.to_string(),
4030                    expected: "single digit participant indices separated by commas".into(),
4031                    line: None,
4032                });
4033            }
4034            let idx = part.parse::<u8>().unwrap();
4035            if !(1..=5).contains(&idx) {
4036                return Err(crate::error::ValidationError::InvalidValue {
4037                    field: "PATH".into(),
4038                    value: s.to_string(),
4039                    expected: "participant indices in range 1..5".into(),
4040                    line: None,
4041                });
4042            }
4043        }
4044        Ok(Self(s.to_string()))
4045    }
4046}
4047
4048impl std::fmt::Display for TdmPath {
4049    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4050        write!(f, "{}", self.0)
4051    }
4052}
4053
4054#[cfg(test)]
4055mod tests {
4056    use super::*;
4057
4058    #[test]
4059    fn test_non_negative_double() {
4060        assert!(NonNegativeDouble::new(0.0).is_ok());
4061        assert!(NonNegativeDouble::new(1.0).is_ok());
4062        assert!(NonNegativeDouble::new(-0.1).is_err());
4063    }
4064
4065    #[test]
4066    fn test_positive_integer() {
4067        assert!(PositiveInteger::new(1).is_ok());
4068        assert!(PositiveInteger::new(100).is_ok());
4069        assert!(PositiveInteger::new(0).is_err());
4070    }
4071
4072    #[test]
4073    fn test_element_set_no() {
4074        assert!(ElementSetNo::new(0).is_ok());
4075        assert!(ElementSetNo::new(9999).is_ok());
4076        assert!(ElementSetNo::new(10000).is_err());
4077    }
4078
4079    #[test]
4080    fn test_epoch_xsd_compliance() {
4081        // Valid calendar/ordinal formats
4082        assert!(Epoch::new("2023-11-13T12:00:00").is_ok());
4083        assert!(Epoch::new("2023-11-13T12:00:00Z").is_ok());
4084        assert!(Epoch::new("2023-11-13T12:00:00.123Z").is_ok());
4085        assert!(Epoch::new("2023-317T12:00:00Z").is_ok()); // Ordinal day
4086        assert!(Epoch::new("2023-11-13T12:00:00+05:00").is_ok());
4087        assert!(Epoch::new("2023-11-13T12:00:00-05:00").is_ok());
4088        assert!(Epoch::new("-2023-11-13T12:00:00Z").is_ok()); // Negative year
4089
4090        // Valid numeric formats
4091        assert!(Epoch::new("12345.678").is_ok());
4092        assert!(Epoch::new("+12345.678").is_ok());
4093        assert!(Epoch::new("-12345.678").is_ok());
4094        assert!(Epoch::new(".678").is_ok());
4095        assert!(Epoch::new("12345.").is_ok());
4096        assert!(Epoch::new("12345").is_ok());
4097        assert!(Epoch::new("+").is_ok()); // Technically valid according to XSD [+-]?\d*(\.\d*)?
4098        assert!(Epoch::new("-").is_ok()); // Technically valid according to XSD
4099        assert!(Epoch::new(".").is_ok()); // Technically valid according to XSD
4100
4101        // Empty string
4102        assert!(Epoch::new("").is_ok());
4103
4104        // Invalid formats
4105        assert!(Epoch::new("2023-11-13").is_err()); // Missing time
4106        assert!(Epoch::new("2023-11-13T12:00").is_err()); // Missing seconds
4107        assert!(Epoch::new("2023-11-13T12:00:00Z+05:00").is_err()); // Double TZ
4108        assert!(Epoch::new("not-a-date").is_err());
4109    }
4110
4111    #[test]
4112    fn test_epoch_length_limit() {
4113        let long_epoch = "A".repeat(65);
4114        assert!(Epoch::new(&long_epoch).is_err());
4115        let _max_epoch = "A".repeat(64);
4116        // "A" is not a valid epoch format, so it should fail anyway, but let's test length check
4117        // We can use numeric format for long valid epoch if needed, but 64 is huge for digits.
4118        let long_numeric = "1".repeat(64);
4119        assert!(Epoch::new(&long_numeric).is_ok());
4120        let too_long_numeric = "1".repeat(65);
4121        assert!(Epoch::new(&too_long_numeric).is_err());
4122    }
4123}
4124
4125#[cfg(test)]
4126mod extra_tests {
4127    use super::*;
4128    use std::str::FromStr;
4129
4130    #[test]
4131    fn test_vec3double_from_kvn_error() {
4132        assert!(Vec3Double::from_kvn_value("1.0 2.0").is_err()); // missing 3rd
4133        assert!(Vec3Double::from_kvn_value("1.0 2.0 3.0 4.0").is_err()); // extra
4134        assert!(Vec3Double::from_kvn_value("1.0 foo 3.0").is_err()); // invalid float
4135        assert!(Vec3Double::from_kvn_value("invalid").is_err());
4136    }
4137
4138    #[test]
4139    fn test_vec3double_display() {
4140        let v = Vec3Double::new(1.1, 2.2, 3.3);
4141        assert_eq!(format!("{}", v), "1.1 2.2 3.3");
4142    }
4143
4144    macro_rules! test_enum_from_str {
4145        ($type:ty, $valid:expr, $invalid:expr) => {
4146            // Test valid
4147            assert!($valid.parse::<$type>().is_ok());
4148            // Test invalid
4149            let res = $invalid.parse::<$type>();
4150            assert!(res.is_err());
4151            // Check error message content if possible, or just strict existence
4152            let err = res.unwrap_err();
4153            assert!(!err.to_string().is_empty());
4154        };
4155    }
4156
4157    #[test]
4158    fn test_enum_parsing_errors() {
4159        test_enum_from_str!(ReentryUncertaintyMethodType, "NONE", "INVALID");
4160        test_enum_from_str!(CdmObjectType, "OBJECT1", "INVALID");
4161        test_enum_from_str!(ScreenVolumeFrameType, "RTN", "INVALID");
4162        test_enum_from_str!(ScreenVolumeShapeType, "BOX", "INVALID");
4163        test_enum_from_str!(ReferenceFrameType, "GCRF", "INVALID");
4164        test_enum_from_str!(CovarianceMethodType, "CALCULATED", "INVALID");
4165        test_enum_from_str!(ManeuverableType, "YES", "INVALID");
4166        test_enum_from_str!(TdmAngleType, "AZEL", "INVALID");
4167        test_enum_from_str!(TdmDataQuality, "RAW", "INVALID");
4168        test_enum_from_str!(TdmIntegrationRef, "START", "INVALID");
4169        test_enum_from_str!(TdmMode, "SEQUENTIAL", "INVALID");
4170        test_enum_from_str!(TdmRangeMode, "COHERENT", "INVALID");
4171        test_enum_from_str!(TdmRangeUnits, "km", "INVALID");
4172        test_enum_from_str!(TdmReferenceFrame, "EME2000", "INVALID");
4173        test_enum_from_str!(TdmTimetagRef, "TRANSMIT", "INVALID");
4174    }
4175
4176    #[test]
4177    fn test_unit_value_from_kvn() {
4178        let uv = UnitValue::<f64, PositionUnits>::from_kvn("123.45", Some("km")).unwrap();
4179        assert_eq!(uv.value, 123.45);
4180        assert_eq!(uv.units, Some(PositionUnits::Km));
4181
4182        let uv_no_unit = UnitValue::<f64, PositionUnits>::from_kvn("123.45", None).unwrap();
4183        assert_eq!(uv_no_unit.units, None);
4184    }
4185
4186    #[test]
4187    fn test_angle_validation() {
4188        assert!(Angle::new(359.9, None).is_ok());
4189        assert!(Angle::new(-359.9, None).is_ok());
4190        assert!(Angle::new(360.0, None).is_err());
4191        assert!(Angle::new(-360.1, None).is_err());
4192    }
4193
4194    #[test]
4195    fn test_day_interval_validation() {
4196        assert!(DayInterval::new(10.0, None).is_ok());
4197        assert!(DayInterval::new(-0.1, None).is_err());
4198        assert!(DayIntervalRequired::new(0.1).is_ok());
4199        assert!(DayIntervalRequired::new(0.0).is_err());
4200    }
4201
4202    #[test]
4203    fn test_frequency_validation() {
4204        assert!(Frequency::new(1.0, None).is_ok());
4205        assert!(Frequency::new(0.0, None).is_err());
4206    }
4207
4208    #[test]
4209    fn test_gm_validation() {
4210        assert!(Gm::new(1.0, None).is_ok());
4211        assert!(Gm::new(0.0, None).is_err());
4212        assert!("KM**3/S**2".parse::<GmUnits>().is_ok());
4213    }
4214
4215    #[test]
4216    fn test_altitude_required_validation() {
4217        assert!(AltitudeRequired::new(0.0).is_ok());
4218        assert!(AltitudeRequired::new(9000.0).is_err());
4219        assert!(AltitudeRequired::new(-431.0).is_err());
4220    }
4221
4222    #[test]
4223    fn test_mass_validation() {
4224        assert!(Mass::new(0.0, None).is_ok());
4225        assert!(Mass::new(-1.0, None).is_err());
4226    }
4227
4228    #[test]
4229    fn test_area_validation() {
4230        assert!(Area::new(0.0, None).is_ok());
4231        assert!(Area::new(-1.0, None).is_err());
4232    }
4233
4234    #[test]
4235    fn test_ms2_parsing() {
4236        let ms2 = Ms2::from_str("9.81").unwrap();
4237        assert_eq!(ms2.value, 9.81);
4238        assert_eq!(ms2.units, Ms2Units::MPerS2);
4239    }
4240
4241    #[test]
4242    fn test_solar_flux_units() {
4243        test_enum_from_str!(SolarFluxUnits, "SFU", "INVALID");
4244        assert_eq!(format!("{}", SolarFluxUnits::JanskyScaled), "10**4 Jansky");
4245    }
4246
4247    #[test]
4248    fn test_epoch_conversion() {
4249        let s = "2023-01-01T00:00:00Z";
4250        let e = Epoch::from_str(s).unwrap();
4251        assert_eq!(Epoch::try_from(s.to_string()).unwrap(), e);
4252        assert_eq!(e.as_str(), s);
4253        assert!(!e.is_empty());
4254    }
4255
4256    #[test]
4257    fn test_percentage_validation() {
4258        assert!(Percentage::new(50.0, None).is_ok());
4259        assert!(Percentage::new(-0.1, None).is_err());
4260        assert!(Percentage::new(100.1, None).is_err());
4261        assert!(PercentageRequired::new(50.0).is_ok());
4262        assert!(PercentageRequired::new(-0.1).is_err());
4263        assert!(PercentageRequired::new(100.1).is_err());
4264    }
4265
4266    #[test]
4267    fn test_unit_conversions() {
4268        let f = Frequency::new(10.0, Some(FrequencyUnits::Hz)).unwrap();
4269        let uv = f.to_unit_value();
4270        assert_eq!(uv.value, 10.0);
4271        assert_eq!(uv.units, Some(FrequencyUnits::Hz));
4272
4273        let gm = Gm::new(1.0, Some(GmUnits::Km3PerS2)).unwrap();
4274        let uv = gm.to_unit_value();
4275        assert_eq!(uv.value, 1.0);
4276        assert_eq!(uv.units, Some(GmUnits::Km3PerS2));
4277
4278        let a = Angle::new(1.0, Some(AngleUnits::Deg)).unwrap();
4279        let uv = a.to_unit_value();
4280        assert_eq!(uv.value, 1.0);
4281        assert_eq!(uv.units, Some(AngleUnits::Deg));
4282    }
4283
4284    #[test]
4285    fn test_from_kvn_float() {
4286        let f = Frequency::from_kvn_float(10.0, Some("Hz")).unwrap();
4287        assert_eq!(f.value, 10.0);
4288        assert_eq!(f.units, Some(FrequencyUnits::Hz));
4289
4290        let gm = Gm::from_kvn_float(1.0, Some("KM**3/S**2")).unwrap();
4291        assert_eq!(gm.value, 1.0);
4292    }
4293
4294    #[test]
4295    fn test_additional_units() {
4296        test_enum_from_str!(AngleRateUnits, "deg/s", "INVALID");
4297        test_enum_from_str!(MomentUnits, "kg*m**2", "INVALID");
4298        test_enum_from_str!(QuaternionDotUnits, "1/s", "INVALID");
4299    }
4300
4301    #[test]
4302    fn test_tdm_reference_frame_aliases() {
4303        assert!("ITRF1993".parse::<TdmReferenceFrame>().is_ok());
4304        assert!("ITRF93".parse::<TdmReferenceFrame>().is_ok());
4305        assert!("TOD_EARTH".parse::<TdmReferenceFrame>().is_ok());
4306    }
4307
4308    #[test]
4309    fn test_tdm_path_index_range_validation() {
4310        assert!("1,2,1".parse::<TdmPath>().is_ok());
4311        assert!("1,6".parse::<TdmPath>().is_err());
4312        assert!("0,2".parse::<TdmPath>().is_err());
4313    }
4314}