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, Clone)]
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(Serialize, Clone, Debug)]
962pub struct ServerAnnotation {
963 #[serde(skip_serializing_if = "Option::is_none")]
965 pub label_id: Option<u64>,
966 #[serde(skip_serializing_if = "Option::is_none")]
968 pub label_index: Option<u64>,
969 #[serde(skip_serializing_if = "Option::is_none")]
971 pub label_name: Option<String>,
972 #[serde(rename = "type")]
974 pub annotation_type: String,
975 pub x: f64,
977 pub y: f64,
979 pub w: f64,
981 pub h: f64,
983 pub score: f64,
985 #[serde(skip_serializing_if = "String::is_empty")]
987 pub polygon: String,
988 pub image_id: u64,
990 pub annotation_set_id: u64,
992 #[serde(skip_serializing_if = "Option::is_none")]
994 pub object_reference: Option<String>,
995}
996
997#[derive(Serialize, Debug)]
999pub struct AnnotationAddBulkParams {
1000 pub annotation_set_id: u64,
1001 pub annotations: Vec<ServerAnnotation>,
1002}
1003
1004#[derive(Serialize, Debug)]
1006pub struct AnnotationBulkDeleteParams {
1007 pub annotation_set_id: u64,
1008 pub annotation_types: Vec<String>,
1009 #[serde(skip_serializing_if = "Vec::is_empty")]
1011 pub image_ids: Vec<u64>,
1012 #[serde(skip_serializing_if = "Option::is_none")]
1014 pub delete_all: Option<bool>,
1015}
1016
1017#[derive(Deserialize)]
1018pub struct Snapshot {
1019 id: SnapshotID,
1020 description: String,
1021 status: String,
1022 path: String,
1023 #[serde(rename = "date")]
1024 created: DateTime<Utc>,
1025}
1026
1027impl Display for Snapshot {
1028 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1029 write!(f, "{} {}", self.id, self.description)
1030 }
1031}
1032
1033impl Snapshot {
1034 pub fn id(&self) -> SnapshotID {
1035 self.id
1036 }
1037
1038 pub fn description(&self) -> &str {
1039 &self.description
1040 }
1041
1042 pub fn status(&self) -> &str {
1043 &self.status
1044 }
1045
1046 pub fn path(&self) -> &str {
1047 &self.path
1048 }
1049
1050 pub fn created(&self) -> &DateTime<Utc> {
1051 &self.created
1052 }
1053}
1054
1055#[derive(Serialize, Debug)]
1056pub struct SnapshotRestore {
1057 pub project_id: ProjectID,
1058 pub snapshot_id: SnapshotID,
1059 pub fps: u64,
1060 #[serde(rename = "enabled_topics", skip_serializing_if = "Vec::is_empty")]
1061 pub topics: Vec<String>,
1062 #[serde(rename = "label_names", skip_serializing_if = "Vec::is_empty")]
1063 pub autolabel: Vec<String>,
1064 #[serde(rename = "depth_gen")]
1065 pub autodepth: bool,
1066 pub agtg_pipeline: bool,
1067 #[serde(skip_serializing_if = "Option::is_none")]
1068 pub dataset_name: Option<String>,
1069 #[serde(skip_serializing_if = "Option::is_none")]
1070 pub dataset_description: Option<String>,
1071}
1072
1073#[derive(Deserialize, Debug)]
1074pub struct SnapshotRestoreResult {
1075 pub id: SnapshotID,
1076 pub description: String,
1077 pub dataset_name: String,
1078 pub dataset_id: DatasetID,
1079 pub annotation_set_id: AnnotationSetID,
1080 #[serde(default)]
1081 pub task_id: Option<TaskID>,
1082 pub date: DateTime<Utc>,
1083}
1084
1085#[derive(Serialize, Debug)]
1090pub struct SnapshotCreateFromDataset {
1091 pub description: String,
1093 pub dataset_id: DatasetID,
1095 pub annotation_set_id: AnnotationSetID,
1097}
1098
1099#[derive(Deserialize, Debug)]
1103pub struct SnapshotFromDatasetResult {
1104 #[serde(alias = "snapshot_id")]
1106 pub id: SnapshotID,
1107 #[serde(default)]
1109 pub task_id: Option<TaskID>,
1110}
1111
1112#[derive(Deserialize)]
1113pub struct Experiment {
1114 id: ExperimentID,
1115 project_id: ProjectID,
1116 name: String,
1117 description: String,
1118}
1119
1120impl Display for Experiment {
1121 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1122 write!(f, "{} {}", self.id, self.name)
1123 }
1124}
1125
1126impl Experiment {
1127 pub fn id(&self) -> ExperimentID {
1128 self.id
1129 }
1130
1131 pub fn project_id(&self) -> ProjectID {
1132 self.project_id
1133 }
1134
1135 pub fn name(&self) -> &str {
1136 &self.name
1137 }
1138
1139 pub fn description(&self) -> &str {
1140 &self.description
1141 }
1142
1143 pub async fn project(&self, client: &client::Client) -> Result<Project, Error> {
1144 client.project(self.project_id).await
1145 }
1146
1147 pub async fn training_sessions(
1148 &self,
1149 client: &client::Client,
1150 name: Option<&str>,
1151 ) -> Result<Vec<TrainingSession>, Error> {
1152 client.training_sessions(self.id, name).await
1153 }
1154}
1155
1156#[derive(Serialize, Debug)]
1157pub struct PublishMetrics {
1158 #[serde(rename = "trainer_session_id", skip_serializing_if = "Option::is_none")]
1159 pub trainer_session_id: Option<TrainingSessionID>,
1160 #[serde(
1161 rename = "validate_session_id",
1162 skip_serializing_if = "Option::is_none"
1163 )]
1164 pub validate_session_id: Option<ValidationSessionID>,
1165 pub metrics: HashMap<String, Parameter>,
1166}
1167
1168#[derive(Deserialize)]
1169struct TrainingSessionParams {
1170 model_params: HashMap<String, Parameter>,
1171 dataset_params: DatasetParams,
1172}
1173
1174#[derive(Deserialize)]
1175pub struct TrainingSession {
1176 id: TrainingSessionID,
1177 #[serde(rename = "trainer_id")]
1178 experiment_id: ExperimentID,
1179 model: String,
1180 name: String,
1181 description: String,
1182 params: TrainingSessionParams,
1183 #[serde(rename = "docker_task")]
1184 task: Task,
1185}
1186
1187impl Display for TrainingSession {
1188 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1189 write!(f, "{} {}", self.id, self.name())
1190 }
1191}
1192
1193impl TrainingSession {
1194 pub fn id(&self) -> TrainingSessionID {
1195 self.id
1196 }
1197
1198 pub fn name(&self) -> &str {
1199 &self.name
1200 }
1201
1202 pub fn description(&self) -> &str {
1203 &self.description
1204 }
1205
1206 pub fn model(&self) -> &str {
1207 &self.model
1208 }
1209
1210 pub fn experiment_id(&self) -> ExperimentID {
1211 self.experiment_id
1212 }
1213
1214 pub fn task(&self) -> Task {
1215 self.task.clone()
1216 }
1217
1218 pub fn model_params(&self) -> &HashMap<String, Parameter> {
1219 &self.params.model_params
1220 }
1221
1222 pub fn dataset_params(&self) -> &DatasetParams {
1223 &self.params.dataset_params
1224 }
1225
1226 pub fn train_group(&self) -> &str {
1227 &self.params.dataset_params.train_group
1228 }
1229
1230 pub fn val_group(&self) -> &str {
1231 &self.params.dataset_params.val_group
1232 }
1233
1234 pub async fn experiment(&self, client: &client::Client) -> Result<Experiment, Error> {
1235 client.experiment(self.experiment_id).await
1236 }
1237
1238 pub async fn dataset(&self, client: &client::Client) -> Result<Dataset, Error> {
1239 client.dataset(self.params.dataset_params.dataset_id).await
1240 }
1241
1242 pub async fn annotation_set(&self, client: &client::Client) -> Result<AnnotationSet, Error> {
1243 client
1244 .annotation_set(self.params.dataset_params.annotation_set_id)
1245 .await
1246 }
1247
1248 pub async fn artifacts(&self, client: &client::Client) -> Result<Vec<Artifact>, Error> {
1249 client.artifacts(self.id).await
1250 }
1251
1252 pub async fn metrics(
1253 &self,
1254 client: &client::Client,
1255 ) -> Result<HashMap<String, Parameter>, Error> {
1256 #[derive(Deserialize)]
1257 #[serde(untagged, deny_unknown_fields, expecting = "map, empty map or string")]
1258 enum Response {
1259 Empty {},
1260 Map(HashMap<String, Parameter>),
1261 String(String),
1262 }
1263
1264 let params = HashMap::from([("trainer_session_id", self.id().value())]);
1265 let resp: Response = client
1266 .rpc("trainer.session.metrics".to_owned(), Some(params))
1267 .await?;
1268
1269 Ok(match resp {
1270 Response::String(metrics) => serde_json::from_str(&metrics)?,
1271 Response::Map(metrics) => metrics,
1272 Response::Empty {} => HashMap::new(),
1273 })
1274 }
1275
1276 pub async fn set_metrics(
1277 &self,
1278 client: &client::Client,
1279 metrics: HashMap<String, Parameter>,
1280 ) -> Result<(), Error> {
1281 let metrics = PublishMetrics {
1282 trainer_session_id: Some(self.id()),
1283 validate_session_id: None,
1284 metrics,
1285 };
1286
1287 let _: String = client
1288 .rpc("trainer.session.metrics".to_owned(), Some(metrics))
1289 .await?;
1290
1291 Ok(())
1292 }
1293
1294 pub async fn download_artifact(
1296 &self,
1297 client: &client::Client,
1298 filename: &str,
1299 ) -> Result<Vec<u8>, Error> {
1300 client
1301 .fetch(&format!(
1302 "download_model?training_session_id={}&file={}",
1303 self.id().value(),
1304 filename
1305 ))
1306 .await
1307 }
1308
1309 pub async fn upload_artifact(
1313 &self,
1314 client: &client::Client,
1315 filename: &str,
1316 path: PathBuf,
1317 ) -> Result<(), Error> {
1318 self.upload(client, &[(format!("artifacts/{}", filename), path)])
1319 .await
1320 }
1321
1322 pub async fn download_checkpoint(
1324 &self,
1325 client: &client::Client,
1326 filename: &str,
1327 ) -> Result<Vec<u8>, Error> {
1328 client
1329 .fetch(&format!(
1330 "download_checkpoint?folder=checkpoints&training_session_id={}&file={}",
1331 self.id().value(),
1332 filename
1333 ))
1334 .await
1335 }
1336
1337 pub async fn upload_checkpoint(
1341 &self,
1342 client: &client::Client,
1343 filename: &str,
1344 path: PathBuf,
1345 ) -> Result<(), Error> {
1346 self.upload(client, &[(format!("checkpoints/{}", filename), path)])
1347 .await
1348 }
1349
1350 pub async fn download(&self, client: &client::Client, filename: &str) -> Result<String, Error> {
1354 #[derive(Serialize)]
1355 struct DownloadRequest {
1356 session_id: TrainingSessionID,
1357 file_path: String,
1358 }
1359
1360 let params = DownloadRequest {
1361 session_id: self.id(),
1362 file_path: filename.to_string(),
1363 };
1364
1365 client
1366 .rpc("trainer.download.file".to_owned(), Some(params))
1367 .await
1368 }
1369
1370 pub async fn upload(
1371 &self,
1372 client: &client::Client,
1373 files: &[(String, PathBuf)],
1374 ) -> Result<(), Error> {
1375 let mut parts = Form::new().part(
1376 "params",
1377 Part::text(format!("{{ \"session_id\": {} }}", self.id().value())),
1378 );
1379
1380 for (name, path) in files {
1381 let file_part = Part::file(path).await?.file_name(name.to_owned());
1382 parts = parts.part("file", file_part);
1383 }
1384
1385 let result = client.post_multipart("trainer.upload.files", parts).await?;
1386 trace!("TrainingSession::upload: {:?}", result);
1387 Ok(())
1388 }
1389}
1390
1391#[derive(Deserialize, Clone, Debug)]
1392pub struct ValidationSession {
1393 id: ValidationSessionID,
1394 description: String,
1395 dataset_id: DatasetID,
1396 experiment_id: ExperimentID,
1397 training_session_id: TrainingSessionID,
1398 #[serde(rename = "gt_annotation_set_id")]
1399 annotation_set_id: AnnotationSetID,
1400 #[serde(deserialize_with = "validation_session_params")]
1401 params: HashMap<String, Parameter>,
1402 #[serde(rename = "docker_task")]
1403 task: Task,
1404}
1405
1406fn validation_session_params<'de, D>(
1407 deserializer: D,
1408) -> Result<HashMap<String, Parameter>, D::Error>
1409where
1410 D: Deserializer<'de>,
1411{
1412 #[derive(Deserialize)]
1413 struct ModelParams {
1414 validation: Option<HashMap<String, Parameter>>,
1415 }
1416
1417 #[derive(Deserialize)]
1418 struct ValidateParams {
1419 model: String,
1420 }
1421
1422 #[derive(Deserialize)]
1423 struct Params {
1424 model_params: ModelParams,
1425 validate_params: ValidateParams,
1426 }
1427
1428 let params = Params::deserialize(deserializer)?;
1429 let params = match params.model_params.validation {
1430 Some(mut map) => {
1431 map.insert(
1432 "model".to_string(),
1433 Parameter::String(params.validate_params.model),
1434 );
1435 map
1436 }
1437 None => HashMap::from([(
1438 "model".to_string(),
1439 Parameter::String(params.validate_params.model),
1440 )]),
1441 };
1442
1443 Ok(params)
1444}
1445
1446impl ValidationSession {
1447 pub fn id(&self) -> ValidationSessionID {
1448 self.id
1449 }
1450
1451 pub fn name(&self) -> &str {
1452 self.task.name()
1453 }
1454
1455 pub fn description(&self) -> &str {
1456 &self.description
1457 }
1458
1459 pub fn dataset_id(&self) -> DatasetID {
1460 self.dataset_id
1461 }
1462
1463 pub fn experiment_id(&self) -> ExperimentID {
1464 self.experiment_id
1465 }
1466
1467 pub fn training_session_id(&self) -> TrainingSessionID {
1468 self.training_session_id
1469 }
1470
1471 pub fn annotation_set_id(&self) -> AnnotationSetID {
1472 self.annotation_set_id
1473 }
1474
1475 pub fn params(&self) -> &HashMap<String, Parameter> {
1476 &self.params
1477 }
1478
1479 pub fn task(&self) -> &Task {
1480 &self.task
1481 }
1482
1483 pub async fn metrics(
1484 &self,
1485 client: &client::Client,
1486 ) -> Result<HashMap<String, Parameter>, Error> {
1487 #[derive(Deserialize)]
1488 #[serde(untagged, deny_unknown_fields, expecting = "map, empty map or string")]
1489 enum Response {
1490 Empty {},
1491 Map(HashMap<String, Parameter>),
1492 String(String),
1493 }
1494
1495 let params = HashMap::from([("validate_session_id", self.id().value())]);
1496 let resp: Response = client
1497 .rpc("validate.session.metrics".to_owned(), Some(params))
1498 .await?;
1499
1500 Ok(match resp {
1501 Response::String(metrics) => serde_json::from_str(&metrics)?,
1502 Response::Map(metrics) => metrics,
1503 Response::Empty {} => HashMap::new(),
1504 })
1505 }
1506
1507 pub async fn set_metrics(
1508 &self,
1509 client: &client::Client,
1510 metrics: HashMap<String, Parameter>,
1511 ) -> Result<(), Error> {
1512 let metrics = PublishMetrics {
1513 trainer_session_id: None,
1514 validate_session_id: Some(self.id()),
1515 metrics,
1516 };
1517
1518 let _: String = client
1519 .rpc("validate.session.metrics".to_owned(), Some(metrics))
1520 .await?;
1521
1522 Ok(())
1523 }
1524
1525 pub async fn upload(
1526 &self,
1527 client: &client::Client,
1528 files: &[(String, PathBuf)],
1529 ) -> Result<(), Error> {
1530 let mut parts = Form::new().part(
1531 "params",
1532 Part::text(format!("{{ \"session_id\": {} }}", self.id().value())),
1533 );
1534
1535 for (name, path) in files {
1536 let file_part = Part::file(path).await?.file_name(name.to_owned());
1537 parts = parts.part("file", file_part);
1538 }
1539
1540 let result = client
1541 .post_multipart("validate.upload.files", parts)
1542 .await?;
1543 trace!("ValidationSession::upload: {:?}", result);
1544 Ok(())
1545 }
1546}
1547
1548#[derive(Deserialize, Clone, Debug)]
1549pub struct DatasetParams {
1550 dataset_id: DatasetID,
1551 annotation_set_id: AnnotationSetID,
1552 #[serde(rename = "train_group_name")]
1553 train_group: String,
1554 #[serde(rename = "val_group_name")]
1555 val_group: String,
1556}
1557
1558impl DatasetParams {
1559 pub fn dataset_id(&self) -> DatasetID {
1560 self.dataset_id
1561 }
1562
1563 pub fn annotation_set_id(&self) -> AnnotationSetID {
1564 self.annotation_set_id
1565 }
1566
1567 pub fn train_group(&self) -> &str {
1568 &self.train_group
1569 }
1570
1571 pub fn val_group(&self) -> &str {
1572 &self.val_group
1573 }
1574}
1575
1576#[derive(Serialize, Debug, Clone)]
1577pub struct TasksListParams {
1578 #[serde(skip_serializing_if = "Option::is_none")]
1579 pub continue_token: Option<String>,
1580 #[serde(skip_serializing_if = "Option::is_none")]
1581 pub types: Option<Vec<String>>,
1582 #[serde(rename = "manage_types", skip_serializing_if = "Option::is_none")]
1583 pub manager: Option<Vec<String>>,
1584 #[serde(skip_serializing_if = "Option::is_none")]
1585 pub status: Option<Vec<String>>,
1586}
1587
1588#[derive(Deserialize, Debug, Clone)]
1589pub struct TasksListResult {
1590 pub tasks: Vec<Task>,
1591 pub continue_token: Option<String>,
1592}
1593
1594#[derive(Deserialize, Debug, Clone)]
1595pub struct Task {
1596 id: TaskID,
1597 name: String,
1598 #[serde(rename = "type")]
1599 workflow: String,
1600 status: String,
1601 #[serde(rename = "manage_type")]
1602 manager: Option<String>,
1603 #[serde(rename = "instance_type")]
1604 instance: String,
1605 #[serde(rename = "date")]
1606 created: DateTime<Utc>,
1607}
1608
1609impl Task {
1610 pub fn id(&self) -> TaskID {
1611 self.id
1612 }
1613
1614 pub fn name(&self) -> &str {
1615 &self.name
1616 }
1617
1618 pub fn workflow(&self) -> &str {
1619 &self.workflow
1620 }
1621
1622 pub fn status(&self) -> &str {
1623 &self.status
1624 }
1625
1626 pub fn manager(&self) -> Option<&str> {
1627 self.manager.as_deref()
1628 }
1629
1630 pub fn instance(&self) -> &str {
1631 &self.instance
1632 }
1633
1634 pub fn created(&self) -> &DateTime<Utc> {
1635 &self.created
1636 }
1637}
1638
1639impl Display for Task {
1640 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1641 write!(
1642 f,
1643 "{} [{:?} {}] {}",
1644 self.id,
1645 self.manager(),
1646 self.workflow(),
1647 self.name()
1648 )
1649 }
1650}
1651
1652#[derive(Deserialize, Debug)]
1653pub struct TaskInfo {
1654 id: TaskID,
1655 project_id: Option<ProjectID>,
1656 #[serde(rename = "task_description")]
1657 description: String,
1658 #[serde(rename = "type")]
1659 workflow: String,
1660 status: Option<String>,
1661 progress: TaskProgress,
1662 #[serde(rename = "created_date")]
1663 created: DateTime<Utc>,
1664 #[serde(rename = "end_date")]
1665 completed: DateTime<Utc>,
1666}
1667
1668impl Display for TaskInfo {
1669 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1670 write!(f, "{} {}: {}", self.id, self.workflow(), self.description())
1671 }
1672}
1673
1674impl TaskInfo {
1675 pub fn id(&self) -> TaskID {
1676 self.id
1677 }
1678
1679 pub fn project_id(&self) -> Option<ProjectID> {
1680 self.project_id
1681 }
1682
1683 pub fn description(&self) -> &str {
1684 &self.description
1685 }
1686
1687 pub fn workflow(&self) -> &str {
1688 &self.workflow
1689 }
1690
1691 pub fn status(&self) -> &Option<String> {
1692 &self.status
1693 }
1694
1695 pub async fn set_status(&mut self, client: &Client, status: &str) -> Result<(), Error> {
1696 let t = client.task_status(self.id(), status).await?;
1697 self.status = Some(t.status);
1698 Ok(())
1699 }
1700
1701 pub fn stages(&self) -> HashMap<String, Stage> {
1702 match &self.progress.stages {
1703 Some(stages) => stages.clone(),
1704 None => HashMap::new(),
1705 }
1706 }
1707
1708 pub async fn update_stage(
1709 &mut self,
1710 client: &Client,
1711 stage: &str,
1712 status: &str,
1713 message: &str,
1714 percentage: u8,
1715 ) -> Result<(), Error> {
1716 client
1717 .update_stage(self.id(), stage, status, message, percentage)
1718 .await?;
1719 let t = client.task_info(self.id()).await?;
1720 self.progress.stages = Some(t.progress.stages.unwrap_or_default());
1721 Ok(())
1722 }
1723
1724 pub async fn set_stages(
1725 &mut self,
1726 client: &Client,
1727 stages: &[(&str, &str)],
1728 ) -> Result<(), Error> {
1729 client.set_stages(self.id(), stages).await?;
1730 let t = client.task_info(self.id()).await?;
1731 self.progress.stages = Some(t.progress.stages.unwrap_or_default());
1732 Ok(())
1733 }
1734
1735 pub fn created(&self) -> &DateTime<Utc> {
1736 &self.created
1737 }
1738
1739 pub fn completed(&self) -> &DateTime<Utc> {
1740 &self.completed
1741 }
1742}
1743
1744#[derive(Deserialize, Debug)]
1745pub struct TaskProgress {
1746 stages: Option<HashMap<String, Stage>>,
1747}
1748
1749#[derive(Serialize, Debug, Clone)]
1750pub struct TaskStatus {
1751 #[serde(rename = "docker_task_id")]
1752 pub task_id: TaskID,
1753 pub status: String,
1754}
1755
1756#[derive(Serialize, Deserialize, Debug, Clone)]
1757pub struct Stage {
1758 #[serde(rename = "docker_task_id", skip_serializing_if = "Option::is_none")]
1759 task_id: Option<TaskID>,
1760 stage: String,
1761 #[serde(skip_serializing_if = "Option::is_none")]
1762 status: Option<String>,
1763 #[serde(skip_serializing_if = "Option::is_none")]
1764 description: Option<String>,
1765 #[serde(skip_serializing_if = "Option::is_none")]
1766 message: Option<String>,
1767 percentage: u8,
1768}
1769
1770impl Stage {
1771 pub fn new(
1772 task_id: Option<TaskID>,
1773 stage: String,
1774 status: Option<String>,
1775 message: Option<String>,
1776 percentage: u8,
1777 ) -> Self {
1778 Stage {
1779 task_id,
1780 stage,
1781 status,
1782 description: None,
1783 message,
1784 percentage,
1785 }
1786 }
1787
1788 pub fn task_id(&self) -> &Option<TaskID> {
1789 &self.task_id
1790 }
1791
1792 pub fn stage(&self) -> &str {
1793 &self.stage
1794 }
1795
1796 pub fn status(&self) -> &Option<String> {
1797 &self.status
1798 }
1799
1800 pub fn description(&self) -> &Option<String> {
1801 &self.description
1802 }
1803
1804 pub fn message(&self) -> &Option<String> {
1805 &self.message
1806 }
1807
1808 pub fn percentage(&self) -> u8 {
1809 self.percentage
1810 }
1811}
1812
1813#[derive(Serialize, Debug)]
1814pub struct TaskStages {
1815 #[serde(rename = "docker_task_id")]
1816 pub task_id: TaskID,
1817 #[serde(skip_serializing_if = "Vec::is_empty")]
1818 pub stages: Vec<HashMap<String, String>>,
1819}
1820
1821#[derive(Deserialize, Debug)]
1822pub struct Artifact {
1823 name: String,
1824 #[serde(rename = "modelType")]
1825 model_type: String,
1826}
1827
1828impl Artifact {
1829 pub fn name(&self) -> &str {
1830 &self.name
1831 }
1832
1833 pub fn model_type(&self) -> &str {
1834 &self.model_type
1835 }
1836}
1837
1838#[cfg(test)]
1839mod tests {
1840 use super::*;
1841
1842 #[test]
1844 fn test_organization_id_from_u64() {
1845 let id = OrganizationID::from(12345);
1846 assert_eq!(id.value(), 12345);
1847 }
1848
1849 #[test]
1850 fn test_organization_id_display() {
1851 let id = OrganizationID::from(0xabc123);
1852 assert_eq!(format!("{}", id), "org-abc123");
1853 }
1854
1855 #[test]
1856 fn test_organization_id_try_from_str_valid() {
1857 let id = OrganizationID::try_from("org-abc123").unwrap();
1858 assert_eq!(id.value(), 0xabc123);
1859 }
1860
1861 #[test]
1862 fn test_organization_id_try_from_str_invalid_prefix() {
1863 let result = OrganizationID::try_from("invalid-abc123");
1864 assert!(result.is_err());
1865 match result {
1866 Err(Error::InvalidParameters(msg)) => {
1867 assert!(msg.contains("must start with 'org-'"));
1868 }
1869 _ => panic!("Expected InvalidParameters error"),
1870 }
1871 }
1872
1873 #[test]
1874 fn test_organization_id_try_from_str_invalid_hex() {
1875 let result = OrganizationID::try_from("org-xyz");
1876 assert!(result.is_err());
1877 }
1878
1879 #[test]
1880 fn test_organization_id_try_from_str_empty() {
1881 let result = OrganizationID::try_from("org-");
1882 assert!(result.is_err());
1883 }
1884
1885 #[test]
1886 fn test_organization_id_into_u64() {
1887 let id = OrganizationID::from(54321);
1888 let value: u64 = id.into();
1889 assert_eq!(value, 54321);
1890 }
1891
1892 #[test]
1894 fn test_project_id_from_u64() {
1895 let id = ProjectID::from(78910);
1896 assert_eq!(id.value(), 78910);
1897 }
1898
1899 #[test]
1900 fn test_project_id_display() {
1901 let id = ProjectID::from(0xdef456);
1902 assert_eq!(format!("{}", id), "p-def456");
1903 }
1904
1905 #[test]
1906 fn test_project_id_from_str_valid() {
1907 let id = ProjectID::from_str("p-def456").unwrap();
1908 assert_eq!(id.value(), 0xdef456);
1909 }
1910
1911 #[test]
1912 fn test_project_id_try_from_str_valid() {
1913 let id = ProjectID::try_from("p-123abc").unwrap();
1914 assert_eq!(id.value(), 0x123abc);
1915 }
1916
1917 #[test]
1918 fn test_project_id_try_from_string_valid() {
1919 let id = ProjectID::try_from("p-456def".to_string()).unwrap();
1920 assert_eq!(id.value(), 0x456def);
1921 }
1922
1923 #[test]
1924 fn test_project_id_from_str_invalid_prefix() {
1925 let result = ProjectID::from_str("proj-123");
1926 assert!(result.is_err());
1927 match result {
1928 Err(Error::InvalidParameters(msg)) => {
1929 assert!(msg.contains("must start with 'p-'"));
1930 }
1931 _ => panic!("Expected InvalidParameters error"),
1932 }
1933 }
1934
1935 #[test]
1936 fn test_project_id_from_str_invalid_hex() {
1937 let result = ProjectID::from_str("p-notahex");
1938 assert!(result.is_err());
1939 }
1940
1941 #[test]
1942 fn test_project_id_into_u64() {
1943 let id = ProjectID::from(99999);
1944 let value: u64 = id.into();
1945 assert_eq!(value, 99999);
1946 }
1947
1948 #[test]
1950 fn test_experiment_id_from_u64() {
1951 let id = ExperimentID::from(1193046);
1952 assert_eq!(id.value(), 1193046);
1953 }
1954
1955 #[test]
1956 fn test_experiment_id_display() {
1957 let id = ExperimentID::from(0x123abc);
1958 assert_eq!(format!("{}", id), "exp-123abc");
1959 }
1960
1961 #[test]
1962 fn test_experiment_id_from_str_valid() {
1963 let id = ExperimentID::from_str("exp-456def").unwrap();
1964 assert_eq!(id.value(), 0x456def);
1965 }
1966
1967 #[test]
1968 fn test_experiment_id_try_from_str_valid() {
1969 let id = ExperimentID::try_from("exp-789abc").unwrap();
1970 assert_eq!(id.value(), 0x789abc);
1971 }
1972
1973 #[test]
1974 fn test_experiment_id_try_from_string_valid() {
1975 let id = ExperimentID::try_from("exp-fedcba".to_string()).unwrap();
1976 assert_eq!(id.value(), 0xfedcba);
1977 }
1978
1979 #[test]
1980 fn test_experiment_id_from_str_invalid_prefix() {
1981 let result = ExperimentID::from_str("experiment-123");
1982 assert!(result.is_err());
1983 match result {
1984 Err(Error::InvalidParameters(msg)) => {
1985 assert!(msg.contains("must start with 'exp-'"));
1986 }
1987 _ => panic!("Expected InvalidParameters error"),
1988 }
1989 }
1990
1991 #[test]
1992 fn test_experiment_id_from_str_invalid_hex() {
1993 let result = ExperimentID::from_str("exp-zzz");
1994 assert!(result.is_err());
1995 }
1996
1997 #[test]
1998 fn test_experiment_id_into_u64() {
1999 let id = ExperimentID::from(777777);
2000 let value: u64 = id.into();
2001 assert_eq!(value, 777777);
2002 }
2003
2004 #[test]
2006 fn test_training_session_id_from_u64() {
2007 let id = TrainingSessionID::from(7901234);
2008 assert_eq!(id.value(), 7901234);
2009 }
2010
2011 #[test]
2012 fn test_training_session_id_display() {
2013 let id = TrainingSessionID::from(0xabc123);
2014 assert_eq!(format!("{}", id), "t-abc123");
2015 }
2016
2017 #[test]
2018 fn test_training_session_id_from_str_valid() {
2019 let id = TrainingSessionID::from_str("t-abc123").unwrap();
2020 assert_eq!(id.value(), 0xabc123);
2021 }
2022
2023 #[test]
2024 fn test_training_session_id_try_from_str_valid() {
2025 let id = TrainingSessionID::try_from("t-deadbeef").unwrap();
2026 assert_eq!(id.value(), 0xdeadbeef);
2027 }
2028
2029 #[test]
2030 fn test_training_session_id_try_from_string_valid() {
2031 let id = TrainingSessionID::try_from("t-cafebabe".to_string()).unwrap();
2032 assert_eq!(id.value(), 0xcafebabe);
2033 }
2034
2035 #[test]
2036 fn test_training_session_id_from_str_invalid_prefix() {
2037 let result = TrainingSessionID::from_str("training-123");
2038 assert!(result.is_err());
2039 match result {
2040 Err(Error::InvalidParameters(msg)) => {
2041 assert!(msg.contains("must start with 't-'"));
2042 }
2043 _ => panic!("Expected InvalidParameters error"),
2044 }
2045 }
2046
2047 #[test]
2048 fn test_training_session_id_from_str_invalid_hex() {
2049 let result = TrainingSessionID::from_str("t-qqq");
2050 assert!(result.is_err());
2051 }
2052
2053 #[test]
2054 fn test_training_session_id_into_u64() {
2055 let id = TrainingSessionID::from(123456);
2056 let value: u64 = id.into();
2057 assert_eq!(value, 123456);
2058 }
2059
2060 #[test]
2062 fn test_validation_session_id_from_u64() {
2063 let id = ValidationSessionID::from(3456789);
2064 assert_eq!(id.value(), 3456789);
2065 }
2066
2067 #[test]
2068 fn test_validation_session_id_display() {
2069 let id = ValidationSessionID::from(0x34c985);
2070 assert_eq!(format!("{}", id), "v-34c985");
2071 }
2072
2073 #[test]
2074 fn test_validation_session_id_try_from_str_valid() {
2075 let id = ValidationSessionID::try_from("v-deadbeef").unwrap();
2076 assert_eq!(id.value(), 0xdeadbeef);
2077 }
2078
2079 #[test]
2080 fn test_validation_session_id_try_from_string_valid() {
2081 let id = ValidationSessionID::try_from("v-12345678".to_string()).unwrap();
2082 assert_eq!(id.value(), 0x12345678);
2083 }
2084
2085 #[test]
2086 fn test_validation_session_id_try_from_str_invalid_prefix() {
2087 let result = ValidationSessionID::try_from("validation-123");
2088 assert!(result.is_err());
2089 match result {
2090 Err(Error::InvalidParameters(msg)) => {
2091 assert!(msg.contains("must start with 'v-'"));
2092 }
2093 _ => panic!("Expected InvalidParameters error"),
2094 }
2095 }
2096
2097 #[test]
2098 fn test_validation_session_id_try_from_str_invalid_hex() {
2099 let result = ValidationSessionID::try_from("v-xyz");
2100 assert!(result.is_err());
2101 }
2102
2103 #[test]
2104 fn test_validation_session_id_into_u64() {
2105 let id = ValidationSessionID::from(987654);
2106 let value: u64 = id.into();
2107 assert_eq!(value, 987654);
2108 }
2109
2110 #[test]
2112 fn test_snapshot_id_from_u64() {
2113 let id = SnapshotID::from(111222);
2114 assert_eq!(id.value(), 111222);
2115 }
2116
2117 #[test]
2118 fn test_snapshot_id_display() {
2119 let id = SnapshotID::from(0xaabbcc);
2120 assert_eq!(format!("{}", id), "ss-aabbcc");
2121 }
2122
2123 #[test]
2124 fn test_snapshot_id_try_from_str_valid() {
2125 let id = SnapshotID::try_from("ss-aabbcc").unwrap();
2126 assert_eq!(id.value(), 0xaabbcc);
2127 }
2128
2129 #[test]
2130 fn test_snapshot_id_try_from_str_invalid_prefix() {
2131 let result = SnapshotID::try_from("snapshot-123");
2132 assert!(result.is_err());
2133 match result {
2134 Err(Error::InvalidParameters(msg)) => {
2135 assert!(msg.contains("must start with 'ss-'"));
2136 }
2137 _ => panic!("Expected InvalidParameters error"),
2138 }
2139 }
2140
2141 #[test]
2142 fn test_snapshot_id_try_from_str_invalid_hex() {
2143 let result = SnapshotID::try_from("ss-ggg");
2144 assert!(result.is_err());
2145 }
2146
2147 #[test]
2148 fn test_snapshot_id_into_u64() {
2149 let id = SnapshotID::from(333444);
2150 let value: u64 = id.into();
2151 assert_eq!(value, 333444);
2152 }
2153
2154 #[test]
2156 fn test_task_id_from_u64() {
2157 let id = TaskID::from(555666);
2158 assert_eq!(id.value(), 555666);
2159 }
2160
2161 #[test]
2162 fn test_task_id_display() {
2163 let id = TaskID::from(0x123456);
2164 assert_eq!(format!("{}", id), "task-123456");
2165 }
2166
2167 #[test]
2168 fn test_task_id_from_str_valid() {
2169 let id = TaskID::from_str("task-123456").unwrap();
2170 assert_eq!(id.value(), 0x123456);
2171 }
2172
2173 #[test]
2174 fn test_task_id_try_from_str_valid() {
2175 let id = TaskID::try_from("task-abcdef").unwrap();
2176 assert_eq!(id.value(), 0xabcdef);
2177 }
2178
2179 #[test]
2180 fn test_task_id_try_from_string_valid() {
2181 let id = TaskID::try_from("task-fedcba".to_string()).unwrap();
2182 assert_eq!(id.value(), 0xfedcba);
2183 }
2184
2185 #[test]
2186 fn test_task_id_from_str_invalid_prefix() {
2187 let result = TaskID::from_str("t-123");
2188 assert!(result.is_err());
2189 match result {
2190 Err(Error::InvalidParameters(msg)) => {
2191 assert!(msg.contains("must start with 'task-'"));
2192 }
2193 _ => panic!("Expected InvalidParameters error"),
2194 }
2195 }
2196
2197 #[test]
2198 fn test_task_id_from_str_invalid_hex() {
2199 let result = TaskID::from_str("task-zzz");
2200 assert!(result.is_err());
2201 }
2202
2203 #[test]
2204 fn test_task_id_into_u64() {
2205 let id = TaskID::from(777888);
2206 let value: u64 = id.into();
2207 assert_eq!(value, 777888);
2208 }
2209
2210 #[test]
2212 fn test_dataset_id_from_u64() {
2213 let id = DatasetID::from(1193046);
2214 assert_eq!(id.value(), 1193046);
2215 }
2216
2217 #[test]
2218 fn test_dataset_id_display() {
2219 let id = DatasetID::from(0x123abc);
2220 assert_eq!(format!("{}", id), "ds-123abc");
2221 }
2222
2223 #[test]
2224 fn test_dataset_id_from_str_valid() {
2225 let id = DatasetID::from_str("ds-456def").unwrap();
2226 assert_eq!(id.value(), 0x456def);
2227 }
2228
2229 #[test]
2230 fn test_dataset_id_try_from_str_valid() {
2231 let id = DatasetID::try_from("ds-789abc").unwrap();
2232 assert_eq!(id.value(), 0x789abc);
2233 }
2234
2235 #[test]
2236 fn test_dataset_id_try_from_string_valid() {
2237 let id = DatasetID::try_from("ds-fedcba".to_string()).unwrap();
2238 assert_eq!(id.value(), 0xfedcba);
2239 }
2240
2241 #[test]
2242 fn test_dataset_id_from_str_invalid_prefix() {
2243 let result = DatasetID::from_str("dataset-123");
2244 assert!(result.is_err());
2245 match result {
2246 Err(Error::InvalidParameters(msg)) => {
2247 assert!(msg.contains("must start with 'ds-'"));
2248 }
2249 _ => panic!("Expected InvalidParameters error"),
2250 }
2251 }
2252
2253 #[test]
2254 fn test_dataset_id_from_str_invalid_hex() {
2255 let result = DatasetID::from_str("ds-zzz");
2256 assert!(result.is_err());
2257 }
2258
2259 #[test]
2260 fn test_dataset_id_into_u64() {
2261 let id = DatasetID::from(111111);
2262 let value: u64 = id.into();
2263 assert_eq!(value, 111111);
2264 }
2265
2266 #[test]
2268 fn test_annotation_set_id_from_u64() {
2269 let id = AnnotationSetID::from(222333);
2270 assert_eq!(id.value(), 222333);
2271 }
2272
2273 #[test]
2274 fn test_annotation_set_id_display() {
2275 let id = AnnotationSetID::from(0xabcdef);
2276 assert_eq!(format!("{}", id), "as-abcdef");
2277 }
2278
2279 #[test]
2280 fn test_annotation_set_id_from_str_valid() {
2281 let id = AnnotationSetID::from_str("as-abcdef").unwrap();
2282 assert_eq!(id.value(), 0xabcdef);
2283 }
2284
2285 #[test]
2286 fn test_annotation_set_id_try_from_str_valid() {
2287 let id = AnnotationSetID::try_from("as-123456").unwrap();
2288 assert_eq!(id.value(), 0x123456);
2289 }
2290
2291 #[test]
2292 fn test_annotation_set_id_try_from_string_valid() {
2293 let id = AnnotationSetID::try_from("as-fedcba".to_string()).unwrap();
2294 assert_eq!(id.value(), 0xfedcba);
2295 }
2296
2297 #[test]
2298 fn test_annotation_set_id_from_str_invalid_prefix() {
2299 let result = AnnotationSetID::from_str("annotation-123");
2300 assert!(result.is_err());
2301 match result {
2302 Err(Error::InvalidParameters(msg)) => {
2303 assert!(msg.contains("must start with 'as-'"));
2304 }
2305 _ => panic!("Expected InvalidParameters error"),
2306 }
2307 }
2308
2309 #[test]
2310 fn test_annotation_set_id_from_str_invalid_hex() {
2311 let result = AnnotationSetID::from_str("as-zzz");
2312 assert!(result.is_err());
2313 }
2314
2315 #[test]
2316 fn test_annotation_set_id_into_u64() {
2317 let id = AnnotationSetID::from(444555);
2318 let value: u64 = id.into();
2319 assert_eq!(value, 444555);
2320 }
2321
2322 #[test]
2324 fn test_sample_id_from_u64() {
2325 let id = SampleID::from(666777);
2326 assert_eq!(id.value(), 666777);
2327 }
2328
2329 #[test]
2330 fn test_sample_id_display() {
2331 let id = SampleID::from(0x987654);
2332 assert_eq!(format!("{}", id), "s-987654");
2333 }
2334
2335 #[test]
2336 fn test_sample_id_try_from_str_valid() {
2337 let id = SampleID::try_from("s-987654").unwrap();
2338 assert_eq!(id.value(), 0x987654);
2339 }
2340
2341 #[test]
2342 fn test_sample_id_try_from_str_invalid_prefix() {
2343 let result = SampleID::try_from("sample-123");
2344 assert!(result.is_err());
2345 match result {
2346 Err(Error::InvalidParameters(msg)) => {
2347 assert!(msg.contains("must start with 's-'"));
2348 }
2349 _ => panic!("Expected InvalidParameters error"),
2350 }
2351 }
2352
2353 #[test]
2354 fn test_sample_id_try_from_str_invalid_hex() {
2355 let result = SampleID::try_from("s-zzz");
2356 assert!(result.is_err());
2357 }
2358
2359 #[test]
2360 fn test_sample_id_into_u64() {
2361 let id = SampleID::from(888999);
2362 let value: u64 = id.into();
2363 assert_eq!(value, 888999);
2364 }
2365
2366 #[test]
2368 fn test_app_id_from_u64() {
2369 let id = AppId::from(123123);
2370 assert_eq!(id.value(), 123123);
2371 }
2372
2373 #[test]
2374 fn test_app_id_display() {
2375 let id = AppId::from(0x456789);
2376 assert_eq!(format!("{}", id), "app-456789");
2377 }
2378
2379 #[test]
2380 fn test_app_id_try_from_str_valid() {
2381 let id = AppId::try_from("app-456789").unwrap();
2382 assert_eq!(id.value(), 0x456789);
2383 }
2384
2385 #[test]
2386 fn test_app_id_try_from_str_invalid_prefix() {
2387 let result = AppId::try_from("application-123");
2388 assert!(result.is_err());
2389 match result {
2390 Err(Error::InvalidParameters(msg)) => {
2391 assert!(msg.contains("must start with 'app-'"));
2392 }
2393 _ => panic!("Expected InvalidParameters error"),
2394 }
2395 }
2396
2397 #[test]
2398 fn test_app_id_try_from_str_invalid_hex() {
2399 let result = AppId::try_from("app-zzz");
2400 assert!(result.is_err());
2401 }
2402
2403 #[test]
2404 fn test_app_id_into_u64() {
2405 let id = AppId::from(321321);
2406 let value: u64 = id.into();
2407 assert_eq!(value, 321321);
2408 }
2409
2410 #[test]
2412 fn test_image_id_from_u64() {
2413 let id = ImageId::from(789789);
2414 assert_eq!(id.value(), 789789);
2415 }
2416
2417 #[test]
2418 fn test_image_id_display() {
2419 let id = ImageId::from(0xabcd1234);
2420 assert_eq!(format!("{}", id), "im-abcd1234");
2421 }
2422
2423 #[test]
2424 fn test_image_id_try_from_str_valid() {
2425 let id = ImageId::try_from("im-abcd1234").unwrap();
2426 assert_eq!(id.value(), 0xabcd1234);
2427 }
2428
2429 #[test]
2430 fn test_image_id_try_from_str_invalid_prefix() {
2431 let result = ImageId::try_from("image-123");
2432 assert!(result.is_err());
2433 match result {
2434 Err(Error::InvalidParameters(msg)) => {
2435 assert!(msg.contains("must start with 'im-'"));
2436 }
2437 _ => panic!("Expected InvalidParameters error"),
2438 }
2439 }
2440
2441 #[test]
2442 fn test_image_id_try_from_str_invalid_hex() {
2443 let result = ImageId::try_from("im-zzz");
2444 assert!(result.is_err());
2445 }
2446
2447 #[test]
2448 fn test_image_id_into_u64() {
2449 let id = ImageId::from(987987);
2450 let value: u64 = id.into();
2451 assert_eq!(value, 987987);
2452 }
2453
2454 #[test]
2456 fn test_id_types_equality() {
2457 let id1 = ProjectID::from(12345);
2458 let id2 = ProjectID::from(12345);
2459 let id3 = ProjectID::from(54321);
2460
2461 assert_eq!(id1, id2);
2462 assert_ne!(id1, id3);
2463 }
2464
2465 #[test]
2466 fn test_id_types_hash() {
2467 use std::collections::HashSet;
2468
2469 let mut set = HashSet::new();
2470 set.insert(DatasetID::from(100));
2471 set.insert(DatasetID::from(200));
2472 set.insert(DatasetID::from(100)); assert_eq!(set.len(), 2);
2475 assert!(set.contains(&DatasetID::from(100)));
2476 assert!(set.contains(&DatasetID::from(200)));
2477 }
2478
2479 #[test]
2480 fn test_id_types_copy_clone() {
2481 let id1 = ExperimentID::from(999);
2482 let id2 = id1; let id3 = id1; assert_eq!(id1, id2);
2486 assert_eq!(id1, id3);
2487 }
2488
2489 #[test]
2491 fn test_id_zero_value() {
2492 let id = ProjectID::from(0);
2493 assert_eq!(format!("{}", id), "p-0");
2494 assert_eq!(id.value(), 0);
2495 }
2496
2497 #[test]
2498 fn test_id_max_value() {
2499 let id = ProjectID::from(u64::MAX);
2500 assert_eq!(format!("{}", id), "p-ffffffffffffffff");
2501 assert_eq!(id.value(), u64::MAX);
2502 }
2503
2504 #[test]
2505 fn test_id_round_trip_conversion() {
2506 let original = 0xdeadbeef_u64;
2507 let id = TrainingSessionID::from(original);
2508 let back: u64 = id.into();
2509 assert_eq!(original, back);
2510 }
2511
2512 #[test]
2513 fn test_id_case_insensitive_hex() {
2514 let id1 = DatasetID::from_str("ds-ABCDEF").unwrap();
2516 let id2 = DatasetID::from_str("ds-abcdef").unwrap();
2517 assert_eq!(id1.value(), id2.value());
2518 }
2519
2520 #[test]
2521 fn test_id_with_leading_zeros() {
2522 let id = ProjectID::from_str("p-00001234").unwrap();
2523 assert_eq!(id.value(), 0x1234);
2524 }
2525
2526 #[test]
2528 fn test_parameter_integer() {
2529 let param = Parameter::Integer(42);
2530 match param {
2531 Parameter::Integer(val) => assert_eq!(val, 42),
2532 _ => panic!("Expected Integer variant"),
2533 }
2534 }
2535
2536 #[test]
2537 fn test_parameter_real() {
2538 let param = Parameter::Real(2.5);
2539 match param {
2540 Parameter::Real(val) => assert_eq!(val, 2.5),
2541 _ => panic!("Expected Real variant"),
2542 }
2543 }
2544
2545 #[test]
2546 fn test_parameter_boolean() {
2547 let param = Parameter::Boolean(true);
2548 match param {
2549 Parameter::Boolean(val) => assert!(val),
2550 _ => panic!("Expected Boolean variant"),
2551 }
2552 }
2553
2554 #[test]
2555 fn test_parameter_string() {
2556 let param = Parameter::String("test".to_string());
2557 match param {
2558 Parameter::String(val) => assert_eq!(val, "test"),
2559 _ => panic!("Expected String variant"),
2560 }
2561 }
2562
2563 #[test]
2564 fn test_parameter_array() {
2565 let param = Parameter::Array(vec![
2566 Parameter::Integer(1),
2567 Parameter::Integer(2),
2568 Parameter::Integer(3),
2569 ]);
2570 match param {
2571 Parameter::Array(arr) => assert_eq!(arr.len(), 3),
2572 _ => panic!("Expected Array variant"),
2573 }
2574 }
2575
2576 #[test]
2577 fn test_parameter_object() {
2578 let mut map = HashMap::new();
2579 map.insert("key".to_string(), Parameter::Integer(100));
2580 let param = Parameter::Object(map);
2581 match param {
2582 Parameter::Object(obj) => {
2583 assert_eq!(obj.len(), 1);
2584 assert!(obj.contains_key("key"));
2585 }
2586 _ => panic!("Expected Object variant"),
2587 }
2588 }
2589
2590 #[test]
2591 fn test_parameter_clone() {
2592 let param1 = Parameter::Integer(42);
2593 let param2 = param1.clone();
2594 assert_eq!(param1, param2);
2595 }
2596
2597 #[test]
2598 fn test_parameter_nested() {
2599 let inner_array = Parameter::Array(vec![Parameter::Integer(1), Parameter::Integer(2)]);
2600 let outer_array = Parameter::Array(vec![inner_array.clone(), inner_array]);
2601
2602 match outer_array {
2603 Parameter::Array(arr) => {
2604 assert_eq!(arr.len(), 2);
2605 }
2606 _ => panic!("Expected Array variant"),
2607 }
2608 }
2609}