Skip to main content

lutra_bin/
std_types.rs

1use crate::{Decode, Encode, Layout, Result};
2
3/// Standard-library types used by generated Lutra code.
4pub mod ops {
5    /// Result of a three-way comparison.
6    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7    #[allow(non_camel_case_types)]
8    pub enum Ordering {
9        Less,
10        Equal,
11        Greater,
12    }
13}
14
15/// An instant in time — microseconds since 1970-01-01T00:00:00 UTC.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
17pub struct Timestamp {
18    pub microseconds: i64,
19}
20
21/// A calendar date — days since 1970-01-01.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
23pub struct Date {
24    pub days_epoch: i32,
25}
26
27/// A time-of-day offset — microseconds since midnight (always in `[0, 86_400_000_000)`).
28#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
29pub struct Time {
30    pub micros_midnight: u64,
31}
32
33/// A signed, unbounded duration — microseconds.
34#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
35pub struct Duration {
36    pub microseconds: i64,
37}
38
39/// A fixed-point decimal (scale = 2).
40#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
41pub struct Decimal(pub i64);
42
43impl Encode for Timestamp {
44    type HeadPtr = ();
45
46    fn encode_head(&self, buf: &mut crate::bytes::BytesMut) {
47        self.microseconds.encode_head(buf)
48    }
49
50    fn encode_body(&self, _: (), _: &mut crate::bytes::BytesMut) {}
51}
52
53impl Layout for Timestamp {
54    fn head_size() -> usize {
55        i64::head_size()
56    }
57}
58
59impl Decode for Timestamp {
60    fn decode(buf: &[u8]) -> Result<Self> {
61        Ok(Self {
62            microseconds: i64::decode(buf)?,
63        })
64    }
65}
66impl Encode for Date {
67    type HeadPtr = ();
68
69    fn encode_head(&self, buf: &mut crate::bytes::BytesMut) {
70        self.days_epoch.encode_head(buf)
71    }
72
73    fn encode_body(&self, _: (), _: &mut crate::bytes::BytesMut) {}
74}
75impl Layout for Date {
76    fn head_size() -> usize {
77        i32::head_size()
78    }
79}
80impl Decode for Date {
81    fn decode(buf: &[u8]) -> Result<Self> {
82        Ok(Self {
83            days_epoch: i32::decode(buf)?,
84        })
85    }
86}
87impl Encode for Time {
88    type HeadPtr = ();
89
90    fn encode_head(&self, buf: &mut crate::bytes::BytesMut) {
91        self.micros_midnight.encode_head(buf)
92    }
93
94    fn encode_body(&self, _: (), _: &mut crate::bytes::BytesMut) {}
95}
96impl Layout for Time {
97    fn head_size() -> usize {
98        u64::head_size()
99    }
100}
101impl Decode for Time {
102    fn decode(buf: &[u8]) -> Result<Self> {
103        Ok(Self {
104            micros_midnight: u64::decode(buf)?,
105        })
106    }
107}
108impl Encode for Duration {
109    type HeadPtr = ();
110
111    fn encode_head(&self, buf: &mut crate::bytes::BytesMut) {
112        self.microseconds.encode_head(buf)
113    }
114
115    fn encode_body(&self, _: (), _: &mut crate::bytes::BytesMut) {}
116}
117impl Layout for Duration {
118    fn head_size() -> usize {
119        i64::head_size()
120    }
121}
122impl Decode for Duration {
123    fn decode(buf: &[u8]) -> Result<Self> {
124        Ok(Self {
125            microseconds: i64::decode(buf)?,
126        })
127    }
128}
129impl Encode for Decimal {
130    type HeadPtr = ();
131
132    fn encode_head(&self, buf: &mut crate::bytes::BytesMut) {
133        self.0.encode_head(buf)
134    }
135
136    fn encode_body(&self, _: (), _: &mut crate::bytes::BytesMut) {}
137}
138impl Layout for Decimal {
139    fn head_size() -> usize {
140        i64::head_size()
141    }
142}
143impl Decode for Decimal {
144    fn decode(buf: &[u8]) -> Result<Self> {
145        Ok(Self(i64::decode(buf)?))
146    }
147}
148impl Encode for ops::Ordering {
149    type HeadPtr = ();
150
151    fn encode_head(&self, buf: &mut crate::bytes::BytesMut) {
152        let tag = match self {
153            Self::Less => 0_u8,
154            Self::Equal => 1,
155            Self::Greater => 2,
156        };
157        tag.encode_head(buf)
158    }
159
160    fn encode_body(&self, _: (), _: &mut crate::bytes::BytesMut) {}
161}
162impl Layout for ops::Ordering {
163    fn head_size() -> usize {
164        8
165    }
166}
167impl Decode for ops::Ordering {
168    fn decode(buf: &[u8]) -> Result<Self> {
169        Ok(match u8::decode(buf)? {
170            0 => Self::Less,
171            1 => Self::Equal,
172            2 => Self::Greater,
173            _ => return Err(crate::Error::InvalidData),
174        })
175    }
176}
177
178#[cfg(feature = "chrono")]
179mod chrono_impls {
180    use chrono::{Datelike, Timelike};
181
182    use super::{Date, Time, Timestamp};
183
184    const EPOCH_DAYS_FROM_CE: i32 = 719_163;
185    const MICROS_PER_SECOND: u32 = 1_000_000;
186    const MICROS_PER_DAY: i64 = 86_400_000_000;
187
188    impl TryFrom<Timestamp> for chrono::DateTime<chrono::Utc> {
189        type Error = crate::Error;
190
191        fn try_from(value: Timestamp) -> core::result::Result<Self, Self::Error> {
192            chrono::DateTime::from_timestamp_micros(value.microseconds)
193                .ok_or(crate::Error::InvalidData)
194        }
195    }
196
197    impl From<chrono::DateTime<chrono::Utc>> for Timestamp {
198        fn from(value: chrono::DateTime<chrono::Utc>) -> Self {
199            Self {
200                microseconds: value.timestamp_micros(),
201            }
202        }
203    }
204
205    impl TryFrom<Date> for chrono::NaiveDate {
206        type Error = crate::Error;
207
208        fn try_from(value: Date) -> core::result::Result<Self, Self::Error> {
209            chrono::NaiveDate::from_num_days_from_ce_opt(value.days_epoch + EPOCH_DAYS_FROM_CE)
210                .ok_or(crate::Error::InvalidData)
211        }
212    }
213
214    impl TryFrom<chrono::NaiveDate> for Date {
215        type Error = crate::Error;
216
217        fn try_from(value: chrono::NaiveDate) -> core::result::Result<Self, Self::Error> {
218            let days = value.num_days_from_ce() - EPOCH_DAYS_FROM_CE;
219            Ok(Self { days_epoch: days })
220        }
221    }
222
223    impl TryFrom<Time> for chrono::NaiveTime {
224        type Error = crate::Error;
225
226        fn try_from(value: Time) -> core::result::Result<Self, Self::Error> {
227            if value.micros_midnight >= MICROS_PER_DAY as u64 {
228                return Err(crate::Error::InvalidData);
229            }
230
231            let secs = u32::try_from(value.micros_midnight / u64::from(MICROS_PER_SECOND))
232                .map_err(|_| crate::Error::InvalidData)?;
233            let micros = u32::try_from(value.micros_midnight % u64::from(MICROS_PER_SECOND))
234                .map_err(|_| crate::Error::InvalidData)?;
235
236            chrono::NaiveTime::from_num_seconds_from_midnight_opt(secs, micros * 1_000)
237                .ok_or(crate::Error::InvalidData)
238        }
239    }
240
241    impl From<chrono::NaiveTime> for Time {
242        fn from(value: chrono::NaiveTime) -> Self {
243            let secs = u64::from(value.num_seconds_from_midnight());
244            let micros = u64::from(value.nanosecond() / 1_000);
245            Self {
246                micros_midnight: secs * u64::from(MICROS_PER_SECOND) + micros,
247            }
248        }
249    }
250}