1#![doc = document_features::document_features!()]
5pub mod arrow_msg;
21mod entry_id;
22mod entry_name;
23pub mod example_components;
24pub mod hash;
25mod index;
26pub mod path;
27
28mod instance;
32mod vec_deque_ext;
33
34use std::sync::Arc;
35
36use arrow::array::RecordBatch as ArrowRecordBatch;
37use re_build_info::CrateVersion;
38use re_byte_size::SizeBytes;
39
40pub use re_types_core::TimelineName;
41
42pub use self::arrow_msg::{ArrowMsg, ArrowRecordBatchReleaseCallback};
43pub use self::entry_id::{EntryId, EntryIdOrName};
44pub use self::entry_name::{EntryName, InvalidEntryNameError};
45pub use self::index::{
46 AbsoluteTimeRange, AbsoluteTimeRangeF, Duration, NonMinI64, TimeCell, TimeInt, TimePoint,
47 TimeReal, TimeType, Timeline, TimelinePoint, Timestamp, TimestampFormat, TimestampFormatKind,
48 TryFromIntError,
49};
50pub use self::instance::Instance;
51pub use self::path::*;
52pub use self::vec_deque_ext::{VecDequeInsertionExt, VecDequeRemovalExt, VecDequeSortingExt};
53
54pub mod external {
55 pub use {arrow, re_tuid, re_types_core};
56}
57
58#[macro_export]
59macro_rules! impl_into_enum {
60 ($from_ty: ty, $enum_name: ident, $to_enum_variant: ident) => {
61 impl From<$from_ty> for $enum_name {
62 #[inline]
63 fn from(value: $from_ty) -> Self {
64 Self::$to_enum_variant(value)
65 }
66 }
67 };
68}
69
70#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
79#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
80pub enum StoreKind {
81 Recording,
83
84 Blueprint,
86}
87
88impl std::fmt::Display for StoreKind {
89 #[inline]
90 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91 match self {
92 Self::Recording => "Recording".fmt(f),
93 Self::Blueprint => "Blueprint".fmt(f),
94 }
95 }
96}
97
98impl std::str::FromStr for StoreKind {
99 type Err = String;
100
101 fn from_str(s: &str) -> Result<Self, Self::Err> {
102 match s.to_lowercase().as_str() {
103 "recording" => Ok(Self::Recording),
104 "blueprint" => Ok(Self::Blueprint),
105 unknown => Err(format!("{unknown:?} is not a valid StoreKind")),
106 }
107 }
108}
109
110#[test]
111fn store_kind_str_roundtrip() {
112 {
113 let kind = StoreKind::Recording;
114 assert_eq!(kind, kind.to_string().parse().unwrap());
115 }
116
117 {
118 let kind = StoreKind::Blueprint;
119 assert_eq!(kind, kind.to_string().parse().unwrap());
120 }
121}
122
123#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
147#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
148pub struct StoreId {
149 kind: StoreKind,
150 application_id: ApplicationId,
151 recording_id: RecordingId,
152}
153
154impl std::fmt::Debug for StoreId {
156 #[inline]
157 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158 f.debug_tuple("StoreId")
159 .field(&self.kind)
160 .field(&self.application_id.as_str())
161 .field(&self.recording_id.as_str())
162 .finish()
163 }
164}
165
166impl StoreId {
167 #[inline]
168 pub fn new(
169 kind: StoreKind,
170 application_id: impl Into<ApplicationId>,
171 recording_id: impl Into<RecordingId>,
172 ) -> Self {
173 Self {
174 kind,
175 application_id: application_id.into(),
176 recording_id: recording_id.into(),
177 }
178 }
179
180 #[inline]
181 pub fn recording(
182 application_id: impl Into<ApplicationId>,
183 recording_id: impl Into<RecordingId>,
184 ) -> Self {
185 Self::new(StoreKind::Recording, application_id, recording_id)
186 }
187
188 #[inline]
190 pub fn default_blueprint(application_id: ApplicationId) -> Self {
191 let recording_id = application_id.as_recording_id();
192 Self::new(StoreKind::Blueprint, application_id, recording_id)
193 }
194
195 #[inline]
200 pub fn random(kind: StoreKind, application_id: impl Into<ApplicationId>) -> Self {
201 Self {
202 kind,
203 recording_id: RecordingId::random(),
204 application_id: application_id.into(),
205 }
206 }
207
208 #[inline]
209 pub fn empty_recording() -> Self {
210 Self::new(StoreKind::Recording, "<EMPTY>", "<EMPTY>")
211 }
212
213 #[inline]
214 pub fn with_recording_id(self, recording_id: impl Into<RecordingId>) -> Self {
215 Self {
216 kind: self.kind,
217 recording_id: recording_id.into(),
218 application_id: self.application_id,
219 }
220 }
221
222 #[inline]
223 pub fn with_application_id(self, application_id: impl Into<ApplicationId>) -> Self {
224 Self {
225 kind: self.kind,
226 recording_id: self.recording_id,
227 application_id: application_id.into(),
228 }
229 }
230
231 #[inline]
232 pub fn from_uuid(kind: StoreKind, application_id: ApplicationId, uuid: uuid::Uuid) -> Self {
233 Self::new(kind, application_id, uuid.simple().to_string())
234 }
235
236 #[inline]
237 pub fn is_empty_recording(&self) -> bool {
238 self.kind == StoreKind::Recording && self.recording_id.as_str() == "<EMPTY>"
239 }
240
241 #[inline]
242 pub fn is_default_blueprint(&self) -> bool {
243 self.kind == StoreKind::Blueprint
244 && self.application_id.as_str() == self.recording_id.as_str()
245 }
246
247 #[inline]
248 pub fn kind(&self) -> StoreKind {
249 self.kind
250 }
251
252 #[inline]
253 pub fn is_recording(&self) -> bool {
254 self.kind == StoreKind::Recording
255 }
256
257 #[inline]
258 pub fn is_blueprint(&self) -> bool {
259 self.kind == StoreKind::Blueprint
260 }
261
262 #[inline]
263 pub fn recording_id(&self) -> &RecordingId {
264 &self.recording_id
265 }
266
267 #[inline]
268 pub fn application_id(&self) -> &ApplicationId {
269 &self.application_id
270 }
271}
272
273#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
282#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
283pub struct ApplicationId(Arc<String>);
284
285impl From<&str> for ApplicationId {
286 fn from(s: &str) -> Self {
287 Self(Arc::new(s.to_owned()))
288 }
289}
290
291impl From<String> for ApplicationId {
292 fn from(s: String) -> Self {
293 Self(Arc::new(s))
294 }
295}
296
297impl SizeBytes for ApplicationId {
298 #[inline]
299 fn heap_size_bytes(&self) -> u64 {
300 self.0.heap_size_bytes()
301 }
302}
303
304impl ApplicationId {
305 pub fn unknown() -> Self {
309 static UNKNOWN_APP_ID: std::sync::LazyLock<ApplicationId> =
310 std::sync::LazyLock::new(|| ApplicationId(Arc::new("unknown_app_id".to_owned())));
311
312 UNKNOWN_APP_ID.clone()
313 }
314
315 pub fn as_str(&self) -> &str {
316 self.0.as_str()
317 }
318
319 pub fn random() -> Self {
321 Self(Arc::new(format!("app_{}", uuid::Uuid::new_v4().simple())))
322 }
323
324 fn as_recording_id(&self) -> RecordingId {
325 RecordingId(Arc::clone(&self.0))
326 }
327}
328
329impl std::fmt::Display for ApplicationId {
330 #[inline]
331 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
332 self.0.fmt(f)
333 }
334}
335
336#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
344#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
345pub struct RecordingId(Arc<String>);
346
347impl From<&str> for RecordingId {
348 fn from(s: &str) -> Self {
349 Self(Arc::new(s.to_owned()))
350 }
351}
352
353impl From<String> for RecordingId {
354 fn from(s: String) -> Self {
355 Self(Arc::new(s))
356 }
357}
358
359impl SizeBytes for RecordingId {
360 #[inline]
361 fn heap_size_bytes(&self) -> u64 {
362 self.0.heap_size_bytes()
363 }
364}
365
366impl RecordingId {
367 pub fn as_str(&self) -> &str {
368 self.0.as_str()
369 }
370
371 pub fn random() -> Self {
373 Self(Arc::new(format!("rec_{}", uuid::Uuid::new_v4().simple())))
374 }
375}
376
377impl std::fmt::Display for RecordingId {
378 #[inline]
379 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
380 self.0.fmt(f)
381 }
382}
383
384#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
388#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
389pub struct TableId(Arc<String>);
390
391impl TableId {
392 pub fn new(id: String) -> Self {
393 Self(Arc::new(id))
394 }
395
396 pub fn as_str(&self) -> &str {
397 self.0.as_str()
398 }
399}
400
401impl From<&str> for TableId {
402 fn from(s: &str) -> Self {
403 Self(Arc::new(s.into()))
404 }
405}
406
407impl From<String> for TableId {
408 fn from(s: String) -> Self {
409 Self(Arc::new(s))
410 }
411}
412
413impl std::fmt::Display for TableId {
414 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
415 self.0.fmt(f)
416 }
417}
418
419#[derive(Clone, Debug, PartialEq, Eq)] pub struct BlueprintActivationCommand {
432 pub blueprint_id: StoreId,
434
435 pub make_active: bool,
441
442 pub make_default: bool,
448}
449
450impl BlueprintActivationCommand {
451 pub fn make_default(blueprint_id: StoreId) -> Self {
453 Self {
454 blueprint_id,
455 make_active: false,
456 make_default: true,
457 }
458 }
459
460 pub fn make_active(blueprint_id: StoreId) -> Self {
464 Self {
465 blueprint_id,
466 make_active: true,
467 make_default: true,
468 }
469 }
470}
471
472#[must_use]
478#[derive(Clone, Debug, PartialEq)] pub enum LogMsg {
481 SetStoreInfo(SetStoreInfo),
485
486 ArrowMsg(StoreId, ArrowMsg),
490
491 BlueprintActivationCommand(BlueprintActivationCommand),
497}
498
499impl LogMsg {
500 pub fn store_id(&self) -> &StoreId {
501 match self {
502 Self::SetStoreInfo(msg) => &msg.info.store_id,
503 Self::ArrowMsg(store_id, _) => store_id,
504 Self::BlueprintActivationCommand(cmd) => &cmd.blueprint_id,
505 }
506 }
507
508 pub fn set_store_id(&mut self, new_store_id: StoreId) {
509 match self {
510 Self::SetStoreInfo(store_info) => {
511 store_info.info.store_id = new_store_id;
512 }
513 Self::ArrowMsg(store_id, _) => {
514 *store_id = new_store_id;
515 }
516 Self::BlueprintActivationCommand(cmd) => {
517 cmd.blueprint_id = new_store_id;
518 }
519 }
520 }
521
522 pub fn arrow_record_batch_mut(&mut self) -> Option<&mut ArrowRecordBatch> {
525 match self {
526 Self::ArrowMsg(_, arrow_msg) => Some(&mut arrow_msg.batch),
527 _ => None,
528 }
529 }
530
531 pub fn insert_arrow_record_batch_metadata(&mut self, key: String, value: String) {
532 if let Some(record_batch) = self.arrow_record_batch_mut() {
533 record_batch.schema_metadata_mut().insert(key, value);
534 }
535 }
536}
537
538impl_into_enum!(SetStoreInfo, LogMsg, SetStoreInfo);
539impl_into_enum!(
540 BlueprintActivationCommand,
541 LogMsg,
542 BlueprintActivationCommand
543);
544
545#[must_use]
548#[derive(Clone, Debug, PartialEq, Eq)]
549pub struct SetStoreInfo {
550 pub row_id: re_tuid::Tuid,
557
558 pub info: StoreInfo,
559}
560
561#[derive(Clone, Debug, PartialEq, Eq)]
563pub struct StoreInfo {
564 pub store_id: StoreId,
569
570 pub cloned_from: Option<StoreId>,
579
580 pub store_source: StoreSource,
581
582 pub store_version: Option<CrateVersion>,
587}
588
589impl StoreInfo {
590 pub fn new(store_id: StoreId, store_source: StoreSource) -> Self {
592 Self {
593 store_id,
594 cloned_from: None,
595 store_source,
596 store_version: Some(CrateVersion::LOCAL),
597 }
598 }
599
600 pub fn new_unversioned(store_id: StoreId, store_source: StoreSource) -> Self {
602 Self {
603 store_id,
604 cloned_from: None,
605 store_source,
606 store_version: None,
607 }
608 }
609
610 pub fn testing() -> Self {
612 Self::new_unversioned(
614 StoreId::random(StoreKind::Recording, "test_app"),
615 StoreSource::Other("test".to_owned()),
616 )
617 }
618
619 pub fn testing_with_recording_id(recording_id: impl Into<RecordingId>) -> Self {
624 Self::new_unversioned(
626 StoreId::new(StoreKind::Recording, "test_app", recording_id),
627 StoreSource::Other("test".to_owned()),
628 )
629 }
630}
631
632impl StoreInfo {
633 pub fn is_app_default_blueprint(&self) -> bool {
636 self.application_id().as_str() == self.recording_id().as_str()
637 }
638
639 pub fn application_id(&self) -> &ApplicationId {
640 self.store_id.application_id()
641 }
642
643 pub fn recording_id(&self) -> &RecordingId {
644 self.store_id.recording_id()
645 }
646}
647
648#[derive(Clone, PartialEq, Eq)]
649pub struct PythonVersion {
650 pub major: u8,
652
653 pub minor: u8,
655
656 pub patch: u8,
658
659 pub suffix: String,
661}
662
663impl std::fmt::Debug for PythonVersion {
664 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
665 std::fmt::Display::fmt(self, f)
666 }
667}
668
669impl std::fmt::Display for PythonVersion {
670 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
671 let Self {
672 major,
673 minor,
674 patch,
675 suffix,
676 } = self;
677 write!(f, "{major}.{minor}.{patch}{suffix}")
678 }
679}
680
681impl std::str::FromStr for PythonVersion {
682 type Err = PythonVersionParseError;
683
684 fn from_str(s: &str) -> Result<Self, Self::Err> {
685 if s.is_empty() {
686 return Err(PythonVersionParseError::MissingMajor);
687 }
688 let (major, rest) = s
689 .split_once('.')
690 .ok_or(PythonVersionParseError::MissingMinor)?;
691 if rest.is_empty() {
692 return Err(PythonVersionParseError::MissingMinor);
693 }
694 let (minor, rest) = rest
695 .split_once('.')
696 .ok_or(PythonVersionParseError::MissingPatch)?;
697 if rest.is_empty() {
698 return Err(PythonVersionParseError::MissingPatch);
699 }
700 let pos = rest.bytes().position(|v| !v.is_ascii_digit());
701 let (patch, suffix) = match pos {
702 Some(pos) => rest.split_at(pos),
703 None => (rest, ""),
704 };
705
706 Ok(Self {
707 major: major
708 .parse()
709 .map_err(PythonVersionParseError::InvalidMajor)?,
710 minor: minor
711 .parse()
712 .map_err(PythonVersionParseError::InvalidMinor)?,
713 patch: patch
714 .parse()
715 .map_err(PythonVersionParseError::InvalidPatch)?,
716 suffix: suffix.into(),
717 })
718 }
719}
720
721#[derive(Debug, thiserror::Error)]
722pub enum PythonVersionParseError {
723 #[error("missing major version")]
724 MissingMajor,
725
726 #[error("missing minor version")]
727 MissingMinor,
728
729 #[error("missing patch version")]
730 MissingPatch,
731
732 #[error("invalid major version: {0}")]
733 InvalidMajor(std::num::ParseIntError),
734
735 #[error("invalid minor version: {0}")]
736 InvalidMinor(std::num::ParseIntError),
737
738 #[error("invalid patch version: {0}")]
739 InvalidPatch(std::num::ParseIntError),
740}
741
742#[derive(Clone, Debug, PartialEq, Eq, Hash)]
743pub enum FileSource {
744 Cli,
745
746 Uri,
748
749 DragAndDrop {
750 recommended_store_id: Option<StoreId>,
753
754 force_store_info: bool,
759 },
760
761 FileDialog {
762 recommended_store_id: Option<StoreId>,
765
766 force_store_info: bool,
771 },
772
773 Sdk,
774}
775
776impl FileSource {
777 pub fn recommended_store_id(&self) -> Option<&StoreId> {
778 match self {
779 Self::FileDialog {
780 recommended_store_id,
781 ..
782 }
783 | Self::DragAndDrop {
784 recommended_store_id,
785 ..
786 } => recommended_store_id.as_ref(),
787 Self::Cli | Self::Uri | Self::Sdk => None,
788 }
789 }
790
791 #[inline]
792 pub fn force_store_info(&self) -> bool {
793 match self {
794 Self::FileDialog {
795 force_store_info, ..
796 }
797 | Self::DragAndDrop {
798 force_store_info, ..
799 } => *force_store_info,
800 Self::Cli | Self::Uri | Self::Sdk => false,
801 }
802 }
803}
804
805#[derive(Clone, Debug, PartialEq, Eq)]
807pub enum StoreSource {
808 Unknown,
809
810 CSdk,
812
813 PythonSdk(PythonVersion),
815
816 RustSdk {
818 rustc_version: String,
820
821 llvm_version: String,
823 },
824
825 File {
827 file_source: FileSource,
828 },
829
830 Viewer,
832
833 Other(String),
835}
836
837impl std::fmt::Display for StoreSource {
838 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
839 match self {
840 Self::Unknown => "Unknown".fmt(f),
841 Self::CSdk => "C SDK".fmt(f),
842 Self::PythonSdk(version) => write!(f, "Python {version} SDK"),
843 Self::RustSdk { rustc_version, .. } => write!(f, "Rust SDK (rustc {rustc_version})"),
844 Self::File { file_source, .. } => match file_source {
845 FileSource::Cli => write!(f, "File via CLI"),
846 FileSource::Uri => write!(f, "File via URI"),
847 FileSource::DragAndDrop { .. } => write!(f, "File via drag-and-drop"),
848 FileSource::FileDialog { .. } => write!(f, "File via file dialog"),
849 FileSource::Sdk => write!(f, "File via SDK"),
850 },
851 Self::Viewer => write!(f, "Viewer-generated"),
852 Self::Other(string) => format!("{string:?}").fmt(f), }
854 }
855}
856
857#[must_use]
868#[derive(Clone, Debug, PartialEq)]
869pub struct TableMsg {
870 pub id: TableId,
872
873 pub data: ArrowRecordBatch,
875}
876
877impl re_byte_size::SizeBytes for TableMsg {
878 #[inline]
879 fn heap_size_bytes(&self) -> u64 {
880 let Self { id: _, data } = self;
881
882 data.heap_size_bytes()
883 }
884}
885
886impl TableMsg {
887 pub fn insert_arrow_record_batch_metadata(&mut self, key: String, value: String) {
888 self.data.schema_metadata_mut().insert(key, value);
889 }
890}
891
892#[inline]
896pub fn build_log_time(log_time: Timestamp) -> (Timeline, TimeInt) {
897 (
898 Timeline::log_time(),
899 TimeInt::new_temporal(log_time.nanos_since_epoch()),
900 )
901}
902
903#[inline]
905pub fn build_frame_nr(frame_nr: impl TryInto<TimeInt>) -> (Timeline, TimeInt) {
906 (
907 Timeline::new("frame_nr", TimeType::Sequence),
908 TimeInt::saturated_temporal(frame_nr),
909 )
910}
911
912#[inline]
913pub fn build_index_value(value: impl TryInto<TimeInt>, time_type: TimeType) -> (Timeline, TimeInt) {
914 let timeline_name = match time_type {
915 TimeType::Sequence => "frame_nr",
916 TimeType::DurationNs => "duration",
917 TimeType::TimestampNs => "timestamp",
918 };
919
920 (
921 Timeline::new(timeline_name, time_type),
922 TimeInt::saturated_temporal(value),
923 )
924}
925
926impl SizeBytes for StoreId {
927 #[inline]
928 fn heap_size_bytes(&self) -> u64 {
929 let Self {
930 kind: _,
931 recording_id: id,
932 application_id,
933 } = self;
934
935 id.heap_size_bytes() + application_id.heap_size_bytes()
936 }
937}
938
939impl SizeBytes for PythonVersion {
940 #[inline]
941 fn heap_size_bytes(&self) -> u64 {
942 let Self {
943 major: _,
944 minor: _,
945 patch: _,
946 suffix,
947 } = self;
948
949 suffix.heap_size_bytes()
950 }
951}
952
953impl SizeBytes for FileSource {
954 #[inline]
955 fn heap_size_bytes(&self) -> u64 {
956 match self {
957 Self::Uri | Self::Sdk | Self::Cli => 0,
958 Self::DragAndDrop {
959 recommended_store_id,
960 force_store_info,
961 }
962 | Self::FileDialog {
963 recommended_store_id,
964 force_store_info,
965 } => recommended_store_id.heap_size_bytes() + force_store_info.heap_size_bytes(),
966 }
967 }
968}
969
970impl SizeBytes for StoreSource {
971 #[inline]
972 fn heap_size_bytes(&self) -> u64 {
973 match self {
974 Self::Unknown | Self::CSdk | Self::Viewer => 0,
975 Self::PythonSdk(python_version) => python_version.heap_size_bytes(),
976 Self::RustSdk {
977 rustc_version,
978 llvm_version,
979 } => rustc_version.heap_size_bytes() + llvm_version.heap_size_bytes(),
980 Self::File { file_source } => file_source.heap_size_bytes(),
981 Self::Other(description) => description.heap_size_bytes(),
982 }
983 }
984}
985
986impl SizeBytes for StoreInfo {
987 #[inline]
988 fn heap_size_bytes(&self) -> u64 {
989 let Self {
990 store_id,
991 cloned_from: _,
992 store_source,
993 store_version,
994 } = self;
995
996 store_id.heap_size_bytes()
997 + store_source.heap_size_bytes()
998 + store_version.heap_size_bytes()
999 }
1000}
1001
1002impl SizeBytes for SetStoreInfo {
1003 #[inline]
1004 fn heap_size_bytes(&self) -> u64 {
1005 let Self { row_id, info } = self;
1006
1007 row_id.heap_size_bytes() + info.heap_size_bytes()
1008 }
1009}
1010
1011impl SizeBytes for BlueprintActivationCommand {
1012 #[inline]
1013 fn heap_size_bytes(&self) -> u64 {
1014 0
1015 }
1016}
1017
1018impl SizeBytes for ArrowMsg {
1019 #[inline]
1020 fn heap_size_bytes(&self) -> u64 {
1021 let Self {
1022 chunk_id,
1023 batch,
1024 on_release: _,
1025 } = self;
1026
1027 chunk_id.heap_size_bytes() + batch.heap_size_bytes()
1028 }
1029}
1030
1031impl SizeBytes for LogMsg {
1032 #[inline]
1033 fn heap_size_bytes(&self) -> u64 {
1034 match self {
1035 Self::SetStoreInfo(set_store_info) => set_store_info.heap_size_bytes(),
1036 Self::ArrowMsg(store_id, arrow_msg) => {
1037 store_id.heap_size_bytes() + arrow_msg.heap_size_bytes()
1038 }
1039 Self::BlueprintActivationCommand(blueprint_activation_command) => {
1040 blueprint_activation_command.heap_size_bytes()
1041 }
1042 }
1043 }
1044}
1045
1046#[cfg(test)]
1049mod tests {
1050 use super::*;
1051
1052 #[test]
1053 fn parse_python_version() {
1054 macro_rules! assert_parse_err {
1055 ($input:literal, $expected:pat) => {
1056 let actual = $input.parse::<PythonVersion>();
1057
1058 assert!(
1059 matches!(actual, Err($expected)),
1060 "actual: {actual:?}, expected: {}",
1061 stringify!($expected)
1062 );
1063 };
1064 }
1065
1066 macro_rules! assert_parse_ok {
1067 ($input:literal, $expected:expr) => {
1068 let actual = $input.parse::<PythonVersion>().expect("failed to parse");
1069 assert_eq!(actual, $expected);
1070 };
1071 }
1072
1073 assert_parse_err!("", PythonVersionParseError::MissingMajor);
1074 assert_parse_err!("3", PythonVersionParseError::MissingMinor);
1075 assert_parse_err!("3.", PythonVersionParseError::MissingMinor);
1076 assert_parse_err!("3.11", PythonVersionParseError::MissingPatch);
1077 assert_parse_err!("3.11.", PythonVersionParseError::MissingPatch);
1078 assert_parse_err!("a.11.0", PythonVersionParseError::InvalidMajor(_));
1079 assert_parse_err!("3.b.0", PythonVersionParseError::InvalidMinor(_));
1080 assert_parse_err!("3.11.c", PythonVersionParseError::InvalidPatch(_));
1081 assert_parse_ok!(
1082 "3.11.0",
1083 PythonVersion {
1084 major: 3,
1085 minor: 11,
1086 patch: 0,
1087 suffix: String::new(),
1088 }
1089 );
1090 assert_parse_ok!(
1091 "3.11.0a1",
1092 PythonVersion {
1093 major: 3,
1094 minor: 11,
1095 patch: 0,
1096 suffix: "a1".to_owned(),
1097 }
1098 );
1099 }
1100}