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