1use std::{borrow::Cow, cmp::Ordering, ops::Deref, str::FromStr};
2
3use ordered_float::OrderedFloat;
4pub use uuid::Uuid;
5
6#[derive(
7 Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
8)]
9pub struct AttrKey(Cow<'static, str>);
10
11impl AttrKey {
12 pub const fn new(k: String) -> Self {
13 Self(Cow::Owned(k))
14 }
15
16 pub const fn new_static(k: &'static str) -> Self {
17 Self(Cow::Borrowed(k))
18 }
19}
20
21impl From<&str> for AttrKey {
22 fn from(s: &str) -> Self {
23 AttrKey(Cow::from(s.to_owned()))
24 }
25}
26
27impl From<String> for AttrKey {
28 fn from(s: String) -> Self {
29 AttrKey(Cow::from(s))
30 }
31}
32
33impl AsRef<str> for AttrKey {
34 fn as_ref(&self) -> &str {
35 self.0.as_ref()
36 }
37}
38
39impl From<AttrKey> for String {
40 fn from(k: AttrKey) -> Self {
41 match k.0 {
42 Cow::Borrowed(b) => b.to_owned(),
43 Cow::Owned(o) => o,
44 }
45 }
46}
47
48impl std::fmt::Display for AttrKey {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
50 write!(f, "{}", self.0)
51 }
52}
53
54#[derive(
61 Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
62)]
63pub struct BigInt(Box<i128>);
64
65impl BigInt {
66 pub fn new_attr_val(big_i: i128) -> AttrVal {
67 if big_i < (i64::MIN as i128) || big_i > (i64::MAX as i128) {
69 AttrVal::BigInt(BigInt(Box::new(big_i)))
70 } else {
71 AttrVal::Integer(big_i as i64)
72 }
73 }
74}
75impl std::fmt::Display for BigInt {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 self.0.fmt(f)
78 }
79}
80
81impl AsRef<i128> for BigInt {
82 fn as_ref(&self) -> &i128 {
83 self.0.as_ref()
84 }
85}
86
87impl Deref for BigInt {
88 type Target = i128;
89
90 fn deref(&self) -> &Self::Target {
91 self.0.as_ref()
92 }
93}
94
95#[derive(
101 Copy, Clone, Eq, PartialEq, Debug, Ord, PartialOrd, Hash, serde::Serialize, serde::Deserialize,
102)]
103#[repr(transparent)]
104#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
105pub struct Nanoseconds(u64);
106
107impl Nanoseconds {
108 pub fn get_raw(&self) -> u64 {
109 self.0
110 }
111}
112
113impl From<u64> for Nanoseconds {
114 fn from(n: u64) -> Self {
115 Nanoseconds(n)
116 }
117}
118
119impl std::fmt::Display for Nanoseconds {
120 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121 write!(f, "{}ns", self.0)
122 }
123}
124
125impl FromStr for Nanoseconds {
126 type Err = std::num::ParseIntError;
127 fn from_str(s: &str) -> Result<Self, Self::Err> {
128 Ok(Nanoseconds(s.parse::<u64>()?))
129 }
130}
131
132#[derive(Eq, PartialEq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]
138#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
139#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
140pub struct LogicalTime(Box<[u64; 4]>);
141
142impl LogicalTime {
143 pub fn unary<A: Into<u64>>(a: A) -> Self {
144 LogicalTime(Box::new([0, 0, 0, a.into()]))
145 }
146
147 pub fn binary<A: Into<u64>, B: Into<u64>>(a: A, b: B) -> Self {
148 LogicalTime(Box::new([0, 0, a.into(), b.into()]))
149 }
150
151 pub fn trinary<A: Into<u64>, B: Into<u64>, C: Into<u64>>(a: A, b: B, c: C) -> Self {
152 LogicalTime(Box::new([0, a.into(), b.into(), c.into()]))
153 }
154
155 pub fn quaternary<A: Into<u64>, B: Into<u64>, C: Into<u64>, D: Into<u64>>(
156 a: A,
157 b: B,
158 c: C,
159 d: D,
160 ) -> Self {
161 LogicalTime(Box::new([a.into(), b.into(), c.into(), d.into()]))
162 }
163
164 pub fn get_raw(&self) -> &[u64; 4] {
165 &self.0
166 }
167}
168
169#[cfg(feature = "pyo3")]
170#[pyo3::pymethods]
171impl LogicalTime {
172 #[staticmethod]
173 #[pyo3(name = "unary")]
174 pub fn unary_py(a: u64) -> Self {
175 LogicalTime(Box::new([0, 0, 0, a]))
176 }
177
178 #[staticmethod]
179 #[pyo3(name = "binary")]
180 pub fn binary_py(a: u64, b: u64) -> Self {
181 LogicalTime(Box::new([0, 0, a, b]))
182 }
183
184 #[staticmethod]
185 #[pyo3(name = "trinary")]
186 pub fn trinary_py(a: u64, b: u64, c: u64) -> Self {
187 LogicalTime(Box::new([0, a, b, c]))
188 }
189
190 #[staticmethod]
191 #[pyo3(name = "quaternary")]
192 pub fn quaternary_py(a: u64, b: u64, c: u64, d: u64) -> Self {
193 LogicalTime(Box::new([a, b, c, d]))
194 }
195
196 pub fn as_tuple(&self) -> (u64, u64, u64, u64) {
197 (self.0[0], self.0[1], self.0[2], self.0[3])
198 }
199
200 pub fn as_array(&self) -> [u64; 4] {
201 *(self.0)
202 }
203}
204
205impl Ord for LogicalTime {
206 fn cmp(&self, other: &Self) -> Ordering {
207 for (a, b) in self.0.iter().zip(other.0.iter()) {
208 match a.cmp(b) {
209 Ordering::Equal => (), Ordering::Less => return Ordering::Less,
211 Ordering::Greater => return Ordering::Greater,
212 }
213 }
214
215 Ordering::Equal
216 }
217}
218
219impl PartialOrd for LogicalTime {
220 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
221 Some(self.cmp(other))
222 }
223}
224
225impl std::fmt::Display for LogicalTime {
226 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
227 write!(f, "{}:{}:{}:{}", self.0[0], self.0[1], self.0[2], self.0[3])
228 }
229}
230
231impl FromStr for LogicalTime {
232 type Err = ();
233 fn from_str(s: &str) -> Result<Self, Self::Err> {
234 let mut segments = s.rsplit(':');
235
236 if let Ok(mut time) = segments.try_fold(Vec::new(), |mut acc, segment| {
237 segment.parse::<u64>().map(|t| {
238 acc.insert(0, t);
239 acc
240 })
241 }) {
242 while time.len() < 4 {
243 time.insert(0, 0)
244 }
245
246 let time_array = time.into_boxed_slice().try_into().map_err(|_| ())?;
247
248 Ok(LogicalTime(time_array))
249 } else {
250 Err(())
251 }
252 }
253}
254
255pub const TIMELINE_ID_SIGIL: char = '%';
260
261#[derive(
264 Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Debug, serde::Serialize, serde::Deserialize,
265)]
266#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
267#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
268pub struct TimelineId(Uuid);
269
270#[cfg(feature = "pyo3")]
271#[pyo3::pymethods]
272impl TimelineId {
273 #[staticmethod]
274 pub fn zero_py() -> Self {
275 TimelineId(Uuid::nil())
276 }
277
278 #[staticmethod]
279 #[pyo3(name = "allocate")]
280 pub fn allocate_py() -> Self {
281 TimelineId(Uuid::new_v4())
282 }
283
284 fn __eq__(&self, other: &Self) -> bool {
285 self.0 == other.0
286 }
287
288 fn __hash__(&self) -> u64 {
289 use std::hash::Hash as _;
290 use std::hash::Hasher as _;
291 let mut hasher = std::hash::DefaultHasher::new();
292 self.hash(&mut hasher);
293 hasher.finish()
294 }
295}
296
297impl TimelineId {
298 pub fn zero() -> Self {
299 TimelineId(Uuid::nil())
300 }
301
302 pub fn allocate() -> Self {
303 TimelineId(Uuid::new_v4())
304 }
305
306 pub fn get_raw(&self) -> &Uuid {
307 &self.0
308 }
309}
310
311impl From<Uuid> for TimelineId {
312 fn from(uuid: Uuid) -> Self {
313 TimelineId(uuid)
314 }
315}
316
317impl std::fmt::Display for TimelineId {
318 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
319 self.0.fmt(f)
320 }
321}
322
323pub type OpaqueEventId = [u8; 16];
328
329#[derive(
330 Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
331)]
332#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
333#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
334pub struct EventCoordinate {
335 pub timeline_id: TimelineId,
336 pub id: OpaqueEventId,
337}
338
339impl EventCoordinate {
340 pub fn as_bytes(&self) -> [u8; 32] {
341 let mut bytes = [0u8; 32];
342 bytes[0..16].copy_from_slice(self.timeline_id.0.as_bytes());
343 bytes[16..32].copy_from_slice(&self.id);
344 bytes
345 }
346
347 pub fn from_byte_slice(bytes: &[u8]) -> Option<Self> {
348 if bytes.len() != 32 {
349 return None;
350 }
351
352 Some(EventCoordinate {
353 timeline_id: Uuid::from_slice(&bytes[0..16]).ok()?.into(),
354 id: bytes[16..32].try_into().ok()?,
355 })
356 }
357}
358
359impl std::fmt::Display for EventCoordinate {
360 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
361 write!(f, "{TIMELINE_ID_SIGIL}")?;
362
363 for byte in self.timeline_id.0.as_bytes() {
365 write!(f, "{byte:02x}")?;
366 }
367
368 write!(f, ":{}", EncodeHexWithoutLeadingZeroes(&self.id))
369 }
370}
371
372pub struct EncodeHexWithoutLeadingZeroes<'a>(pub &'a [u8]);
373
374impl<'a> std::fmt::Display for EncodeHexWithoutLeadingZeroes<'a> {
375 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
376 let mut cursor = 0;
377 let bytes = self.0;
378 while bytes[cursor] == 0 && cursor < bytes.len() - 1 {
379 cursor += 1;
380 }
381
382 if cursor == bytes.len() {
383 write!(f, "0")?;
384 } else {
385 for byte in bytes.iter().skip(cursor) {
386 write!(f, "{byte:02x}")?;
387 }
388 }
389
390 Ok(())
391 }
392}
393
394#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
399pub enum AttrVal {
400 TimelineId(Box<TimelineId>),
401 EventCoordinate(Box<EventCoordinate>),
402 String(Cow<'static, str>),
403 Integer(i64),
404 BigInt(BigInt),
405 Float(OrderedFloat<f64>),
406 Bool(bool),
407 Timestamp(Nanoseconds),
408 LogicalTime(LogicalTime),
409}
410
411impl AttrVal {
412 pub fn attr_type(&self) -> AttrType {
413 match self {
414 AttrVal::TimelineId(_) => AttrType::TimelineId,
415 AttrVal::EventCoordinate(_) => AttrType::EventCoordinate,
416 AttrVal::String(_) => AttrType::String,
417 AttrVal::Integer(_) => AttrType::Integer,
418 AttrVal::BigInt(_) => AttrType::BigInt,
419 AttrVal::Float(_) => AttrType::Float,
420 AttrVal::Bool(_) => AttrType::Bool,
421 AttrVal::Timestamp(_) => AttrType::Nanoseconds,
422 AttrVal::LogicalTime(_) => AttrType::LogicalTime,
423 }
424 }
425
426 pub fn as_timeline_id(self) -> std::result::Result<TimelineId, WrongAttrTypeError> {
427 self.try_into()
428 }
429
430 pub fn as_event_coordinate(self) -> std::result::Result<EventCoordinate, WrongAttrTypeError> {
431 self.try_into()
432 }
433
434 pub fn as_string(self) -> std::result::Result<Cow<'static, str>, WrongAttrTypeError> {
435 self.try_into()
436 }
437
438 pub fn as_int(self) -> std::result::Result<i64, WrongAttrTypeError> {
439 self.try_into()
440 }
441
442 pub fn as_bigint(self) -> std::result::Result<i128, WrongAttrTypeError> {
443 self.try_into()
444 }
445
446 pub fn as_float(self) -> std::result::Result<f64, WrongAttrTypeError> {
447 self.try_into()
448 }
449
450 pub fn as_bool(self) -> std::result::Result<bool, WrongAttrTypeError> {
451 self.try_into()
452 }
453
454 pub fn as_timestamp(self) -> std::result::Result<Nanoseconds, WrongAttrTypeError> {
455 self.try_into()
456 }
457
458 pub fn as_logical_time(self) -> std::result::Result<LogicalTime, WrongAttrTypeError> {
459 self.try_into()
460 }
461}
462
463impl std::fmt::Display for AttrVal {
464 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
465 match self {
466 AttrVal::String(s) => s.fmt(f),
467 AttrVal::Integer(i) => i.fmt(f),
468 AttrVal::BigInt(bi) => bi.fmt(f),
469 AttrVal::Float(fp) => fp.fmt(f),
470 AttrVal::Bool(b) => b.fmt(f),
471 AttrVal::Timestamp(ns) => ns.fmt(f),
472 AttrVal::LogicalTime(lt) => lt.fmt(f),
473 AttrVal::EventCoordinate(ec) => ec.fmt(f),
474 AttrVal::TimelineId(tid) => tid.fmt(f),
475 }
476 }
477}
478
479impl FromStr for AttrVal {
480 type Err = std::convert::Infallible;
481
482 fn from_str(s: &str) -> Result<Self, Self::Err> {
483 Ok(if let Ok(v) = s.to_lowercase().parse::<bool>() {
487 v.into()
488 } else if let Ok(v) = s.parse::<i128>() {
489 v.into()
491 } else if let Ok(v) = s.parse::<f64>() {
492 v.into()
493 } else if let Ok(v) = s.parse::<LogicalTime>() {
494 v.into()
495 } else if let Ok(v) = s.parse::<Uuid>() {
496 v.into()
497 } else {
498 AttrVal::String(s.trim_matches(|c| c == '"' || c == '\'').to_owned().into())
501 })
502 }
503}
504
505impl From<String> for AttrVal {
506 fn from(s: String) -> AttrVal {
507 AttrVal::String(Cow::Owned(s))
508 }
509}
510
511impl From<&str> for AttrVal {
512 fn from(s: &str) -> AttrVal {
513 AttrVal::String(Cow::Owned(s.to_owned()))
514 }
515}
516
517impl From<Cow<'static, str>> for AttrVal {
518 fn from(s: Cow<'static, str>) -> Self {
519 AttrVal::String(s)
520 }
521}
522
523impl From<&String> for AttrVal {
524 fn from(s: &String) -> Self {
525 AttrVal::String(Cow::Owned(s.clone()))
526 }
527}
528
529impl From<bool> for AttrVal {
530 fn from(b: bool) -> AttrVal {
531 AttrVal::Bool(b)
532 }
533}
534
535impl From<Nanoseconds> for AttrVal {
536 fn from(ns: Nanoseconds) -> AttrVal {
537 AttrVal::Timestamp(ns)
538 }
539}
540
541impl From<LogicalTime> for AttrVal {
542 fn from(lt: LogicalTime) -> AttrVal {
543 AttrVal::LogicalTime(lt)
544 }
545}
546
547impl From<Uuid> for AttrVal {
548 fn from(u: Uuid) -> AttrVal {
549 AttrVal::TimelineId(Box::new(u.into()))
550 }
551}
552
553impl From<EventCoordinate> for AttrVal {
554 fn from(coord: EventCoordinate) -> Self {
555 AttrVal::EventCoordinate(Box::new(coord))
556 }
557}
558
559impl From<TimelineId> for AttrVal {
560 fn from(timeline_id: TimelineId) -> Self {
561 AttrVal::TimelineId(Box::new(timeline_id))
562 }
563}
564
565#[derive(Hash, Eq, PartialEq, Copy, Clone, Debug, PartialOrd, Ord)]
566pub enum AttrType {
567 TimelineId,
568 EventCoordinate,
569 String,
570 Integer,
571 BigInt,
572 Float,
573 Bool,
574 Nanoseconds,
575 LogicalTime,
576 Any,
577}
578
579impl std::fmt::Display for AttrType {
580 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
581 match self {
582 AttrType::TimelineId => "TimelineId",
583 AttrType::String => "String",
584 AttrType::Integer => "Integer",
585 AttrType::BigInt => "BigInteger",
586 AttrType::Float => "Float",
587 AttrType::Bool => "Bool",
588 AttrType::Nanoseconds => "Nanoseconds",
589 AttrType::LogicalTime => "LogicalTime",
590 AttrType::Any => "Any",
591 AttrType::EventCoordinate => "Coordinate",
592 }
593 .fmt(f)
594 }
595}
596
597pub mod conversion {
598 use std::convert::TryFrom;
599
600 use super::*;
601
602 macro_rules! impl_from_integer {
603 ($ty:ty) => {
604 impl From<$ty> for AttrVal {
605 fn from(i: $ty) -> Self {
606 AttrVal::Integer(i as i64)
607 }
608 }
609 };
610 }
611
612 impl_from_integer!(i8);
613 impl_from_integer!(i16);
614 impl_from_integer!(i32);
615 impl_from_integer!(i64);
616 impl_from_integer!(u8);
617 impl_from_integer!(u16);
618 impl_from_integer!(u32);
619
620 macro_rules! impl_from_bigint {
621 ($ty:ty) => {
622 impl From<$ty> for AttrVal {
623 fn from(i: $ty) -> Self {
624 BigInt::new_attr_val(i as i128)
625 }
626 }
627 };
628 }
629
630 impl_from_bigint!(u64);
631 impl_from_bigint!(i128);
632
633 macro_rules! impl_from_float {
634 ($ty:ty) => {
635 impl From<$ty> for AttrVal {
636 fn from(f: $ty) -> Self {
637 AttrVal::Float((f as f64).into())
638 }
639 }
640 };
641 }
642
643 impl_from_float!(f32);
644 impl_from_float!(f64);
645
646 macro_rules! impl_try_from_attr_val {
647 ($variant:path, $ty:ty, $expected:path) => {
648 impl TryFrom<AttrVal> for $ty {
649 type Error = WrongAttrTypeError;
650
651 fn try_from(value: AttrVal) -> std::result::Result<Self, Self::Error> {
652 if let $variant(x) = value {
653 Ok(x.into())
654 } else {
655 Err(WrongAttrTypeError {
656 actual: value.attr_type(),
657 expected: $expected,
658 })
659 }
660 }
661 }
662 };
663 }
664
665 macro_rules! impl_try_from_attr_val_deref {
666 ($variant:path, $ty:ty, $expected:path) => {
667 impl TryFrom<AttrVal> for $ty {
668 type Error = WrongAttrTypeError;
669
670 fn try_from(value: AttrVal) -> std::result::Result<Self, Self::Error> {
671 if let $variant(x) = value {
672 Ok((*x).clone())
673 } else {
674 Err(WrongAttrTypeError {
675 actual: value.attr_type(),
676 expected: $expected,
677 })
678 }
679 }
680 }
681 };
682 }
683
684 impl_try_from_attr_val_deref!(AttrVal::TimelineId, TimelineId, AttrType::TimelineId);
685 impl_try_from_attr_val_deref!(
686 AttrVal::EventCoordinate,
687 EventCoordinate,
688 AttrType::EventCoordinate
689 );
690
691 impl_try_from_attr_val!(AttrVal::Integer, i64, AttrType::Integer);
692 impl_try_from_attr_val!(AttrVal::String, Cow<'static, str>, AttrType::String);
693 impl_try_from_attr_val_deref!(AttrVal::BigInt, i128, AttrType::BigInt);
694 impl_try_from_attr_val!(AttrVal::Float, f64, AttrType::Float);
695 impl_try_from_attr_val!(AttrVal::Bool, bool, AttrType::Bool);
696 impl_try_from_attr_val!(AttrVal::LogicalTime, LogicalTime, AttrType::LogicalTime);
697 impl_try_from_attr_val!(AttrVal::Timestamp, Nanoseconds, AttrType::Nanoseconds);
698}
699
700#[derive(Debug, thiserror::Error, Eq, PartialEq)]
701#[error("Wrong attribute type: expected {expected:?}, found {actual:?}")]
702pub struct WrongAttrTypeError {
703 actual: AttrType,
704 expected: AttrType,
705}
706
707#[cfg(feature = "pyo3")]
708impl<'py> pyo3::FromPyObject<'py> for AttrVal {
709 fn extract_bound(
710 ob: &pyo3::prelude::Bound<'py, pyo3::prelude::PyAny>,
711 ) -> pyo3::prelude::PyResult<Self> {
712 use pyo3::prelude::*;
713
714 if let Ok(i) = ob.extract::<i64>() {
716 return Ok(i.into());
717 }
718
719 if let Ok(f) = ob.extract::<f64>() {
720 return Ok(f.into());
721 }
722
723 if let Ok(s) = ob.extract::<String>() {
724 return Ok(s.into());
725 }
726
727 if let Ok(b) = ob.extract::<bool>() {
728 return Ok(b.into());
729 }
730
731 if let Ok(ts) = ob.extract::<std::time::SystemTime>() {
732 match ts.duration_since(std::time::UNIX_EPOCH) {
733 Ok(dur) => {
734 let ns = dur.as_nanos();
735 if ns > u64::MAX as u128 {
736 return Err(pyo3::exceptions::PyValueError::new_err(
737 "Timestamp value too large",
738 ));
739 } else {
740 return Ok(Nanoseconds::from(ns as u64).into());
741 }
742 }
743 Err(_) => {
744 return Err(pyo3::exceptions::PyValueError::new_err(
745 "Timestamp before UNIX epoch",
746 ));
747 }
748 }
749 }
750
751 if let Ok(tl_id) = ob.extract::<TimelineId>() {
753 return Ok(tl_id.0.into());
754 }
755
756 if let Ok(lt) = ob.extract::<LogicalTime>() {
757 return Ok(lt.into());
758 }
759
760 if let Ok(ec) = ob.extract::<EventCoordinate>() {
761 return Ok(ec.into());
762 }
763
764 if let Ok(i) = ob.extract::<i128>() {
765 return Ok(i.into());
766 }
767
768 if let Ok(id) = ob.extract::<crate::mutation_plane::types::MutationId>() {
769 return Ok(i128::from_le_bytes(Uuid::from(id).into_bytes()).into());
770 }
771
772 if let Ok(id) = ob.extract::<crate::mutation_plane::types::MutatorId>() {
773 return Ok(i128::from_le_bytes(Uuid::from(id).into_bytes()).into());
774 }
775
776 Err(pyo3::exceptions::PyValueError::new_err(
777 "Cannot represent value as AttrVal",
778 ))
779 }
780}
781
782#[cfg(feature = "pyo3")]
783impl pyo3::IntoPy<pyo3::PyObject> for AttrVal {
784 fn into_py(self, py: pyo3::prelude::Python<'_>) -> pyo3::PyObject {
785 match self {
786 AttrVal::TimelineId(tid) => tid.into_py(py),
787 AttrVal::EventCoordinate(ec) => ec.into_py(py),
788 AttrVal::String(s) => s.into_py(py),
789 AttrVal::Integer(i) => i.into_py(py),
790 AttrVal::BigInt(bi) => bi.into_py(py),
791 AttrVal::Float(f) => f.into_py(py),
792 AttrVal::Bool(b) => b.into_py(py),
793 AttrVal::Timestamp(ns) => {
794 (std::time::UNIX_EPOCH + std::time::Duration::from_nanos(ns.get_raw())).into_py(py)
795 }
796 AttrVal::LogicalTime(lt) => lt.into_py(py),
797 }
798 }
799}
800
801#[cfg(test)]
802mod tests {
803 use super::*;
804
805 #[test]
806 fn parse_logical_time() {
807 let reference = Ok(LogicalTime::quaternary(0u64, 0u64, 0u64, 42u64));
808
809 assert_eq!(reference, "42".parse());
811 assert_eq!(reference, "0:42".parse());
812 assert_eq!(reference, "0:0:42".parse());
813 assert_eq!(reference, "0:0:0:42".parse());
814
815 assert_eq!(Err(()), ":".parse::<LogicalTime>());
817 assert_eq!(Err(()), "::".parse::<LogicalTime>());
818 assert_eq!(Err(()), ":0".parse::<LogicalTime>());
819 assert_eq!(Err(()), "0:".parse::<LogicalTime>());
820 assert_eq!(Err(()), "127.0.0.1:8080".parse::<LogicalTime>());
821 assert_eq!(Err(()), "localhost:8080".parse::<LogicalTime>());
822 assert_eq!(Err(()), "example.com:8080".parse::<LogicalTime>());
823 }
824
825 #[test]
826 fn parse_attr_vals() {
827 assert_eq!(Ok(AttrVal::Bool(false)), "false".parse());
829 assert_eq!(Ok(AttrVal::Bool(true)), "true".parse());
830
831 assert_eq!(Ok(AttrVal::Integer(37)), "37".parse());
833
834 assert_eq!(
836 Ok(BigInt::new_attr_val(36893488147419103232i128)),
837 "36893488147419103232".parse()
838 );
839
840 assert_eq!(Ok(AttrVal::Float(76.37f64.into())), "76.37".parse());
842
843 assert_eq!(
845 Ok(AttrVal::TimelineId(Box::new(
846 Uuid::parse_str("bec14bc0-1dea-4b68-b138-62f7b6827e35")
847 .unwrap()
848 .into()
849 ))),
850 "bec14bc0-1dea-4b68-b138-62f7b6827e35".parse()
851 );
852
853 let lt_ref = Ok(AttrVal::LogicalTime(LogicalTime::quaternary(
861 0u64, 0u64, 0u64, 42u64,
862 )));
863 assert_eq!(lt_ref, "0:42".parse());
864 assert_eq!(lt_ref, "0:0:42".parse());
865 assert_eq!(lt_ref, "0:0:0:42".parse());
866
867 assert_eq!(
869 Ok(AttrVal::String("Hello, World!".into())),
870 "\"Hello, World!\"".parse()
871 );
872 assert_eq!(
873 Ok(AttrVal::String("Hello, World!".into())),
874 "'Hello, World!'".parse()
875 );
876 assert_eq!(
877 Ok(AttrVal::String("Hello, World!".into())),
878 "Hello, World!".parse()
879 );
880
881 assert_eq!(Ok(AttrVal::String("".into())), "\"\"".parse());
882 assert_eq!(Ok(AttrVal::String("".into())), "\"".parse());
883
884 assert_eq!(Ok(AttrVal::String("".into())), "''".parse());
885 assert_eq!(Ok(AttrVal::String("".into())), "'".parse());
886
887 assert_eq!(Ok(AttrVal::String("".into())), "".parse());
888 }
889}