1use crate::{AnnotationSet, Client, Dataset, Error, Sample, client};
5use chrono::{DateTime, Utc};
6use log::trace;
7use reqwest::multipart::{Form, Part};
8use serde::{Deserialize, Deserializer, Serialize};
9use std::{collections::HashMap, fmt::Display, path::PathBuf, str::FromStr};
10
11#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
41#[serde(untagged)]
42pub enum Parameter {
43 Integer(i64),
45 Real(f64),
47 Boolean(bool),
49 String(String),
51 Array(Vec<Parameter>),
53 Object(HashMap<String, Parameter>),
55}
56
57#[derive(Deserialize)]
58pub struct LoginResult {
59 pub(crate) token: String,
60}
61
62#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
82pub struct OrganizationID(u64);
83
84impl Display for OrganizationID {
85 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
86 write!(f, "org-{:x}", self.0)
87 }
88}
89
90impl From<u64> for OrganizationID {
91 fn from(id: u64) -> Self {
92 OrganizationID(id)
93 }
94}
95
96impl From<OrganizationID> for u64 {
97 fn from(val: OrganizationID) -> Self {
98 val.0
99 }
100}
101
102impl OrganizationID {
103 pub fn value(&self) -> u64 {
104 self.0
105 }
106}
107
108impl TryFrom<&str> for OrganizationID {
109 type Error = Error;
110
111 fn try_from(s: &str) -> Result<Self, Self::Error> {
112 let hex_part = s.strip_prefix("org-").ok_or_else(|| {
113 Error::InvalidParameters("Organization ID must start with 'org-' prefix".to_string())
114 })?;
115 let id = u64::from_str_radix(hex_part, 16)?;
116 Ok(OrganizationID(id))
117 }
118}
119
120#[derive(Deserialize, Clone, Debug)]
141pub struct Organization {
142 id: OrganizationID,
143 name: String,
144 #[serde(rename = "latest_credit")]
145 credits: i64,
146}
147
148impl Display for Organization {
149 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
150 write!(f, "{}", self.name())
151 }
152}
153
154impl Organization {
155 pub fn id(&self) -> OrganizationID {
156 self.id
157 }
158
159 pub fn name(&self) -> &str {
160 &self.name
161 }
162
163 pub fn credits(&self) -> i64 {
164 self.credits
165 }
166}
167
168#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
189pub struct ProjectID(u64);
190
191impl Display for ProjectID {
192 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
193 write!(f, "p-{:x}", self.0)
194 }
195}
196
197impl From<u64> for ProjectID {
198 fn from(id: u64) -> Self {
199 ProjectID(id)
200 }
201}
202
203impl From<ProjectID> for u64 {
204 fn from(val: ProjectID) -> Self {
205 val.0
206 }
207}
208
209impl ProjectID {
210 pub fn value(&self) -> u64 {
211 self.0
212 }
213}
214
215impl TryFrom<&str> for ProjectID {
216 type Error = Error;
217
218 fn try_from(s: &str) -> Result<Self, Self::Error> {
219 ProjectID::from_str(s)
220 }
221}
222
223impl TryFrom<String> for ProjectID {
224 type Error = Error;
225
226 fn try_from(s: String) -> Result<Self, Self::Error> {
227 ProjectID::from_str(&s)
228 }
229}
230
231impl FromStr for ProjectID {
232 type Err = Error;
233
234 fn from_str(s: &str) -> Result<Self, Self::Err> {
235 let hex_part = s.strip_prefix("p-").ok_or_else(|| {
236 Error::InvalidParameters("Project ID must start with 'p-' prefix".to_string())
237 })?;
238 let id = u64::from_str_radix(hex_part, 16)?;
239 Ok(ProjectID(id))
240 }
241}
242
243#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
264pub struct ExperimentID(u64);
265
266impl Display for ExperimentID {
267 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
268 write!(f, "exp-{:x}", self.0)
269 }
270}
271
272impl From<u64> for ExperimentID {
273 fn from(id: u64) -> Self {
274 ExperimentID(id)
275 }
276}
277
278impl From<ExperimentID> for u64 {
279 fn from(val: ExperimentID) -> Self {
280 val.0
281 }
282}
283
284impl ExperimentID {
285 pub fn value(&self) -> u64 {
286 self.0
287 }
288}
289
290impl TryFrom<&str> for ExperimentID {
291 type Error = Error;
292
293 fn try_from(s: &str) -> Result<Self, Self::Error> {
294 ExperimentID::from_str(s)
295 }
296}
297
298impl TryFrom<String> for ExperimentID {
299 type Error = Error;
300
301 fn try_from(s: String) -> Result<Self, Self::Error> {
302 ExperimentID::from_str(&s)
303 }
304}
305
306impl FromStr for ExperimentID {
307 type Err = Error;
308
309 fn from_str(s: &str) -> Result<Self, Self::Err> {
310 let hex_part = s.strip_prefix("exp-").ok_or_else(|| {
311 Error::InvalidParameters("Experiment ID must start with 'exp-' prefix".to_string())
312 })?;
313 let id = u64::from_str_radix(hex_part, 16)?;
314 Ok(ExperimentID(id))
315 }
316}
317
318#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
339pub struct TrainingSessionID(u64);
340
341impl Display for TrainingSessionID {
342 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
343 write!(f, "t-{:x}", self.0)
344 }
345}
346
347impl From<u64> for TrainingSessionID {
348 fn from(id: u64) -> Self {
349 TrainingSessionID(id)
350 }
351}
352
353impl From<TrainingSessionID> for u64 {
354 fn from(val: TrainingSessionID) -> Self {
355 val.0
356 }
357}
358
359impl TrainingSessionID {
360 pub fn value(&self) -> u64 {
361 self.0
362 }
363}
364
365impl TryFrom<&str> for TrainingSessionID {
366 type Error = Error;
367
368 fn try_from(s: &str) -> Result<Self, Self::Error> {
369 TrainingSessionID::from_str(s)
370 }
371}
372
373impl TryFrom<String> for TrainingSessionID {
374 type Error = Error;
375
376 fn try_from(s: String) -> Result<Self, Self::Error> {
377 TrainingSessionID::from_str(&s)
378 }
379}
380
381impl FromStr for TrainingSessionID {
382 type Err = Error;
383
384 fn from_str(s: &str) -> Result<Self, Self::Err> {
385 let hex_part = s.strip_prefix("t-").ok_or_else(|| {
386 Error::InvalidParameters("Training Session ID must start with 't-' prefix".to_string())
387 })?;
388 let id = u64::from_str_radix(hex_part, 16)?;
389 Ok(TrainingSessionID(id))
390 }
391}
392
393#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
413pub struct ValidationSessionID(u64);
414
415impl Display for ValidationSessionID {
416 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
417 write!(f, "v-{:x}", self.0)
418 }
419}
420
421impl From<u64> for ValidationSessionID {
422 fn from(id: u64) -> Self {
423 ValidationSessionID(id)
424 }
425}
426
427impl From<ValidationSessionID> for u64 {
428 fn from(val: ValidationSessionID) -> Self {
429 val.0
430 }
431}
432
433impl ValidationSessionID {
434 pub fn value(&self) -> u64 {
435 self.0
436 }
437}
438
439impl TryFrom<&str> for ValidationSessionID {
440 type Error = Error;
441
442 fn try_from(s: &str) -> Result<Self, Self::Error> {
443 let hex_part = s.strip_prefix("v-").ok_or_else(|| {
444 Error::InvalidParameters(
445 "Validation Session ID must start with 'v-' prefix".to_string(),
446 )
447 })?;
448 let id = u64::from_str_radix(hex_part, 16)?;
449 Ok(ValidationSessionID(id))
450 }
451}
452
453impl TryFrom<String> for ValidationSessionID {
454 type Error = Error;
455
456 fn try_from(s: String) -> Result<Self, Self::Error> {
457 ValidationSessionID::try_from(s.as_str())
458 }
459}
460
461#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
462pub struct SnapshotID(u64);
463
464impl Display for SnapshotID {
465 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
466 write!(f, "ss-{:x}", self.0)
467 }
468}
469
470impl From<u64> for SnapshotID {
471 fn from(id: u64) -> Self {
472 SnapshotID(id)
473 }
474}
475
476impl From<SnapshotID> for u64 {
477 fn from(val: SnapshotID) -> Self {
478 val.0
479 }
480}
481
482impl SnapshotID {
483 pub fn value(&self) -> u64 {
484 self.0
485 }
486}
487
488impl TryFrom<&str> for SnapshotID {
489 type Error = Error;
490
491 fn try_from(s: &str) -> Result<Self, Self::Error> {
492 let hex_part = s.strip_prefix("ss-").ok_or_else(|| {
493 Error::InvalidParameters("Snapshot ID must start with 'ss-' prefix".to_string())
494 })?;
495 let id = u64::from_str_radix(hex_part, 16)?;
496 Ok(SnapshotID(id))
497 }
498}
499
500#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
501pub struct TaskID(u64);
502
503impl Display for TaskID {
504 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
505 write!(f, "task-{:x}", self.0)
506 }
507}
508
509impl From<u64> for TaskID {
510 fn from(id: u64) -> Self {
511 TaskID(id)
512 }
513}
514
515impl From<TaskID> for u64 {
516 fn from(val: TaskID) -> Self {
517 val.0
518 }
519}
520
521impl TaskID {
522 pub fn value(&self) -> u64 {
523 self.0
524 }
525}
526
527impl TryFrom<&str> for TaskID {
528 type Error = Error;
529
530 fn try_from(s: &str) -> Result<Self, Self::Error> {
531 TaskID::from_str(s)
532 }
533}
534
535impl TryFrom<String> for TaskID {
536 type Error = Error;
537
538 fn try_from(s: String) -> Result<Self, Self::Error> {
539 TaskID::from_str(&s)
540 }
541}
542
543impl FromStr for TaskID {
544 type Err = Error;
545
546 fn from_str(s: &str) -> Result<Self, Self::Err> {
547 let hex_part = s.strip_prefix("task-").ok_or_else(|| {
548 Error::InvalidParameters("Task ID must start with 'task-' prefix".to_string())
549 })?;
550 let id = u64::from_str_radix(hex_part, 16)?;
551 Ok(TaskID(id))
552 }
553}
554
555#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
576pub struct DatasetID(u64);
577
578impl Display for DatasetID {
579 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
580 write!(f, "ds-{:x}", self.0)
581 }
582}
583
584impl From<u64> for DatasetID {
585 fn from(id: u64) -> Self {
586 DatasetID(id)
587 }
588}
589
590impl From<DatasetID> for u64 {
591 fn from(val: DatasetID) -> Self {
592 val.0
593 }
594}
595
596impl DatasetID {
597 pub fn value(&self) -> u64 {
598 self.0
599 }
600}
601
602impl TryFrom<&str> for DatasetID {
603 type Error = Error;
604
605 fn try_from(s: &str) -> Result<Self, Self::Error> {
606 DatasetID::from_str(s)
607 }
608}
609
610impl TryFrom<String> for DatasetID {
611 type Error = Error;
612
613 fn try_from(s: String) -> Result<Self, Self::Error> {
614 DatasetID::from_str(&s)
615 }
616}
617
618impl FromStr for DatasetID {
619 type Err = Error;
620
621 fn from_str(s: &str) -> Result<Self, Self::Err> {
622 let hex_part = s.strip_prefix("ds-").ok_or_else(|| {
623 Error::InvalidParameters("Dataset ID must start with 'ds-' prefix".to_string())
624 })?;
625 let id = u64::from_str_radix(hex_part, 16)?;
626 Ok(DatasetID(id))
627 }
628}
629
630#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
631pub struct AnnotationSetID(u64);
632
633impl Display for AnnotationSetID {
634 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
635 write!(f, "as-{:x}", self.0)
636 }
637}
638
639impl From<u64> for AnnotationSetID {
640 fn from(id: u64) -> Self {
641 AnnotationSetID(id)
642 }
643}
644
645impl From<AnnotationSetID> for u64 {
646 fn from(val: AnnotationSetID) -> Self {
647 val.0
648 }
649}
650
651impl AnnotationSetID {
652 pub fn value(&self) -> u64 {
653 self.0
654 }
655}
656
657impl TryFrom<&str> for AnnotationSetID {
658 type Error = Error;
659
660 fn try_from(s: &str) -> Result<Self, Self::Error> {
661 AnnotationSetID::from_str(s)
662 }
663}
664
665impl TryFrom<String> for AnnotationSetID {
666 type Error = Error;
667
668 fn try_from(s: String) -> Result<Self, Self::Error> {
669 AnnotationSetID::from_str(&s)
670 }
671}
672
673impl FromStr for AnnotationSetID {
674 type Err = Error;
675
676 fn from_str(s: &str) -> Result<Self, Self::Err> {
677 let hex_part = s.strip_prefix("as-").ok_or_else(|| {
678 Error::InvalidParameters("Annotation Set ID must start with 'as-' prefix".to_string())
679 })?;
680 let id = u64::from_str_radix(hex_part, 16)?;
681 Ok(AnnotationSetID(id))
682 }
683}
684
685#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
686pub struct SampleID(u64);
687
688impl Display for SampleID {
689 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
690 write!(f, "s-{:x}", self.0)
691 }
692}
693
694impl From<u64> for SampleID {
695 fn from(id: u64) -> Self {
696 SampleID(id)
697 }
698}
699
700impl From<SampleID> for u64 {
701 fn from(val: SampleID) -> Self {
702 val.0
703 }
704}
705
706impl SampleID {
707 pub fn value(&self) -> u64 {
708 self.0
709 }
710}
711
712impl TryFrom<&str> for SampleID {
713 type Error = Error;
714
715 fn try_from(s: &str) -> Result<Self, Self::Error> {
716 let hex_part = s.strip_prefix("s-").ok_or_else(|| {
717 Error::InvalidParameters("Sample ID must start with 's-' prefix".to_string())
718 })?;
719 let id = u64::from_str_radix(hex_part, 16)?;
720 Ok(SampleID(id))
721 }
722}
723
724#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
725pub struct AppId(u64);
726
727impl Display for AppId {
728 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
729 write!(f, "app-{:x}", self.0)
730 }
731}
732
733impl From<u64> for AppId {
734 fn from(id: u64) -> Self {
735 AppId(id)
736 }
737}
738
739impl From<AppId> for u64 {
740 fn from(val: AppId) -> Self {
741 val.0
742 }
743}
744
745impl AppId {
746 pub fn value(&self) -> u64 {
747 self.0
748 }
749}
750
751impl TryFrom<&str> for AppId {
752 type Error = Error;
753
754 fn try_from(s: &str) -> Result<Self, Self::Error> {
755 let hex_part = s.strip_prefix("app-").ok_or_else(|| {
756 Error::InvalidParameters("App ID must start with 'app-' prefix".to_string())
757 })?;
758 let id = u64::from_str_radix(hex_part, 16)?;
759 Ok(AppId(id))
760 }
761}
762
763#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
764pub struct ImageId(u64);
765
766impl Display for ImageId {
767 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
768 write!(f, "im-{:x}", self.0)
769 }
770}
771
772impl From<u64> for ImageId {
773 fn from(id: u64) -> Self {
774 ImageId(id)
775 }
776}
777
778impl From<ImageId> for u64 {
779 fn from(val: ImageId) -> Self {
780 val.0
781 }
782}
783
784impl ImageId {
785 pub fn value(&self) -> u64 {
786 self.0
787 }
788}
789
790impl TryFrom<&str> for ImageId {
791 type Error = Error;
792
793 fn try_from(s: &str) -> Result<Self, Self::Error> {
794 let hex_part = s.strip_prefix("im-").ok_or_else(|| {
795 Error::InvalidParameters("Image ID must start with 'im-' prefix".to_string())
796 })?;
797 let id = u64::from_str_radix(hex_part, 16)?;
798 Ok(ImageId(id))
799 }
800}
801
802#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
803pub struct SequenceId(u64);
804
805impl Display for SequenceId {
806 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
807 write!(f, "se-{:x}", self.0)
808 }
809}
810
811impl From<u64> for SequenceId {
812 fn from(id: u64) -> Self {
813 SequenceId(id)
814 }
815}
816
817impl From<SequenceId> for u64 {
818 fn from(val: SequenceId) -> Self {
819 val.0
820 }
821}
822
823impl SequenceId {
824 pub fn value(&self) -> u64 {
825 self.0
826 }
827}
828
829impl TryFrom<&str> for SequenceId {
830 type Error = Error;
831
832 fn try_from(s: &str) -> Result<Self, Self::Error> {
833 let hex_part = s.strip_prefix("se-").ok_or_else(|| {
834 Error::InvalidParameters("Sequence ID must start with 'se-' prefix".to_string())
835 })?;
836 let id = u64::from_str_radix(hex_part, 16)?;
837 Ok(SequenceId(id))
838 }
839}
840
841#[derive(Deserialize, Clone, Debug)]
845pub struct Project {
846 id: ProjectID,
847 name: String,
848 description: String,
849}
850
851impl Display for Project {
852 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
853 write!(f, "{} {}", self.id(), self.name())
854 }
855}
856
857impl Project {
858 pub fn id(&self) -> ProjectID {
859 self.id
860 }
861
862 pub fn name(&self) -> &str {
863 &self.name
864 }
865
866 pub fn description(&self) -> &str {
867 &self.description
868 }
869
870 pub async fn datasets(
871 &self,
872 client: &client::Client,
873 name: Option<&str>,
874 ) -> Result<Vec<Dataset>, Error> {
875 client.datasets(self.id, name).await
876 }
877
878 pub async fn experiments(
879 &self,
880 client: &client::Client,
881 name: Option<&str>,
882 ) -> Result<Vec<Experiment>, Error> {
883 client.experiments(self.id, name).await
884 }
885}
886
887#[derive(Deserialize, Debug)]
888pub struct SamplesCountResult {
889 pub total: u64,
890}
891
892#[derive(Serialize, Clone, Debug)]
893pub struct SamplesListParams {
894 pub dataset_id: DatasetID,
895 #[serde(skip_serializing_if = "Option::is_none")]
896 pub annotation_set_id: Option<AnnotationSetID>,
897 #[serde(skip_serializing_if = "Option::is_none")]
898 pub continue_token: Option<String>,
899 #[serde(skip_serializing_if = "Vec::is_empty")]
900 pub types: Vec<String>,
901 #[serde(skip_serializing_if = "Vec::is_empty")]
902 pub group_names: Vec<String>,
903}
904
905#[derive(Deserialize, Debug)]
906pub struct SamplesListResult {
907 pub samples: Vec<Sample>,
908 pub continue_token: Option<String>,
909}
910
911#[derive(Serialize, Clone, Debug)]
916pub struct SamplesPopulateParams {
917 pub dataset_id: DatasetID,
918 #[serde(skip_serializing_if = "Option::is_none")]
919 pub annotation_set_id: Option<AnnotationSetID>,
920 #[serde(skip_serializing_if = "Option::is_none")]
921 pub presigned_urls: Option<bool>,
922 pub samples: Vec<Sample>,
923}
924
925#[derive(Deserialize, Debug)]
931pub struct SamplesPopulateResult {
932 pub uuid: String,
934 pub urls: Vec<PresignedUrl>,
936}
937
938#[derive(Deserialize, Debug, Clone)]
940pub struct PresignedUrl {
941 pub filename: String,
943 pub key: String,
945 pub url: String,
947}
948
949#[derive(Deserialize)]
950pub struct Snapshot {
951 id: SnapshotID,
952 description: String,
953 status: String,
954 path: String,
955 #[serde(rename = "date")]
956 created: DateTime<Utc>,
957}
958
959impl Display for Snapshot {
960 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
961 write!(f, "{} {}", self.id, self.description)
962 }
963}
964
965impl Snapshot {
966 pub fn id(&self) -> SnapshotID {
967 self.id
968 }
969
970 pub fn description(&self) -> &str {
971 &self.description
972 }
973
974 pub fn status(&self) -> &str {
975 &self.status
976 }
977
978 pub fn path(&self) -> &str {
979 &self.path
980 }
981
982 pub fn created(&self) -> &DateTime<Utc> {
983 &self.created
984 }
985}
986
987#[derive(Serialize, Debug)]
988pub struct SnapshotRestore {
989 pub project_id: ProjectID,
990 pub snapshot_id: SnapshotID,
991 pub fps: u64,
992 #[serde(rename = "enabled_topics", skip_serializing_if = "Vec::is_empty")]
993 pub topics: Vec<String>,
994 #[serde(rename = "label_names", skip_serializing_if = "Vec::is_empty")]
995 pub autolabel: Vec<String>,
996 #[serde(rename = "depth_gen")]
997 pub autodepth: bool,
998 pub agtg_pipeline: bool,
999 #[serde(skip_serializing_if = "Option::is_none")]
1000 pub dataset_name: Option<String>,
1001 #[serde(skip_serializing_if = "Option::is_none")]
1002 pub dataset_description: Option<String>,
1003}
1004
1005#[derive(Deserialize, Debug)]
1006pub struct SnapshotRestoreResult {
1007 pub id: SnapshotID,
1008 pub description: String,
1009 pub dataset_name: String,
1010 pub dataset_id: DatasetID,
1011 pub annotation_set_id: AnnotationSetID,
1012 #[serde(default)]
1013 pub task_id: Option<TaskID>,
1014 pub date: DateTime<Utc>,
1015}
1016
1017#[derive(Serialize, Debug)]
1022pub struct SnapshotCreateFromDataset {
1023 pub description: String,
1025 pub dataset_id: DatasetID,
1027 pub annotation_set_id: AnnotationSetID,
1029}
1030
1031#[derive(Deserialize, Debug)]
1035pub struct SnapshotFromDatasetResult {
1036 #[serde(alias = "snapshot_id")]
1038 pub id: SnapshotID,
1039 #[serde(default)]
1041 pub task_id: Option<TaskID>,
1042}
1043
1044#[derive(Deserialize)]
1045pub struct Experiment {
1046 id: ExperimentID,
1047 project_id: ProjectID,
1048 name: String,
1049 description: String,
1050}
1051
1052impl Display for Experiment {
1053 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1054 write!(f, "{} {}", self.id, self.name)
1055 }
1056}
1057
1058impl Experiment {
1059 pub fn id(&self) -> ExperimentID {
1060 self.id
1061 }
1062
1063 pub fn project_id(&self) -> ProjectID {
1064 self.project_id
1065 }
1066
1067 pub fn name(&self) -> &str {
1068 &self.name
1069 }
1070
1071 pub fn description(&self) -> &str {
1072 &self.description
1073 }
1074
1075 pub async fn project(&self, client: &client::Client) -> Result<Project, Error> {
1076 client.project(self.project_id).await
1077 }
1078
1079 pub async fn training_sessions(
1080 &self,
1081 client: &client::Client,
1082 name: Option<&str>,
1083 ) -> Result<Vec<TrainingSession>, Error> {
1084 client.training_sessions(self.id, name).await
1085 }
1086}
1087
1088#[derive(Serialize, Debug)]
1089pub struct PublishMetrics {
1090 #[serde(rename = "trainer_session_id", skip_serializing_if = "Option::is_none")]
1091 pub trainer_session_id: Option<TrainingSessionID>,
1092 #[serde(
1093 rename = "validate_session_id",
1094 skip_serializing_if = "Option::is_none"
1095 )]
1096 pub validate_session_id: Option<ValidationSessionID>,
1097 pub metrics: HashMap<String, Parameter>,
1098}
1099
1100#[derive(Deserialize)]
1101struct TrainingSessionParams {
1102 model_params: HashMap<String, Parameter>,
1103 dataset_params: DatasetParams,
1104}
1105
1106#[derive(Deserialize)]
1107pub struct TrainingSession {
1108 id: TrainingSessionID,
1109 #[serde(rename = "trainer_id")]
1110 experiment_id: ExperimentID,
1111 model: String,
1112 name: String,
1113 description: String,
1114 params: TrainingSessionParams,
1115 #[serde(rename = "docker_task")]
1116 task: Task,
1117}
1118
1119impl Display for TrainingSession {
1120 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1121 write!(f, "{} {}", self.id, self.name())
1122 }
1123}
1124
1125impl TrainingSession {
1126 pub fn id(&self) -> TrainingSessionID {
1127 self.id
1128 }
1129
1130 pub fn name(&self) -> &str {
1131 &self.name
1132 }
1133
1134 pub fn description(&self) -> &str {
1135 &self.description
1136 }
1137
1138 pub fn model(&self) -> &str {
1139 &self.model
1140 }
1141
1142 pub fn experiment_id(&self) -> ExperimentID {
1143 self.experiment_id
1144 }
1145
1146 pub fn task(&self) -> Task {
1147 self.task.clone()
1148 }
1149
1150 pub fn model_params(&self) -> &HashMap<String, Parameter> {
1151 &self.params.model_params
1152 }
1153
1154 pub fn dataset_params(&self) -> &DatasetParams {
1155 &self.params.dataset_params
1156 }
1157
1158 pub fn train_group(&self) -> &str {
1159 &self.params.dataset_params.train_group
1160 }
1161
1162 pub fn val_group(&self) -> &str {
1163 &self.params.dataset_params.val_group
1164 }
1165
1166 pub async fn experiment(&self, client: &client::Client) -> Result<Experiment, Error> {
1167 client.experiment(self.experiment_id).await
1168 }
1169
1170 pub async fn dataset(&self, client: &client::Client) -> Result<Dataset, Error> {
1171 client.dataset(self.params.dataset_params.dataset_id).await
1172 }
1173
1174 pub async fn annotation_set(&self, client: &client::Client) -> Result<AnnotationSet, Error> {
1175 client
1176 .annotation_set(self.params.dataset_params.annotation_set_id)
1177 .await
1178 }
1179
1180 pub async fn artifacts(&self, client: &client::Client) -> Result<Vec<Artifact>, Error> {
1181 client.artifacts(self.id).await
1182 }
1183
1184 pub async fn metrics(
1185 &self,
1186 client: &client::Client,
1187 ) -> Result<HashMap<String, Parameter>, Error> {
1188 #[derive(Deserialize)]
1189 #[serde(untagged, deny_unknown_fields, expecting = "map, empty map or string")]
1190 enum Response {
1191 Empty {},
1192 Map(HashMap<String, Parameter>),
1193 String(String),
1194 }
1195
1196 let params = HashMap::from([("trainer_session_id", self.id().value())]);
1197 let resp: Response = client
1198 .rpc("trainer.session.metrics".to_owned(), Some(params))
1199 .await?;
1200
1201 Ok(match resp {
1202 Response::String(metrics) => serde_json::from_str(&metrics)?,
1203 Response::Map(metrics) => metrics,
1204 Response::Empty {} => HashMap::new(),
1205 })
1206 }
1207
1208 pub async fn set_metrics(
1209 &self,
1210 client: &client::Client,
1211 metrics: HashMap<String, Parameter>,
1212 ) -> Result<(), Error> {
1213 let metrics = PublishMetrics {
1214 trainer_session_id: Some(self.id()),
1215 validate_session_id: None,
1216 metrics,
1217 };
1218
1219 let _: String = client
1220 .rpc("trainer.session.metrics".to_owned(), Some(metrics))
1221 .await?;
1222
1223 Ok(())
1224 }
1225
1226 pub async fn download_artifact(
1228 &self,
1229 client: &client::Client,
1230 filename: &str,
1231 ) -> Result<Vec<u8>, Error> {
1232 client
1233 .fetch(&format!(
1234 "download_model?training_session_id={}&file={}",
1235 self.id().value(),
1236 filename
1237 ))
1238 .await
1239 }
1240
1241 pub async fn upload_artifact(
1245 &self,
1246 client: &client::Client,
1247 filename: &str,
1248 path: PathBuf,
1249 ) -> Result<(), Error> {
1250 self.upload(client, &[(format!("artifacts/{}", filename), path)])
1251 .await
1252 }
1253
1254 pub async fn download_checkpoint(
1256 &self,
1257 client: &client::Client,
1258 filename: &str,
1259 ) -> Result<Vec<u8>, Error> {
1260 client
1261 .fetch(&format!(
1262 "download_checkpoint?folder=checkpoints&training_session_id={}&file={}",
1263 self.id().value(),
1264 filename
1265 ))
1266 .await
1267 }
1268
1269 pub async fn upload_checkpoint(
1273 &self,
1274 client: &client::Client,
1275 filename: &str,
1276 path: PathBuf,
1277 ) -> Result<(), Error> {
1278 self.upload(client, &[(format!("checkpoints/{}", filename), path)])
1279 .await
1280 }
1281
1282 pub async fn download(&self, client: &client::Client, filename: &str) -> Result<String, Error> {
1286 #[derive(Serialize)]
1287 struct DownloadRequest {
1288 session_id: TrainingSessionID,
1289 file_path: String,
1290 }
1291
1292 let params = DownloadRequest {
1293 session_id: self.id(),
1294 file_path: filename.to_string(),
1295 };
1296
1297 client
1298 .rpc("trainer.download.file".to_owned(), Some(params))
1299 .await
1300 }
1301
1302 pub async fn upload(
1303 &self,
1304 client: &client::Client,
1305 files: &[(String, PathBuf)],
1306 ) -> Result<(), Error> {
1307 let mut parts = Form::new().part(
1308 "params",
1309 Part::text(format!("{{ \"session_id\": {} }}", self.id().value())),
1310 );
1311
1312 for (name, path) in files {
1313 let file_part = Part::file(path).await?.file_name(name.to_owned());
1314 parts = parts.part("file", file_part);
1315 }
1316
1317 let result = client.post_multipart("trainer.upload.files", parts).await?;
1318 trace!("TrainingSession::upload: {:?}", result);
1319 Ok(())
1320 }
1321}
1322
1323#[derive(Deserialize, Clone, Debug)]
1324pub struct ValidationSession {
1325 id: ValidationSessionID,
1326 description: String,
1327 dataset_id: DatasetID,
1328 experiment_id: ExperimentID,
1329 training_session_id: TrainingSessionID,
1330 #[serde(rename = "gt_annotation_set_id")]
1331 annotation_set_id: AnnotationSetID,
1332 #[serde(deserialize_with = "validation_session_params")]
1333 params: HashMap<String, Parameter>,
1334 #[serde(rename = "docker_task")]
1335 task: Task,
1336}
1337
1338fn validation_session_params<'de, D>(
1339 deserializer: D,
1340) -> Result<HashMap<String, Parameter>, D::Error>
1341where
1342 D: Deserializer<'de>,
1343{
1344 #[derive(Deserialize)]
1345 struct ModelParams {
1346 validation: Option<HashMap<String, Parameter>>,
1347 }
1348
1349 #[derive(Deserialize)]
1350 struct ValidateParams {
1351 model: String,
1352 }
1353
1354 #[derive(Deserialize)]
1355 struct Params {
1356 model_params: ModelParams,
1357 validate_params: ValidateParams,
1358 }
1359
1360 let params = Params::deserialize(deserializer)?;
1361 let params = match params.model_params.validation {
1362 Some(mut map) => {
1363 map.insert(
1364 "model".to_string(),
1365 Parameter::String(params.validate_params.model),
1366 );
1367 map
1368 }
1369 None => HashMap::from([(
1370 "model".to_string(),
1371 Parameter::String(params.validate_params.model),
1372 )]),
1373 };
1374
1375 Ok(params)
1376}
1377
1378impl ValidationSession {
1379 pub fn id(&self) -> ValidationSessionID {
1380 self.id
1381 }
1382
1383 pub fn name(&self) -> &str {
1384 self.task.name()
1385 }
1386
1387 pub fn description(&self) -> &str {
1388 &self.description
1389 }
1390
1391 pub fn dataset_id(&self) -> DatasetID {
1392 self.dataset_id
1393 }
1394
1395 pub fn experiment_id(&self) -> ExperimentID {
1396 self.experiment_id
1397 }
1398
1399 pub fn training_session_id(&self) -> TrainingSessionID {
1400 self.training_session_id
1401 }
1402
1403 pub fn annotation_set_id(&self) -> AnnotationSetID {
1404 self.annotation_set_id
1405 }
1406
1407 pub fn params(&self) -> &HashMap<String, Parameter> {
1408 &self.params
1409 }
1410
1411 pub fn task(&self) -> &Task {
1412 &self.task
1413 }
1414
1415 pub async fn metrics(
1416 &self,
1417 client: &client::Client,
1418 ) -> Result<HashMap<String, Parameter>, Error> {
1419 #[derive(Deserialize)]
1420 #[serde(untagged, deny_unknown_fields, expecting = "map, empty map or string")]
1421 enum Response {
1422 Empty {},
1423 Map(HashMap<String, Parameter>),
1424 String(String),
1425 }
1426
1427 let params = HashMap::from([("validate_session_id", self.id().value())]);
1428 let resp: Response = client
1429 .rpc("validate.session.metrics".to_owned(), Some(params))
1430 .await?;
1431
1432 Ok(match resp {
1433 Response::String(metrics) => serde_json::from_str(&metrics)?,
1434 Response::Map(metrics) => metrics,
1435 Response::Empty {} => HashMap::new(),
1436 })
1437 }
1438
1439 pub async fn set_metrics(
1440 &self,
1441 client: &client::Client,
1442 metrics: HashMap<String, Parameter>,
1443 ) -> Result<(), Error> {
1444 let metrics = PublishMetrics {
1445 trainer_session_id: None,
1446 validate_session_id: Some(self.id()),
1447 metrics,
1448 };
1449
1450 let _: String = client
1451 .rpc("validate.session.metrics".to_owned(), Some(metrics))
1452 .await?;
1453
1454 Ok(())
1455 }
1456
1457 pub async fn upload(
1458 &self,
1459 client: &client::Client,
1460 files: &[(String, PathBuf)],
1461 ) -> Result<(), Error> {
1462 let mut parts = Form::new().part(
1463 "params",
1464 Part::text(format!("{{ \"session_id\": {} }}", self.id().value())),
1465 );
1466
1467 for (name, path) in files {
1468 let file_part = Part::file(path).await?.file_name(name.to_owned());
1469 parts = parts.part("file", file_part);
1470 }
1471
1472 let result = client
1473 .post_multipart("validate.upload.files", parts)
1474 .await?;
1475 trace!("ValidationSession::upload: {:?}", result);
1476 Ok(())
1477 }
1478}
1479
1480#[derive(Deserialize, Clone, Debug)]
1481pub struct DatasetParams {
1482 dataset_id: DatasetID,
1483 annotation_set_id: AnnotationSetID,
1484 #[serde(rename = "train_group_name")]
1485 train_group: String,
1486 #[serde(rename = "val_group_name")]
1487 val_group: String,
1488}
1489
1490impl DatasetParams {
1491 pub fn dataset_id(&self) -> DatasetID {
1492 self.dataset_id
1493 }
1494
1495 pub fn annotation_set_id(&self) -> AnnotationSetID {
1496 self.annotation_set_id
1497 }
1498
1499 pub fn train_group(&self) -> &str {
1500 &self.train_group
1501 }
1502
1503 pub fn val_group(&self) -> &str {
1504 &self.val_group
1505 }
1506}
1507
1508#[derive(Serialize, Debug, Clone)]
1509pub struct TasksListParams {
1510 #[serde(skip_serializing_if = "Option::is_none")]
1511 pub continue_token: Option<String>,
1512 #[serde(skip_serializing_if = "Option::is_none")]
1513 pub types: Option<Vec<String>>,
1514 #[serde(rename = "manage_types", skip_serializing_if = "Option::is_none")]
1515 pub manager: Option<Vec<String>>,
1516 #[serde(skip_serializing_if = "Option::is_none")]
1517 pub status: Option<Vec<String>>,
1518}
1519
1520#[derive(Deserialize, Debug, Clone)]
1521pub struct TasksListResult {
1522 pub tasks: Vec<Task>,
1523 pub continue_token: Option<String>,
1524}
1525
1526#[derive(Deserialize, Debug, Clone)]
1527pub struct Task {
1528 id: TaskID,
1529 name: String,
1530 #[serde(rename = "type")]
1531 workflow: String,
1532 status: String,
1533 #[serde(rename = "manage_type")]
1534 manager: Option<String>,
1535 #[serde(rename = "instance_type")]
1536 instance: String,
1537 #[serde(rename = "date")]
1538 created: DateTime<Utc>,
1539}
1540
1541impl Task {
1542 pub fn id(&self) -> TaskID {
1543 self.id
1544 }
1545
1546 pub fn name(&self) -> &str {
1547 &self.name
1548 }
1549
1550 pub fn workflow(&self) -> &str {
1551 &self.workflow
1552 }
1553
1554 pub fn status(&self) -> &str {
1555 &self.status
1556 }
1557
1558 pub fn manager(&self) -> Option<&str> {
1559 self.manager.as_deref()
1560 }
1561
1562 pub fn instance(&self) -> &str {
1563 &self.instance
1564 }
1565
1566 pub fn created(&self) -> &DateTime<Utc> {
1567 &self.created
1568 }
1569}
1570
1571impl Display for Task {
1572 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1573 write!(
1574 f,
1575 "{} [{:?} {}] {}",
1576 self.id,
1577 self.manager(),
1578 self.workflow(),
1579 self.name()
1580 )
1581 }
1582}
1583
1584#[derive(Deserialize, Debug)]
1585pub struct TaskInfo {
1586 id: TaskID,
1587 project_id: Option<ProjectID>,
1588 #[serde(rename = "task_description")]
1589 description: String,
1590 #[serde(rename = "type")]
1591 workflow: String,
1592 status: Option<String>,
1593 progress: TaskProgress,
1594 #[serde(rename = "created_date")]
1595 created: DateTime<Utc>,
1596 #[serde(rename = "end_date")]
1597 completed: DateTime<Utc>,
1598}
1599
1600impl Display for TaskInfo {
1601 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1602 write!(f, "{} {}: {}", self.id, self.workflow(), self.description())
1603 }
1604}
1605
1606impl TaskInfo {
1607 pub fn id(&self) -> TaskID {
1608 self.id
1609 }
1610
1611 pub fn project_id(&self) -> Option<ProjectID> {
1612 self.project_id
1613 }
1614
1615 pub fn description(&self) -> &str {
1616 &self.description
1617 }
1618
1619 pub fn workflow(&self) -> &str {
1620 &self.workflow
1621 }
1622
1623 pub fn status(&self) -> &Option<String> {
1624 &self.status
1625 }
1626
1627 pub async fn set_status(&mut self, client: &Client, status: &str) -> Result<(), Error> {
1628 let t = client.task_status(self.id(), status).await?;
1629 self.status = Some(t.status);
1630 Ok(())
1631 }
1632
1633 pub fn stages(&self) -> HashMap<String, Stage> {
1634 match &self.progress.stages {
1635 Some(stages) => stages.clone(),
1636 None => HashMap::new(),
1637 }
1638 }
1639
1640 pub async fn update_stage(
1641 &mut self,
1642 client: &Client,
1643 stage: &str,
1644 status: &str,
1645 message: &str,
1646 percentage: u8,
1647 ) -> Result<(), Error> {
1648 client
1649 .update_stage(self.id(), stage, status, message, percentage)
1650 .await?;
1651 let t = client.task_info(self.id()).await?;
1652 self.progress.stages = Some(t.progress.stages.unwrap_or_default());
1653 Ok(())
1654 }
1655
1656 pub async fn set_stages(
1657 &mut self,
1658 client: &Client,
1659 stages: &[(&str, &str)],
1660 ) -> Result<(), Error> {
1661 client.set_stages(self.id(), stages).await?;
1662 let t = client.task_info(self.id()).await?;
1663 self.progress.stages = Some(t.progress.stages.unwrap_or_default());
1664 Ok(())
1665 }
1666
1667 pub fn created(&self) -> &DateTime<Utc> {
1668 &self.created
1669 }
1670
1671 pub fn completed(&self) -> &DateTime<Utc> {
1672 &self.completed
1673 }
1674}
1675
1676#[derive(Deserialize, Debug)]
1677pub struct TaskProgress {
1678 stages: Option<HashMap<String, Stage>>,
1679}
1680
1681#[derive(Serialize, Debug, Clone)]
1682pub struct TaskStatus {
1683 #[serde(rename = "docker_task_id")]
1684 pub task_id: TaskID,
1685 pub status: String,
1686}
1687
1688#[derive(Serialize, Deserialize, Debug, Clone)]
1689pub struct Stage {
1690 #[serde(rename = "docker_task_id", skip_serializing_if = "Option::is_none")]
1691 task_id: Option<TaskID>,
1692 stage: String,
1693 #[serde(skip_serializing_if = "Option::is_none")]
1694 status: Option<String>,
1695 #[serde(skip_serializing_if = "Option::is_none")]
1696 description: Option<String>,
1697 #[serde(skip_serializing_if = "Option::is_none")]
1698 message: Option<String>,
1699 percentage: u8,
1700}
1701
1702impl Stage {
1703 pub fn new(
1704 task_id: Option<TaskID>,
1705 stage: String,
1706 status: Option<String>,
1707 message: Option<String>,
1708 percentage: u8,
1709 ) -> Self {
1710 Stage {
1711 task_id,
1712 stage,
1713 status,
1714 description: None,
1715 message,
1716 percentage,
1717 }
1718 }
1719
1720 pub fn task_id(&self) -> &Option<TaskID> {
1721 &self.task_id
1722 }
1723
1724 pub fn stage(&self) -> &str {
1725 &self.stage
1726 }
1727
1728 pub fn status(&self) -> &Option<String> {
1729 &self.status
1730 }
1731
1732 pub fn description(&self) -> &Option<String> {
1733 &self.description
1734 }
1735
1736 pub fn message(&self) -> &Option<String> {
1737 &self.message
1738 }
1739
1740 pub fn percentage(&self) -> u8 {
1741 self.percentage
1742 }
1743}
1744
1745#[derive(Serialize, Debug)]
1746pub struct TaskStages {
1747 #[serde(rename = "docker_task_id")]
1748 pub task_id: TaskID,
1749 #[serde(skip_serializing_if = "Vec::is_empty")]
1750 pub stages: Vec<HashMap<String, String>>,
1751}
1752
1753#[derive(Deserialize, Debug)]
1754pub struct Artifact {
1755 name: String,
1756 #[serde(rename = "modelType")]
1757 model_type: String,
1758}
1759
1760impl Artifact {
1761 pub fn name(&self) -> &str {
1762 &self.name
1763 }
1764
1765 pub fn model_type(&self) -> &str {
1766 &self.model_type
1767 }
1768}
1769
1770#[cfg(test)]
1771mod tests {
1772 use super::*;
1773
1774 #[test]
1776 fn test_organization_id_from_u64() {
1777 let id = OrganizationID::from(12345);
1778 assert_eq!(id.value(), 12345);
1779 }
1780
1781 #[test]
1782 fn test_organization_id_display() {
1783 let id = OrganizationID::from(0xabc123);
1784 assert_eq!(format!("{}", id), "org-abc123");
1785 }
1786
1787 #[test]
1788 fn test_organization_id_try_from_str_valid() {
1789 let id = OrganizationID::try_from("org-abc123").unwrap();
1790 assert_eq!(id.value(), 0xabc123);
1791 }
1792
1793 #[test]
1794 fn test_organization_id_try_from_str_invalid_prefix() {
1795 let result = OrganizationID::try_from("invalid-abc123");
1796 assert!(result.is_err());
1797 match result {
1798 Err(Error::InvalidParameters(msg)) => {
1799 assert!(msg.contains("must start with 'org-'"));
1800 }
1801 _ => panic!("Expected InvalidParameters error"),
1802 }
1803 }
1804
1805 #[test]
1806 fn test_organization_id_try_from_str_invalid_hex() {
1807 let result = OrganizationID::try_from("org-xyz");
1808 assert!(result.is_err());
1809 }
1810
1811 #[test]
1812 fn test_organization_id_try_from_str_empty() {
1813 let result = OrganizationID::try_from("org-");
1814 assert!(result.is_err());
1815 }
1816
1817 #[test]
1818 fn test_organization_id_into_u64() {
1819 let id = OrganizationID::from(54321);
1820 let value: u64 = id.into();
1821 assert_eq!(value, 54321);
1822 }
1823
1824 #[test]
1826 fn test_project_id_from_u64() {
1827 let id = ProjectID::from(78910);
1828 assert_eq!(id.value(), 78910);
1829 }
1830
1831 #[test]
1832 fn test_project_id_display() {
1833 let id = ProjectID::from(0xdef456);
1834 assert_eq!(format!("{}", id), "p-def456");
1835 }
1836
1837 #[test]
1838 fn test_project_id_from_str_valid() {
1839 let id = ProjectID::from_str("p-def456").unwrap();
1840 assert_eq!(id.value(), 0xdef456);
1841 }
1842
1843 #[test]
1844 fn test_project_id_try_from_str_valid() {
1845 let id = ProjectID::try_from("p-123abc").unwrap();
1846 assert_eq!(id.value(), 0x123abc);
1847 }
1848
1849 #[test]
1850 fn test_project_id_try_from_string_valid() {
1851 let id = ProjectID::try_from("p-456def".to_string()).unwrap();
1852 assert_eq!(id.value(), 0x456def);
1853 }
1854
1855 #[test]
1856 fn test_project_id_from_str_invalid_prefix() {
1857 let result = ProjectID::from_str("proj-123");
1858 assert!(result.is_err());
1859 match result {
1860 Err(Error::InvalidParameters(msg)) => {
1861 assert!(msg.contains("must start with 'p-'"));
1862 }
1863 _ => panic!("Expected InvalidParameters error"),
1864 }
1865 }
1866
1867 #[test]
1868 fn test_project_id_from_str_invalid_hex() {
1869 let result = ProjectID::from_str("p-notahex");
1870 assert!(result.is_err());
1871 }
1872
1873 #[test]
1874 fn test_project_id_into_u64() {
1875 let id = ProjectID::from(99999);
1876 let value: u64 = id.into();
1877 assert_eq!(value, 99999);
1878 }
1879
1880 #[test]
1882 fn test_experiment_id_from_u64() {
1883 let id = ExperimentID::from(1193046);
1884 assert_eq!(id.value(), 1193046);
1885 }
1886
1887 #[test]
1888 fn test_experiment_id_display() {
1889 let id = ExperimentID::from(0x123abc);
1890 assert_eq!(format!("{}", id), "exp-123abc");
1891 }
1892
1893 #[test]
1894 fn test_experiment_id_from_str_valid() {
1895 let id = ExperimentID::from_str("exp-456def").unwrap();
1896 assert_eq!(id.value(), 0x456def);
1897 }
1898
1899 #[test]
1900 fn test_experiment_id_try_from_str_valid() {
1901 let id = ExperimentID::try_from("exp-789abc").unwrap();
1902 assert_eq!(id.value(), 0x789abc);
1903 }
1904
1905 #[test]
1906 fn test_experiment_id_try_from_string_valid() {
1907 let id = ExperimentID::try_from("exp-fedcba".to_string()).unwrap();
1908 assert_eq!(id.value(), 0xfedcba);
1909 }
1910
1911 #[test]
1912 fn test_experiment_id_from_str_invalid_prefix() {
1913 let result = ExperimentID::from_str("experiment-123");
1914 assert!(result.is_err());
1915 match result {
1916 Err(Error::InvalidParameters(msg)) => {
1917 assert!(msg.contains("must start with 'exp-'"));
1918 }
1919 _ => panic!("Expected InvalidParameters error"),
1920 }
1921 }
1922
1923 #[test]
1924 fn test_experiment_id_from_str_invalid_hex() {
1925 let result = ExperimentID::from_str("exp-zzz");
1926 assert!(result.is_err());
1927 }
1928
1929 #[test]
1930 fn test_experiment_id_into_u64() {
1931 let id = ExperimentID::from(777777);
1932 let value: u64 = id.into();
1933 assert_eq!(value, 777777);
1934 }
1935
1936 #[test]
1938 fn test_training_session_id_from_u64() {
1939 let id = TrainingSessionID::from(7901234);
1940 assert_eq!(id.value(), 7901234);
1941 }
1942
1943 #[test]
1944 fn test_training_session_id_display() {
1945 let id = TrainingSessionID::from(0xabc123);
1946 assert_eq!(format!("{}", id), "t-abc123");
1947 }
1948
1949 #[test]
1950 fn test_training_session_id_from_str_valid() {
1951 let id = TrainingSessionID::from_str("t-abc123").unwrap();
1952 assert_eq!(id.value(), 0xabc123);
1953 }
1954
1955 #[test]
1956 fn test_training_session_id_try_from_str_valid() {
1957 let id = TrainingSessionID::try_from("t-deadbeef").unwrap();
1958 assert_eq!(id.value(), 0xdeadbeef);
1959 }
1960
1961 #[test]
1962 fn test_training_session_id_try_from_string_valid() {
1963 let id = TrainingSessionID::try_from("t-cafebabe".to_string()).unwrap();
1964 assert_eq!(id.value(), 0xcafebabe);
1965 }
1966
1967 #[test]
1968 fn test_training_session_id_from_str_invalid_prefix() {
1969 let result = TrainingSessionID::from_str("training-123");
1970 assert!(result.is_err());
1971 match result {
1972 Err(Error::InvalidParameters(msg)) => {
1973 assert!(msg.contains("must start with 't-'"));
1974 }
1975 _ => panic!("Expected InvalidParameters error"),
1976 }
1977 }
1978
1979 #[test]
1980 fn test_training_session_id_from_str_invalid_hex() {
1981 let result = TrainingSessionID::from_str("t-qqq");
1982 assert!(result.is_err());
1983 }
1984
1985 #[test]
1986 fn test_training_session_id_into_u64() {
1987 let id = TrainingSessionID::from(123456);
1988 let value: u64 = id.into();
1989 assert_eq!(value, 123456);
1990 }
1991
1992 #[test]
1994 fn test_validation_session_id_from_u64() {
1995 let id = ValidationSessionID::from(3456789);
1996 assert_eq!(id.value(), 3456789);
1997 }
1998
1999 #[test]
2000 fn test_validation_session_id_display() {
2001 let id = ValidationSessionID::from(0x34c985);
2002 assert_eq!(format!("{}", id), "v-34c985");
2003 }
2004
2005 #[test]
2006 fn test_validation_session_id_try_from_str_valid() {
2007 let id = ValidationSessionID::try_from("v-deadbeef").unwrap();
2008 assert_eq!(id.value(), 0xdeadbeef);
2009 }
2010
2011 #[test]
2012 fn test_validation_session_id_try_from_string_valid() {
2013 let id = ValidationSessionID::try_from("v-12345678".to_string()).unwrap();
2014 assert_eq!(id.value(), 0x12345678);
2015 }
2016
2017 #[test]
2018 fn test_validation_session_id_try_from_str_invalid_prefix() {
2019 let result = ValidationSessionID::try_from("validation-123");
2020 assert!(result.is_err());
2021 match result {
2022 Err(Error::InvalidParameters(msg)) => {
2023 assert!(msg.contains("must start with 'v-'"));
2024 }
2025 _ => panic!("Expected InvalidParameters error"),
2026 }
2027 }
2028
2029 #[test]
2030 fn test_validation_session_id_try_from_str_invalid_hex() {
2031 let result = ValidationSessionID::try_from("v-xyz");
2032 assert!(result.is_err());
2033 }
2034
2035 #[test]
2036 fn test_validation_session_id_into_u64() {
2037 let id = ValidationSessionID::from(987654);
2038 let value: u64 = id.into();
2039 assert_eq!(value, 987654);
2040 }
2041
2042 #[test]
2044 fn test_snapshot_id_from_u64() {
2045 let id = SnapshotID::from(111222);
2046 assert_eq!(id.value(), 111222);
2047 }
2048
2049 #[test]
2050 fn test_snapshot_id_display() {
2051 let id = SnapshotID::from(0xaabbcc);
2052 assert_eq!(format!("{}", id), "ss-aabbcc");
2053 }
2054
2055 #[test]
2056 fn test_snapshot_id_try_from_str_valid() {
2057 let id = SnapshotID::try_from("ss-aabbcc").unwrap();
2058 assert_eq!(id.value(), 0xaabbcc);
2059 }
2060
2061 #[test]
2062 fn test_snapshot_id_try_from_str_invalid_prefix() {
2063 let result = SnapshotID::try_from("snapshot-123");
2064 assert!(result.is_err());
2065 match result {
2066 Err(Error::InvalidParameters(msg)) => {
2067 assert!(msg.contains("must start with 'ss-'"));
2068 }
2069 _ => panic!("Expected InvalidParameters error"),
2070 }
2071 }
2072
2073 #[test]
2074 fn test_snapshot_id_try_from_str_invalid_hex() {
2075 let result = SnapshotID::try_from("ss-ggg");
2076 assert!(result.is_err());
2077 }
2078
2079 #[test]
2080 fn test_snapshot_id_into_u64() {
2081 let id = SnapshotID::from(333444);
2082 let value: u64 = id.into();
2083 assert_eq!(value, 333444);
2084 }
2085
2086 #[test]
2088 fn test_task_id_from_u64() {
2089 let id = TaskID::from(555666);
2090 assert_eq!(id.value(), 555666);
2091 }
2092
2093 #[test]
2094 fn test_task_id_display() {
2095 let id = TaskID::from(0x123456);
2096 assert_eq!(format!("{}", id), "task-123456");
2097 }
2098
2099 #[test]
2100 fn test_task_id_from_str_valid() {
2101 let id = TaskID::from_str("task-123456").unwrap();
2102 assert_eq!(id.value(), 0x123456);
2103 }
2104
2105 #[test]
2106 fn test_task_id_try_from_str_valid() {
2107 let id = TaskID::try_from("task-abcdef").unwrap();
2108 assert_eq!(id.value(), 0xabcdef);
2109 }
2110
2111 #[test]
2112 fn test_task_id_try_from_string_valid() {
2113 let id = TaskID::try_from("task-fedcba".to_string()).unwrap();
2114 assert_eq!(id.value(), 0xfedcba);
2115 }
2116
2117 #[test]
2118 fn test_task_id_from_str_invalid_prefix() {
2119 let result = TaskID::from_str("t-123");
2120 assert!(result.is_err());
2121 match result {
2122 Err(Error::InvalidParameters(msg)) => {
2123 assert!(msg.contains("must start with 'task-'"));
2124 }
2125 _ => panic!("Expected InvalidParameters error"),
2126 }
2127 }
2128
2129 #[test]
2130 fn test_task_id_from_str_invalid_hex() {
2131 let result = TaskID::from_str("task-zzz");
2132 assert!(result.is_err());
2133 }
2134
2135 #[test]
2136 fn test_task_id_into_u64() {
2137 let id = TaskID::from(777888);
2138 let value: u64 = id.into();
2139 assert_eq!(value, 777888);
2140 }
2141
2142 #[test]
2144 fn test_dataset_id_from_u64() {
2145 let id = DatasetID::from(1193046);
2146 assert_eq!(id.value(), 1193046);
2147 }
2148
2149 #[test]
2150 fn test_dataset_id_display() {
2151 let id = DatasetID::from(0x123abc);
2152 assert_eq!(format!("{}", id), "ds-123abc");
2153 }
2154
2155 #[test]
2156 fn test_dataset_id_from_str_valid() {
2157 let id = DatasetID::from_str("ds-456def").unwrap();
2158 assert_eq!(id.value(), 0x456def);
2159 }
2160
2161 #[test]
2162 fn test_dataset_id_try_from_str_valid() {
2163 let id = DatasetID::try_from("ds-789abc").unwrap();
2164 assert_eq!(id.value(), 0x789abc);
2165 }
2166
2167 #[test]
2168 fn test_dataset_id_try_from_string_valid() {
2169 let id = DatasetID::try_from("ds-fedcba".to_string()).unwrap();
2170 assert_eq!(id.value(), 0xfedcba);
2171 }
2172
2173 #[test]
2174 fn test_dataset_id_from_str_invalid_prefix() {
2175 let result = DatasetID::from_str("dataset-123");
2176 assert!(result.is_err());
2177 match result {
2178 Err(Error::InvalidParameters(msg)) => {
2179 assert!(msg.contains("must start with 'ds-'"));
2180 }
2181 _ => panic!("Expected InvalidParameters error"),
2182 }
2183 }
2184
2185 #[test]
2186 fn test_dataset_id_from_str_invalid_hex() {
2187 let result = DatasetID::from_str("ds-zzz");
2188 assert!(result.is_err());
2189 }
2190
2191 #[test]
2192 fn test_dataset_id_into_u64() {
2193 let id = DatasetID::from(111111);
2194 let value: u64 = id.into();
2195 assert_eq!(value, 111111);
2196 }
2197
2198 #[test]
2200 fn test_annotation_set_id_from_u64() {
2201 let id = AnnotationSetID::from(222333);
2202 assert_eq!(id.value(), 222333);
2203 }
2204
2205 #[test]
2206 fn test_annotation_set_id_display() {
2207 let id = AnnotationSetID::from(0xabcdef);
2208 assert_eq!(format!("{}", id), "as-abcdef");
2209 }
2210
2211 #[test]
2212 fn test_annotation_set_id_from_str_valid() {
2213 let id = AnnotationSetID::from_str("as-abcdef").unwrap();
2214 assert_eq!(id.value(), 0xabcdef);
2215 }
2216
2217 #[test]
2218 fn test_annotation_set_id_try_from_str_valid() {
2219 let id = AnnotationSetID::try_from("as-123456").unwrap();
2220 assert_eq!(id.value(), 0x123456);
2221 }
2222
2223 #[test]
2224 fn test_annotation_set_id_try_from_string_valid() {
2225 let id = AnnotationSetID::try_from("as-fedcba".to_string()).unwrap();
2226 assert_eq!(id.value(), 0xfedcba);
2227 }
2228
2229 #[test]
2230 fn test_annotation_set_id_from_str_invalid_prefix() {
2231 let result = AnnotationSetID::from_str("annotation-123");
2232 assert!(result.is_err());
2233 match result {
2234 Err(Error::InvalidParameters(msg)) => {
2235 assert!(msg.contains("must start with 'as-'"));
2236 }
2237 _ => panic!("Expected InvalidParameters error"),
2238 }
2239 }
2240
2241 #[test]
2242 fn test_annotation_set_id_from_str_invalid_hex() {
2243 let result = AnnotationSetID::from_str("as-zzz");
2244 assert!(result.is_err());
2245 }
2246
2247 #[test]
2248 fn test_annotation_set_id_into_u64() {
2249 let id = AnnotationSetID::from(444555);
2250 let value: u64 = id.into();
2251 assert_eq!(value, 444555);
2252 }
2253
2254 #[test]
2256 fn test_sample_id_from_u64() {
2257 let id = SampleID::from(666777);
2258 assert_eq!(id.value(), 666777);
2259 }
2260
2261 #[test]
2262 fn test_sample_id_display() {
2263 let id = SampleID::from(0x987654);
2264 assert_eq!(format!("{}", id), "s-987654");
2265 }
2266
2267 #[test]
2268 fn test_sample_id_try_from_str_valid() {
2269 let id = SampleID::try_from("s-987654").unwrap();
2270 assert_eq!(id.value(), 0x987654);
2271 }
2272
2273 #[test]
2274 fn test_sample_id_try_from_str_invalid_prefix() {
2275 let result = SampleID::try_from("sample-123");
2276 assert!(result.is_err());
2277 match result {
2278 Err(Error::InvalidParameters(msg)) => {
2279 assert!(msg.contains("must start with 's-'"));
2280 }
2281 _ => panic!("Expected InvalidParameters error"),
2282 }
2283 }
2284
2285 #[test]
2286 fn test_sample_id_try_from_str_invalid_hex() {
2287 let result = SampleID::try_from("s-zzz");
2288 assert!(result.is_err());
2289 }
2290
2291 #[test]
2292 fn test_sample_id_into_u64() {
2293 let id = SampleID::from(888999);
2294 let value: u64 = id.into();
2295 assert_eq!(value, 888999);
2296 }
2297
2298 #[test]
2300 fn test_app_id_from_u64() {
2301 let id = AppId::from(123123);
2302 assert_eq!(id.value(), 123123);
2303 }
2304
2305 #[test]
2306 fn test_app_id_display() {
2307 let id = AppId::from(0x456789);
2308 assert_eq!(format!("{}", id), "app-456789");
2309 }
2310
2311 #[test]
2312 fn test_app_id_try_from_str_valid() {
2313 let id = AppId::try_from("app-456789").unwrap();
2314 assert_eq!(id.value(), 0x456789);
2315 }
2316
2317 #[test]
2318 fn test_app_id_try_from_str_invalid_prefix() {
2319 let result = AppId::try_from("application-123");
2320 assert!(result.is_err());
2321 match result {
2322 Err(Error::InvalidParameters(msg)) => {
2323 assert!(msg.contains("must start with 'app-'"));
2324 }
2325 _ => panic!("Expected InvalidParameters error"),
2326 }
2327 }
2328
2329 #[test]
2330 fn test_app_id_try_from_str_invalid_hex() {
2331 let result = AppId::try_from("app-zzz");
2332 assert!(result.is_err());
2333 }
2334
2335 #[test]
2336 fn test_app_id_into_u64() {
2337 let id = AppId::from(321321);
2338 let value: u64 = id.into();
2339 assert_eq!(value, 321321);
2340 }
2341
2342 #[test]
2344 fn test_image_id_from_u64() {
2345 let id = ImageId::from(789789);
2346 assert_eq!(id.value(), 789789);
2347 }
2348
2349 #[test]
2350 fn test_image_id_display() {
2351 let id = ImageId::from(0xabcd1234);
2352 assert_eq!(format!("{}", id), "im-abcd1234");
2353 }
2354
2355 #[test]
2356 fn test_image_id_try_from_str_valid() {
2357 let id = ImageId::try_from("im-abcd1234").unwrap();
2358 assert_eq!(id.value(), 0xabcd1234);
2359 }
2360
2361 #[test]
2362 fn test_image_id_try_from_str_invalid_prefix() {
2363 let result = ImageId::try_from("image-123");
2364 assert!(result.is_err());
2365 match result {
2366 Err(Error::InvalidParameters(msg)) => {
2367 assert!(msg.contains("must start with 'im-'"));
2368 }
2369 _ => panic!("Expected InvalidParameters error"),
2370 }
2371 }
2372
2373 #[test]
2374 fn test_image_id_try_from_str_invalid_hex() {
2375 let result = ImageId::try_from("im-zzz");
2376 assert!(result.is_err());
2377 }
2378
2379 #[test]
2380 fn test_image_id_into_u64() {
2381 let id = ImageId::from(987987);
2382 let value: u64 = id.into();
2383 assert_eq!(value, 987987);
2384 }
2385
2386 #[test]
2388 fn test_id_types_equality() {
2389 let id1 = ProjectID::from(12345);
2390 let id2 = ProjectID::from(12345);
2391 let id3 = ProjectID::from(54321);
2392
2393 assert_eq!(id1, id2);
2394 assert_ne!(id1, id3);
2395 }
2396
2397 #[test]
2398 fn test_id_types_hash() {
2399 use std::collections::HashSet;
2400
2401 let mut set = HashSet::new();
2402 set.insert(DatasetID::from(100));
2403 set.insert(DatasetID::from(200));
2404 set.insert(DatasetID::from(100)); assert_eq!(set.len(), 2);
2407 assert!(set.contains(&DatasetID::from(100)));
2408 assert!(set.contains(&DatasetID::from(200)));
2409 }
2410
2411 #[test]
2412 fn test_id_types_copy_clone() {
2413 let id1 = ExperimentID::from(999);
2414 let id2 = id1; let id3 = id1; assert_eq!(id1, id2);
2418 assert_eq!(id1, id3);
2419 }
2420
2421 #[test]
2423 fn test_id_zero_value() {
2424 let id = ProjectID::from(0);
2425 assert_eq!(format!("{}", id), "p-0");
2426 assert_eq!(id.value(), 0);
2427 }
2428
2429 #[test]
2430 fn test_id_max_value() {
2431 let id = ProjectID::from(u64::MAX);
2432 assert_eq!(format!("{}", id), "p-ffffffffffffffff");
2433 assert_eq!(id.value(), u64::MAX);
2434 }
2435
2436 #[test]
2437 fn test_id_round_trip_conversion() {
2438 let original = 0xdeadbeef_u64;
2439 let id = TrainingSessionID::from(original);
2440 let back: u64 = id.into();
2441 assert_eq!(original, back);
2442 }
2443
2444 #[test]
2445 fn test_id_case_insensitive_hex() {
2446 let id1 = DatasetID::from_str("ds-ABCDEF").unwrap();
2448 let id2 = DatasetID::from_str("ds-abcdef").unwrap();
2449 assert_eq!(id1.value(), id2.value());
2450 }
2451
2452 #[test]
2453 fn test_id_with_leading_zeros() {
2454 let id = ProjectID::from_str("p-00001234").unwrap();
2455 assert_eq!(id.value(), 0x1234);
2456 }
2457
2458 #[test]
2460 fn test_parameter_integer() {
2461 let param = Parameter::Integer(42);
2462 match param {
2463 Parameter::Integer(val) => assert_eq!(val, 42),
2464 _ => panic!("Expected Integer variant"),
2465 }
2466 }
2467
2468 #[test]
2469 fn test_parameter_real() {
2470 let param = Parameter::Real(2.5);
2471 match param {
2472 Parameter::Real(val) => assert_eq!(val, 2.5),
2473 _ => panic!("Expected Real variant"),
2474 }
2475 }
2476
2477 #[test]
2478 fn test_parameter_boolean() {
2479 let param = Parameter::Boolean(true);
2480 match param {
2481 Parameter::Boolean(val) => assert!(val),
2482 _ => panic!("Expected Boolean variant"),
2483 }
2484 }
2485
2486 #[test]
2487 fn test_parameter_string() {
2488 let param = Parameter::String("test".to_string());
2489 match param {
2490 Parameter::String(val) => assert_eq!(val, "test"),
2491 _ => panic!("Expected String variant"),
2492 }
2493 }
2494
2495 #[test]
2496 fn test_parameter_array() {
2497 let param = Parameter::Array(vec![
2498 Parameter::Integer(1),
2499 Parameter::Integer(2),
2500 Parameter::Integer(3),
2501 ]);
2502 match param {
2503 Parameter::Array(arr) => assert_eq!(arr.len(), 3),
2504 _ => panic!("Expected Array variant"),
2505 }
2506 }
2507
2508 #[test]
2509 fn test_parameter_object() {
2510 let mut map = HashMap::new();
2511 map.insert("key".to_string(), Parameter::Integer(100));
2512 let param = Parameter::Object(map);
2513 match param {
2514 Parameter::Object(obj) => {
2515 assert_eq!(obj.len(), 1);
2516 assert!(obj.contains_key("key"));
2517 }
2518 _ => panic!("Expected Object variant"),
2519 }
2520 }
2521
2522 #[test]
2523 fn test_parameter_clone() {
2524 let param1 = Parameter::Integer(42);
2525 let param2 = param1.clone();
2526 assert_eq!(param1, param2);
2527 }
2528
2529 #[test]
2530 fn test_parameter_nested() {
2531 let inner_array = Parameter::Array(vec![Parameter::Integer(1), Parameter::Integer(2)]);
2532 let outer_array = Parameter::Array(vec![inner_array.clone(), inner_array]);
2533
2534 match outer_array {
2535 Parameter::Array(arr) => {
2536 assert_eq!(arr.len(), 2);
2537 }
2538 _ => panic!("Expected Array variant"),
2539 }
2540 }
2541}