paft_core/
lib.rs

1//! Core types and utilities for the paft ecosystem.
2#![warn(missing_docs)]
3
4/// Core cross-cutting domain types (currency, exchange, period, money, market state, instrument).
5pub mod domain;
6/// Error definitions shared across crates.
7pub mod error;
8/// Private serde helper modules for custom serialization patterns.
9pub mod serde_helpers;
10
11#[cfg(feature = "dataframe")]
12/// DataFrame conversion traits for paft
13pub mod dataframe;
14
15/// Internal macro exports for string-backed enums used across the paft workspace.
16/// These remain public for crate interoperability but are not covered by semver guarantees.
17#[doc(hidden)]
18#[macro_export]
19macro_rules! __string_enum_base {
20    (
21        $Type:ident, $enum_name:literal, error, {
22            $( $alias:literal => $variant:path ),+ $(,)?
23        }
24    ) => {
25        impl $crate::domain::string_canonical::StringCode for $Type {
26            fn code(&self) -> &str { $Type::code(self) }
27        }
28
29        impl ::serde::Serialize for $Type {
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.code())
35            }
36        }
37
38        impl<'de> ::serde::Deserialize<'de> for $Type {
39            fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
40            where
41                D: ::serde::Deserializer<'de>,
42            {
43                let raw = <String as ::serde::Deserialize>::deserialize(deserializer)?;
44                Self::from_str(&raw).map_err(::serde::de::Error::custom)
45            }
46        }
47
48        impl ::std::str::FromStr for $Type {
49            type Err = $crate::error::PaftError;
50
51            fn from_str(input: &str) -> ::std::result::Result<Self, Self::Err> {
52                let trimmed = input.trim();
53                if trimmed.is_empty() {
54                    return Err($crate::error::PaftError::InvalidEnumValue {
55                        enum_name: $enum_name,
56                        value: input.to_string(),
57                    });
58                }
59                let token = $crate::domain::string_canonical::canonicalize(trimmed);
60                let parsed = match token.as_str() {
61                    $( $alias => $variant, )*
62                    _ => {
63                        return Err($crate::error::PaftError::InvalidEnumValue {
64                            enum_name: $enum_name,
65                            value: input.to_string(),
66                        });
67                    }
68                };
69                Ok(parsed)
70            }
71        }
72
73        impl ::std::convert::TryFrom<String> for $Type {
74            type Error = $crate::error::PaftError;
75
76            fn try_from(value: String) -> ::std::result::Result<Self, Self::Error> {
77                Self::from_str(&value)
78            }
79        }
80
81        impl ::std::convert::From<$Type> for String {
82            fn from(v: $Type) -> Self { v.code().to_string() }
83        }
84
85        impl $Type {
86            #[doc(hidden)]
87            pub const __ALIASES: &'static [(&'static str, $Type)] = &[
88                $( ($alias, $variant) ),*
89            ];
90        }
91    };
92
93    (
94        $Type:ident, $enum_name:literal, other($OtherVariant:path), {
95            $( $alias:literal => $variant:path ),+ $(,)?
96        }
97    ) => {
98        impl $crate::domain::string_canonical::StringCode for $Type {
99            fn code(&self) -> &str { $Type::code(self) }
100            fn is_canonical(&self) -> bool { $Type::is_canonical(self) }
101        }
102
103        impl $Type {
104            /// Returns true when this value represents a canonical variant.
105            #[must_use]
106            pub const fn is_canonical(&self) -> bool { !matches!(self, $OtherVariant(_)) }
107        }
108
109        impl ::serde::Serialize for $Type {
110            fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error>
111            where
112                S: ::serde::Serializer,
113            {
114                serializer.serialize_str(self.code())
115            }
116        }
117
118        impl<'de> ::serde::Deserialize<'de> for $Type {
119            fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
120            where
121                D: ::serde::Deserializer<'de>,
122            {
123                let raw = <String as ::serde::Deserialize>::deserialize(deserializer)?;
124                Self::from_str(&raw).map_err(::serde::de::Error::custom)
125            }
126        }
127
128        impl ::std::str::FromStr for $Type {
129            type Err = $crate::error::PaftError;
130
131            fn from_str(input: &str) -> ::std::result::Result<Self, Self::Err> {
132                let trimmed = input.trim();
133                if trimmed.is_empty() {
134                    return Err($crate::error::PaftError::InvalidEnumValue {
135                        enum_name: $enum_name,
136                        value: input.to_string(),
137                    });
138                }
139                let token = $crate::domain::string_canonical::canonicalize(trimmed);
140                let parsed = match token.as_str() {
141                    $( $alias => $variant, )*
142                    _ => {
143                        let canon = $crate::domain::string_canonical::Canonical::try_new(trimmed)
144                            .map_err(|_| $crate::error::PaftError::InvalidEnumValue {
145                                enum_name: $enum_name,
146                                value: input.to_string(),
147                            })?;
148                        return Ok($OtherVariant(canon));
149                    }
150                };
151                Ok(parsed)
152            }
153        }
154
155        impl ::std::convert::TryFrom<String> for $Type {
156            type Error = $crate::error::PaftError;
157
158            fn try_from(value: String) -> ::std::result::Result<Self, Self::Error> {
159                Self::from_str(&value)
160            }
161        }
162
163        impl ::std::convert::From<$Type> for String {
164            fn from(v: $Type) -> Self { v.code().to_string() }
165        }
166
167        impl $Type {
168            #[doc(hidden)]
169            pub const __ALIASES: &'static [(&'static str, $Type)] = &[
170                $( ($alias, $variant) ),*
171            ];
172        }
173    };
174}
175
176/// Open (extensible) string enum with `Other` variant policy.
177#[doc(hidden)]
178#[macro_export]
179macro_rules! string_enum {
180    (
181        $Type:ident, $Other:ident, $enum_name:literal, {
182            $( $alias:literal => $variant:path ),+ $(,)?
183        }
184    ) => {
185        $crate::__string_enum_base! { $Type, $enum_name, other($Type::$Other), { $( $alias => $variant ),+ } }
186    };
187}
188
189/// Closed string enum (no `Other`), deserialization errors on unknown tokens.
190#[doc(hidden)]
191#[macro_export]
192macro_rules! string_enum_closed {
193    (
194        $Type:ident, $enum_name:literal, {
195            $( $alias:literal => $variant:path ),+ $(,)?
196        }
197    ) => {
198        $crate::__string_enum_base! { $Type, $enum_name, error, { $( $alias => $variant ),+ } }
199    };
200}
201
202/// Helper to implement Display using the type's `code()` method.
203#[doc(hidden)]
204#[macro_export]
205macro_rules! impl_display_via_code {
206    ( $Type:ident ) => {
207        impl ::std::fmt::Display for $Type {
208            fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
209                f.write_str(self.code())
210            }
211        }
212    };
213}
214
215/// Open (extensible) string enum with macro-provided `code()` and open parsing.
216#[doc(hidden)]
217#[macro_export]
218macro_rules! string_enum_with_code {
219    (
220        $Type:ident, $Other:ident, $enum_name:literal,
221        { $( $canon_token:literal => $canon_variant:path ),+ $(,)? },
222        { $( $alias:literal => $variant:path ),* $(,)? }
223    ) => {
224        impl $Type {
225            /// Returns the canonical string code for this value.
226            #[must_use]
227            pub fn code(&self) -> &str {
228                match self {
229                    $( $canon_variant => $canon_token, )+
230                    $Type::$Other(s) => s.as_ref(),
231                }
232            }
233        }
234
235        $crate::__string_enum_base! {
236            $Type, $enum_name, other($Type::$Other),
237            { $( $canon_token => $canon_variant ),+ $(, $alias => $variant )* }
238        }
239    };
240
241    (
242        $Type:ident, $Other:ident, $enum_name:literal,
243        { $( $canon_token:literal => $canon_variant:path ),+ $(,)? }
244    ) => {
245        $crate::string_enum_with_code!(
246            $Type, $Other, $enum_name,
247            { $( $canon_token => $canon_variant ),+ },
248            {}
249        );
250    };
251}
252
253/// Closed string enum with macro-provided `code()` and closed parsing.
254#[doc(hidden)]
255#[macro_export]
256macro_rules! string_enum_closed_with_code {
257    (
258        $Type:ident, $enum_name:literal,
259        { $( $canon_token:literal => $canon_variant:path ),+ $(,)? },
260        { $( $alias:literal => $variant:path ),* $(,)? }
261    ) => {
262        impl $Type {
263            /// Returns the canonical string code for this value.
264            #[must_use]
265            pub const fn code(&self) -> &str {
266                match self {
267                    $( $canon_variant => $canon_token, )+
268                }
269            }
270        }
271
272        $crate::__string_enum_base! {
273            $Type, $enum_name, error,
274            { $( $canon_token => $canon_variant ),+ $(, $alias => $variant )* }
275        }
276    };
277
278    (
279        $Type:ident, $enum_name:literal,
280        { $( $canon_token:literal => $canon_variant:path ),+ $(,)? }
281    ) => {
282        $crate::string_enum_closed_with_code!(
283            $Type, $enum_name,
284            { $( $canon_token => $canon_variant ),+ },
285            {}
286        );
287    };
288}
289
290pub use error::PaftError;
291
292#[cfg(feature = "dataframe")]
293pub use dataframe::{Columnar, ToDataFrame, ToDataFrameVec};