1use crate::{AnnotationSet, Client, Dataset, Error, Sample, client};
5use chrono::{DateTime, Utc};
6use log::trace;
7use reqwest::multipart::{Form, Part};
8use serde::{Deserialize, Deserializer, Serialize};
9use std::{collections::HashMap, fmt::Display, path::PathBuf, str::FromStr};
10
11#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
41#[serde(untagged)]
42pub enum Parameter {
43 Integer(i64),
45 Real(f64),
47 Boolean(bool),
49 String(String),
51 Array(Vec<Parameter>),
53 Object(HashMap<String, Parameter>),
55}
56
57#[derive(Deserialize)]
58pub struct LoginResult {
59 pub(crate) token: String,
60}
61
62#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
82pub struct OrganizationID(u64);
83
84impl Display for OrganizationID {
85 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
86 write!(f, "org-{:x}", self.0)
87 }
88}
89
90impl From<u64> for OrganizationID {
91 fn from(id: u64) -> Self {
92 OrganizationID(id)
93 }
94}
95
96impl From<OrganizationID> for u64 {
97 fn from(val: OrganizationID) -> Self {
98 val.0
99 }
100}
101
102impl OrganizationID {
103 pub fn value(&self) -> u64 {
104 self.0
105 }
106}
107
108impl TryFrom<&str> for OrganizationID {
109 type Error = Error;
110
111 fn try_from(s: &str) -> Result<Self, Self::Error> {
112 let hex_part = s.strip_prefix("org-").ok_or_else(|| {
113 Error::InvalidParameters("Organization ID must start with 'org-' prefix".to_string())
114 })?;
115 let id = u64::from_str_radix(hex_part, 16)?;
116 Ok(OrganizationID(id))
117 }
118}
119
120#[derive(Deserialize, Clone, Debug)]
141pub struct Organization {
142 id: OrganizationID,
143 name: String,
144 #[serde(rename = "latest_credit")]
145 credits: i64,
146}
147
148impl Display for Organization {
149 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
150 write!(f, "{}", self.name())
151 }
152}
153
154impl Organization {
155 pub fn id(&self) -> OrganizationID {
156 self.id
157 }
158
159 pub fn name(&self) -> &str {
160 &self.name
161 }
162
163 pub fn credits(&self) -> i64 {
164 self.credits
165 }
166}
167
168#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
189pub struct ProjectID(u64);
190
191impl Display for ProjectID {
192 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
193 write!(f, "p-{:x}", self.0)
194 }
195}
196
197impl From<u64> for ProjectID {
198 fn from(id: u64) -> Self {
199 ProjectID(id)
200 }
201}
202
203impl From<ProjectID> for u64 {
204 fn from(val: ProjectID) -> Self {
205 val.0
206 }
207}
208
209impl ProjectID {
210 pub fn value(&self) -> u64 {
211 self.0
212 }
213}
214
215impl TryFrom<&str> for ProjectID {
216 type Error = Error;
217
218 fn try_from(s: &str) -> Result<Self, Self::Error> {
219 ProjectID::from_str(s)
220 }
221}
222
223impl TryFrom<String> for ProjectID {
224 type Error = Error;
225
226 fn try_from(s: String) -> Result<Self, Self::Error> {
227 ProjectID::from_str(&s)
228 }
229}
230
231impl FromStr for ProjectID {
232 type Err = Error;
233
234 fn from_str(s: &str) -> Result<Self, Self::Err> {
235 let hex_part = s.strip_prefix("p-").ok_or_else(|| {
236 Error::InvalidParameters("Project ID must start with 'p-' prefix".to_string())
237 })?;
238 let id = u64::from_str_radix(hex_part, 16)?;
239 Ok(ProjectID(id))
240 }
241}
242
243#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
264pub struct ExperimentID(u64);
265
266impl Display for ExperimentID {
267 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
268 write!(f, "exp-{:x}", self.0)
269 }
270}
271
272impl From<u64> for ExperimentID {
273 fn from(id: u64) -> Self {
274 ExperimentID(id)
275 }
276}
277
278impl From<ExperimentID> for u64 {
279 fn from(val: ExperimentID) -> Self {
280 val.0
281 }
282}
283
284impl ExperimentID {
285 pub fn value(&self) -> u64 {
286 self.0
287 }
288}
289
290impl TryFrom<&str> for ExperimentID {
291 type Error = Error;
292
293 fn try_from(s: &str) -> Result<Self, Self::Error> {
294 ExperimentID::from_str(s)
295 }
296}
297
298impl TryFrom<String> for ExperimentID {
299 type Error = Error;
300
301 fn try_from(s: String) -> Result<Self, Self::Error> {
302 ExperimentID::from_str(&s)
303 }
304}
305
306impl FromStr for ExperimentID {
307 type Err = Error;
308
309 fn from_str(s: &str) -> Result<Self, Self::Err> {
310 let hex_part = s.strip_prefix("exp-").ok_or_else(|| {
311 Error::InvalidParameters("Experiment ID must start with 'exp-' prefix".to_string())
312 })?;
313 let id = u64::from_str_radix(hex_part, 16)?;
314 Ok(ExperimentID(id))
315 }
316}
317
318#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
339pub struct TrainingSessionID(u64);
340
341impl Display for TrainingSessionID {
342 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
343 write!(f, "t-{:x}", self.0)
344 }
345}
346
347impl From<u64> for TrainingSessionID {
348 fn from(id: u64) -> Self {
349 TrainingSessionID(id)
350 }
351}
352
353impl From<TrainingSessionID> for u64 {
354 fn from(val: TrainingSessionID) -> Self {
355 val.0
356 }
357}
358
359impl TrainingSessionID {
360 pub fn value(&self) -> u64 {
361 self.0
362 }
363}
364
365impl TryFrom<&str> for TrainingSessionID {
366 type Error = Error;
367
368 fn try_from(s: &str) -> Result<Self, Self::Error> {
369 TrainingSessionID::from_str(s)
370 }
371}
372
373impl TryFrom<String> for TrainingSessionID {
374 type Error = Error;
375
376 fn try_from(s: String) -> Result<Self, Self::Error> {
377 TrainingSessionID::from_str(&s)
378 }
379}
380
381impl FromStr for TrainingSessionID {
382 type Err = Error;
383
384 fn from_str(s: &str) -> Result<Self, Self::Err> {
385 let hex_part = s.strip_prefix("t-").ok_or_else(|| {
386 Error::InvalidParameters("Training Session ID must start with 't-' prefix".to_string())
387 })?;
388 let id = u64::from_str_radix(hex_part, 16)?;
389 Ok(TrainingSessionID(id))
390 }
391}
392
393#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
413pub struct ValidationSessionID(u64);
414
415impl Display for ValidationSessionID {
416 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
417 write!(f, "v-{:x}", self.0)
418 }
419}
420
421impl From<u64> for ValidationSessionID {
422 fn from(id: u64) -> Self {
423 ValidationSessionID(id)
424 }
425}
426
427impl From<ValidationSessionID> for u64 {
428 fn from(val: ValidationSessionID) -> Self {
429 val.0
430 }
431}
432
433impl ValidationSessionID {
434 pub fn value(&self) -> u64 {
435 self.0
436 }
437}
438
439impl TryFrom<&str> for ValidationSessionID {
440 type Error = Error;
441
442 fn try_from(s: &str) -> Result<Self, Self::Error> {
443 let hex_part = s.strip_prefix("v-").ok_or_else(|| {
444 Error::InvalidParameters(
445 "Validation Session ID must start with 'v-' prefix".to_string(),
446 )
447 })?;
448 let id = u64::from_str_radix(hex_part, 16)?;
449 Ok(ValidationSessionID(id))
450 }
451}
452
453impl TryFrom<String> for ValidationSessionID {
454 type Error = Error;
455
456 fn try_from(s: String) -> Result<Self, Self::Error> {
457 ValidationSessionID::try_from(s.as_str())
458 }
459}
460
461#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
462pub struct SnapshotID(u64);
463
464impl Display for SnapshotID {
465 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
466 write!(f, "ss-{:x}", self.0)
467 }
468}
469
470impl From<u64> for SnapshotID {
471 fn from(id: u64) -> Self {
472 SnapshotID(id)
473 }
474}
475
476impl From<SnapshotID> for u64 {
477 fn from(val: SnapshotID) -> Self {
478 val.0
479 }
480}
481
482impl SnapshotID {
483 pub fn value(&self) -> u64 {
484 self.0
485 }
486}
487
488impl TryFrom<&str> for SnapshotID {
489 type Error = Error;
490
491 fn try_from(s: &str) -> Result<Self, Self::Error> {
492 let hex_part = s.strip_prefix("ss-").ok_or_else(|| {
493 Error::InvalidParameters("Snapshot ID must start with 'ss-' prefix".to_string())
494 })?;
495 let id = u64::from_str_radix(hex_part, 16)?;
496 Ok(SnapshotID(id))
497 }
498}
499
500#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
501pub struct TaskID(u64);
502
503impl Display for TaskID {
504 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
505 write!(f, "task-{:x}", self.0)
506 }
507}
508
509impl From<u64> for TaskID {
510 fn from(id: u64) -> Self {
511 TaskID(id)
512 }
513}
514
515impl From<TaskID> for u64 {
516 fn from(val: TaskID) -> Self {
517 val.0
518 }
519}
520
521impl TaskID {
522 pub fn value(&self) -> u64 {
523 self.0
524 }
525}
526
527impl TryFrom<&str> for TaskID {
528 type Error = Error;
529
530 fn try_from(s: &str) -> Result<Self, Self::Error> {
531 TaskID::from_str(s)
532 }
533}
534
535impl TryFrom<String> for TaskID {
536 type Error = Error;
537
538 fn try_from(s: String) -> Result<Self, Self::Error> {
539 TaskID::from_str(&s)
540 }
541}
542
543impl FromStr for TaskID {
544 type Err = Error;
545
546 fn from_str(s: &str) -> Result<Self, Self::Err> {
547 let hex_part = s.strip_prefix("task-").ok_or_else(|| {
548 Error::InvalidParameters("Task ID must start with 'task-' prefix".to_string())
549 })?;
550 let id = u64::from_str_radix(hex_part, 16)?;
551 Ok(TaskID(id))
552 }
553}
554
555#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
576pub struct DatasetID(u64);
577
578impl Display for DatasetID {
579 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
580 write!(f, "ds-{:x}", self.0)
581 }
582}
583
584impl From<u64> for DatasetID {
585 fn from(id: u64) -> Self {
586 DatasetID(id)
587 }
588}
589
590impl From<DatasetID> for u64 {
591 fn from(val: DatasetID) -> Self {
592 val.0
593 }
594}
595
596impl DatasetID {
597 pub fn value(&self) -> u64 {
598 self.0
599 }
600}
601
602impl TryFrom<&str> for DatasetID {
603 type Error = Error;
604
605 fn try_from(s: &str) -> Result<Self, Self::Error> {
606 DatasetID::from_str(s)
607 }
608}
609
610impl TryFrom<String> for DatasetID {
611 type Error = Error;
612
613 fn try_from(s: String) -> Result<Self, Self::Error> {
614 DatasetID::from_str(&s)
615 }
616}
617
618impl FromStr for DatasetID {
619 type Err = Error;
620
621 fn from_str(s: &str) -> Result<Self, Self::Err> {
622 let hex_part = s.strip_prefix("ds-").ok_or_else(|| {
623 Error::InvalidParameters("Dataset ID must start with 'ds-' prefix".to_string())
624 })?;
625 let id = u64::from_str_radix(hex_part, 16)?;
626 Ok(DatasetID(id))
627 }
628}
629
630#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
631pub struct AnnotationSetID(u64);
632
633impl Display for AnnotationSetID {
634 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
635 write!(f, "as-{:x}", self.0)
636 }
637}
638
639impl From<u64> for AnnotationSetID {
640 fn from(id: u64) -> Self {
641 AnnotationSetID(id)
642 }
643}
644
645impl From<AnnotationSetID> for u64 {
646 fn from(val: AnnotationSetID) -> Self {
647 val.0
648 }
649}
650
651impl AnnotationSetID {
652 pub fn value(&self) -> u64 {
653 self.0
654 }
655}
656
657impl TryFrom<&str> for AnnotationSetID {
658 type Error = Error;
659
660 fn try_from(s: &str) -> Result<Self, Self::Error> {
661 AnnotationSetID::from_str(s)
662 }
663}
664
665impl TryFrom<String> for AnnotationSetID {
666 type Error = Error;
667
668 fn try_from(s: String) -> Result<Self, Self::Error> {
669 AnnotationSetID::from_str(&s)
670 }
671}
672
673impl FromStr for AnnotationSetID {
674 type Err = Error;
675
676 fn from_str(s: &str) -> Result<Self, Self::Err> {
677 let hex_part = s.strip_prefix("as-").ok_or_else(|| {
678 Error::InvalidParameters("Annotation Set ID must start with 'as-' prefix".to_string())
679 })?;
680 let id = u64::from_str_radix(hex_part, 16)?;
681 Ok(AnnotationSetID(id))
682 }
683}
684
685#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
686pub struct SampleID(u64);
687
688impl Display for SampleID {
689 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
690 write!(f, "s-{:x}", self.0)
691 }
692}
693
694impl From<u64> for SampleID {
695 fn from(id: u64) -> Self {
696 SampleID(id)
697 }
698}
699
700impl From<SampleID> for u64 {
701 fn from(val: SampleID) -> Self {
702 val.0
703 }
704}
705
706impl SampleID {
707 pub fn value(&self) -> u64 {
708 self.0
709 }
710}
711
712impl TryFrom<&str> for SampleID {
713 type Error = Error;
714
715 fn try_from(s: &str) -> Result<Self, Self::Error> {
716 let hex_part = s.strip_prefix("s-").ok_or_else(|| {
717 Error::InvalidParameters("Sample ID must start with 's-' prefix".to_string())
718 })?;
719 let id = u64::from_str_radix(hex_part, 16)?;
720 Ok(SampleID(id))
721 }
722}
723
724#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
725pub struct AppId(u64);
726
727impl Display for AppId {
728 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
729 write!(f, "app-{:x}", self.0)
730 }
731}
732
733impl From<u64> for AppId {
734 fn from(id: u64) -> Self {
735 AppId(id)
736 }
737}
738
739impl From<AppId> for u64 {
740 fn from(val: AppId) -> Self {
741 val.0
742 }
743}
744
745impl AppId {
746 pub fn value(&self) -> u64 {
747 self.0
748 }
749}
750
751impl TryFrom<&str> for AppId {
752 type Error = Error;
753
754 fn try_from(s: &str) -> Result<Self, Self::Error> {
755 let hex_part = s.strip_prefix("app-").ok_or_else(|| {
756 Error::InvalidParameters("App ID must start with 'app-' prefix".to_string())
757 })?;
758 let id = u64::from_str_radix(hex_part, 16)?;
759 Ok(AppId(id))
760 }
761}
762
763#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
764pub struct ImageId(u64);
765
766impl Display for ImageId {
767 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
768 write!(f, "im-{:x}", self.0)
769 }
770}
771
772impl From<u64> for ImageId {
773 fn from(id: u64) -> Self {
774 ImageId(id)
775 }
776}
777
778impl From<ImageId> for u64 {
779 fn from(val: ImageId) -> Self {
780 val.0
781 }
782}
783
784impl ImageId {
785 pub fn value(&self) -> u64 {
786 self.0
787 }
788}
789
790impl TryFrom<&str> for ImageId {
791 type Error = Error;
792
793 fn try_from(s: &str) -> Result<Self, Self::Error> {
794 let hex_part = s.strip_prefix("im-").ok_or_else(|| {
795 Error::InvalidParameters("Image ID must start with 'im-' prefix".to_string())
796 })?;
797 let id = u64::from_str_radix(hex_part, 16)?;
798 Ok(ImageId(id))
799 }
800}
801
802#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
803pub struct SequenceId(u64);
804
805impl Display for SequenceId {
806 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
807 write!(f, "se-{:x}", self.0)
808 }
809}
810
811impl From<u64> for SequenceId {
812 fn from(id: u64) -> Self {
813 SequenceId(id)
814 }
815}
816
817impl From<SequenceId> for u64 {
818 fn from(val: SequenceId) -> Self {
819 val.0
820 }
821}
822
823impl SequenceId {
824 pub fn value(&self) -> u64 {
825 self.0
826 }
827}
828
829impl TryFrom<&str> for SequenceId {
830 type Error = Error;
831
832 fn try_from(s: &str) -> Result<Self, Self::Error> {
833 let hex_part = s.strip_prefix("se-").ok_or_else(|| {
834 Error::InvalidParameters("Sequence ID must start with 'se-' prefix".to_string())
835 })?;
836 let id = u64::from_str_radix(hex_part, 16)?;
837 Ok(SequenceId(id))
838 }
839}
840
841#[derive(Deserialize, Clone, Debug)]
845pub struct Project {
846 id: ProjectID,
847 name: String,
848 description: String,
849}
850
851impl Display for Project {
852 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
853 write!(f, "{} {}", self.id(), self.name())
854 }
855}
856
857impl Project {
858 pub fn id(&self) -> ProjectID {
859 self.id
860 }
861
862 pub fn name(&self) -> &str {
863 &self.name
864 }
865
866 pub fn description(&self) -> &str {
867 &self.description
868 }
869
870 pub async fn datasets(
871 &self,
872 client: &client::Client,
873 name: Option<&str>,
874 ) -> Result<Vec<Dataset>, Error> {
875 client.datasets(self.id, name).await
876 }
877
878 pub async fn experiments(
879 &self,
880 client: &client::Client,
881 name: Option<&str>,
882 ) -> Result<Vec<Experiment>, Error> {
883 client.experiments(self.id, name).await
884 }
885}
886
887#[derive(Deserialize, Debug)]
888pub struct SamplesCountResult {
889 pub total: u64,
890}
891
892#[derive(Serialize, Clone, Debug)]
893pub struct SamplesListParams {
894 pub dataset_id: DatasetID,
895 #[serde(skip_serializing_if = "Option::is_none")]
896 pub annotation_set_id: Option<AnnotationSetID>,
897 #[serde(skip_serializing_if = "Option::is_none")]
898 pub continue_token: Option<String>,
899 #[serde(skip_serializing_if = "Vec::is_empty")]
900 pub types: Vec<String>,
901 #[serde(skip_serializing_if = "Vec::is_empty")]
902 pub group_names: Vec<String>,
903}
904
905#[derive(Deserialize, Debug)]
906pub struct SamplesListResult {
907 pub samples: Vec<Sample>,
908 pub continue_token: Option<String>,
909}
910
911#[derive(Serialize, Clone, Debug)]
916pub struct SamplesPopulateParams {
917 pub dataset_id: DatasetID,
918 #[serde(skip_serializing_if = "Option::is_none")]
919 pub annotation_set_id: Option<AnnotationSetID>,
920 #[serde(skip_serializing_if = "Option::is_none")]
921 pub presigned_urls: Option<bool>,
922 pub samples: Vec<Sample>,
923}
924
925#[derive(Deserialize, Debug)]
931pub struct SamplesPopulateResult {
932 pub uuid: String,
934 pub urls: Vec<PresignedUrl>,
936}
937
938#[derive(Deserialize, Debug, Clone)]
940pub struct PresignedUrl {
941 pub filename: String,
943 pub key: String,
945 pub url: String,
947}
948
949#[derive(Deserialize)]
950pub struct Snapshot {
951 id: SnapshotID,
952 description: String,
953 status: String,
954 path: String,
955 #[serde(rename = "date")]
956 created: DateTime<Utc>,
957}
958
959impl Display for Snapshot {
960 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
961 write!(f, "{} {}", self.id, self.description)
962 }
963}
964
965impl Snapshot {
966 pub fn id(&self) -> SnapshotID {
967 self.id
968 }
969
970 pub fn description(&self) -> &str {
971 &self.description
972 }
973
974 pub fn status(&self) -> &str {
975 &self.status
976 }
977
978 pub fn path(&self) -> &str {
979 &self.path
980 }
981
982 pub fn created(&self) -> &DateTime<Utc> {
983 &self.created
984 }
985}
986
987#[derive(Serialize, Debug)]
988pub struct SnapshotRestore {
989 pub project_id: ProjectID,
990 pub snapshot_id: SnapshotID,
991 pub fps: u64,
992 #[serde(rename = "enabled_topics", skip_serializing_if = "Vec::is_empty")]
993 pub topics: Vec<String>,
994 #[serde(rename = "label_names", skip_serializing_if = "Vec::is_empty")]
995 pub autolabel: Vec<String>,
996 #[serde(rename = "depth_gen")]
997 pub autodepth: bool,
998 pub agtg_pipeline: bool,
999 #[serde(skip_serializing_if = "Option::is_none")]
1000 pub dataset_name: Option<String>,
1001 #[serde(skip_serializing_if = "Option::is_none")]
1002 pub dataset_description: Option<String>,
1003}
1004
1005#[derive(Deserialize, Debug)]
1006pub struct SnapshotRestoreResult {
1007 pub id: SnapshotID,
1008 pub description: String,
1009 pub dataset_name: String,
1010 pub dataset_id: DatasetID,
1011 pub annotation_set_id: AnnotationSetID,
1012 #[serde(default)]
1013 pub task_id: Option<TaskID>,
1014 pub date: DateTime<Utc>,
1015}
1016
1017#[derive(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(skip_serializing_if = "Option::is_none")]
1486 pub types: Option<Vec<String>>,
1487 #[serde(rename = "manage_types", skip_serializing_if = "Option::is_none")]
1488 pub manager: Option<Vec<String>>,
1489 #[serde(skip_serializing_if = "Option::is_none")]
1490 pub status: Option<Vec<String>>,
1491}
1492
1493#[derive(Deserialize, Debug, Clone)]
1494pub struct TasksListResult {
1495 pub tasks: Vec<Task>,
1496 pub continue_token: Option<String>,
1497}
1498
1499#[derive(Deserialize, Debug, Clone)]
1500pub struct Task {
1501 id: TaskID,
1502 name: String,
1503 #[serde(rename = "type")]
1504 workflow: String,
1505 status: String,
1506 #[serde(rename = "manage_type")]
1507 manager: Option<String>,
1508 #[serde(rename = "instance_type")]
1509 instance: String,
1510 #[serde(rename = "date")]
1511 created: DateTime<Utc>,
1512}
1513
1514impl Task {
1515 pub fn id(&self) -> TaskID {
1516 self.id
1517 }
1518
1519 pub fn name(&self) -> &str {
1520 &self.name
1521 }
1522
1523 pub fn workflow(&self) -> &str {
1524 &self.workflow
1525 }
1526
1527 pub fn status(&self) -> &str {
1528 &self.status
1529 }
1530
1531 pub fn manager(&self) -> Option<&str> {
1532 self.manager.as_deref()
1533 }
1534
1535 pub fn instance(&self) -> &str {
1536 &self.instance
1537 }
1538
1539 pub fn created(&self) -> &DateTime<Utc> {
1540 &self.created
1541 }
1542}
1543
1544impl Display for Task {
1545 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1546 write!(
1547 f,
1548 "{} [{:?} {}] {}",
1549 self.id,
1550 self.manager(),
1551 self.workflow(),
1552 self.name()
1553 )
1554 }
1555}
1556
1557#[derive(Deserialize, Debug)]
1558pub struct TaskInfo {
1559 id: TaskID,
1560 project_id: Option<ProjectID>,
1561 #[serde(rename = "task_description")]
1562 description: String,
1563 #[serde(rename = "type")]
1564 workflow: String,
1565 status: Option<String>,
1566 progress: TaskProgress,
1567 #[serde(rename = "created_date")]
1568 created: DateTime<Utc>,
1569 #[serde(rename = "end_date")]
1570 completed: DateTime<Utc>,
1571}
1572
1573impl Display for TaskInfo {
1574 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1575 write!(f, "{} {}: {}", self.id, self.workflow(), self.description())
1576 }
1577}
1578
1579impl TaskInfo {
1580 pub fn id(&self) -> TaskID {
1581 self.id
1582 }
1583
1584 pub fn project_id(&self) -> Option<ProjectID> {
1585 self.project_id
1586 }
1587
1588 pub fn description(&self) -> &str {
1589 &self.description
1590 }
1591
1592 pub fn workflow(&self) -> &str {
1593 &self.workflow
1594 }
1595
1596 pub fn status(&self) -> &Option<String> {
1597 &self.status
1598 }
1599
1600 pub async fn set_status(&mut self, client: &Client, status: &str) -> Result<(), Error> {
1601 let t = client.task_status(self.id(), status).await?;
1602 self.status = Some(t.status);
1603 Ok(())
1604 }
1605
1606 pub fn stages(&self) -> HashMap<String, Stage> {
1607 match &self.progress.stages {
1608 Some(stages) => stages.clone(),
1609 None => HashMap::new(),
1610 }
1611 }
1612
1613 pub async fn update_stage(
1614 &mut self,
1615 client: &Client,
1616 stage: &str,
1617 status: &str,
1618 message: &str,
1619 percentage: u8,
1620 ) -> Result<(), Error> {
1621 client
1622 .update_stage(self.id(), stage, status, message, percentage)
1623 .await?;
1624 let t = client.task_info(self.id()).await?;
1625 self.progress.stages = Some(t.progress.stages.unwrap_or_default());
1626 Ok(())
1627 }
1628
1629 pub async fn set_stages(
1630 &mut self,
1631 client: &Client,
1632 stages: &[(&str, &str)],
1633 ) -> Result<(), Error> {
1634 client.set_stages(self.id(), stages).await?;
1635 let t = client.task_info(self.id()).await?;
1636 self.progress.stages = Some(t.progress.stages.unwrap_or_default());
1637 Ok(())
1638 }
1639
1640 pub fn created(&self) -> &DateTime<Utc> {
1641 &self.created
1642 }
1643
1644 pub fn completed(&self) -> &DateTime<Utc> {
1645 &self.completed
1646 }
1647}
1648
1649#[derive(Deserialize, Debug)]
1650pub struct TaskProgress {
1651 stages: Option<HashMap<String, Stage>>,
1652}
1653
1654#[derive(Serialize, Debug, Clone)]
1655pub struct TaskStatus {
1656 #[serde(rename = "docker_task_id")]
1657 pub task_id: TaskID,
1658 pub status: String,
1659}
1660
1661#[derive(Serialize, Deserialize, Debug, Clone)]
1662pub struct Stage {
1663 #[serde(rename = "docker_task_id", skip_serializing_if = "Option::is_none")]
1664 task_id: Option<TaskID>,
1665 stage: String,
1666 #[serde(skip_serializing_if = "Option::is_none")]
1667 status: Option<String>,
1668 #[serde(skip_serializing_if = "Option::is_none")]
1669 description: Option<String>,
1670 #[serde(skip_serializing_if = "Option::is_none")]
1671 message: Option<String>,
1672 percentage: u8,
1673}
1674
1675impl Stage {
1676 pub fn new(
1677 task_id: Option<TaskID>,
1678 stage: String,
1679 status: Option<String>,
1680 message: Option<String>,
1681 percentage: u8,
1682 ) -> Self {
1683 Stage {
1684 task_id,
1685 stage,
1686 status,
1687 description: None,
1688 message,
1689 percentage,
1690 }
1691 }
1692
1693 pub fn task_id(&self) -> &Option<TaskID> {
1694 &self.task_id
1695 }
1696
1697 pub fn stage(&self) -> &str {
1698 &self.stage
1699 }
1700
1701 pub fn status(&self) -> &Option<String> {
1702 &self.status
1703 }
1704
1705 pub fn description(&self) -> &Option<String> {
1706 &self.description
1707 }
1708
1709 pub fn message(&self) -> &Option<String> {
1710 &self.message
1711 }
1712
1713 pub fn percentage(&self) -> u8 {
1714 self.percentage
1715 }
1716}
1717
1718#[derive(Serialize, Debug)]
1719pub struct TaskStages {
1720 #[serde(rename = "docker_task_id")]
1721 pub task_id: TaskID,
1722 #[serde(skip_serializing_if = "Vec::is_empty")]
1723 pub stages: Vec<HashMap<String, String>>,
1724}
1725
1726#[derive(Deserialize, Debug)]
1727pub struct Artifact {
1728 name: String,
1729 #[serde(rename = "modelType")]
1730 model_type: String,
1731}
1732
1733impl Artifact {
1734 pub fn name(&self) -> &str {
1735 &self.name
1736 }
1737
1738 pub fn model_type(&self) -> &str {
1739 &self.model_type
1740 }
1741}
1742
1743#[cfg(test)]
1744mod tests {
1745 use super::*;
1746
1747 #[test]
1749 fn test_organization_id_from_u64() {
1750 let id = OrganizationID::from(12345);
1751 assert_eq!(id.value(), 12345);
1752 }
1753
1754 #[test]
1755 fn test_organization_id_display() {
1756 let id = OrganizationID::from(0xabc123);
1757 assert_eq!(format!("{}", id), "org-abc123");
1758 }
1759
1760 #[test]
1761 fn test_organization_id_try_from_str_valid() {
1762 let id = OrganizationID::try_from("org-abc123").unwrap();
1763 assert_eq!(id.value(), 0xabc123);
1764 }
1765
1766 #[test]
1767 fn test_organization_id_try_from_str_invalid_prefix() {
1768 let result = OrganizationID::try_from("invalid-abc123");
1769 assert!(result.is_err());
1770 match result {
1771 Err(Error::InvalidParameters(msg)) => {
1772 assert!(msg.contains("must start with 'org-'"));
1773 }
1774 _ => panic!("Expected InvalidParameters error"),
1775 }
1776 }
1777
1778 #[test]
1779 fn test_organization_id_try_from_str_invalid_hex() {
1780 let result = OrganizationID::try_from("org-xyz");
1781 assert!(result.is_err());
1782 }
1783
1784 #[test]
1785 fn test_organization_id_try_from_str_empty() {
1786 let result = OrganizationID::try_from("org-");
1787 assert!(result.is_err());
1788 }
1789
1790 #[test]
1791 fn test_organization_id_into_u64() {
1792 let id = OrganizationID::from(54321);
1793 let value: u64 = id.into();
1794 assert_eq!(value, 54321);
1795 }
1796
1797 #[test]
1799 fn test_project_id_from_u64() {
1800 let id = ProjectID::from(78910);
1801 assert_eq!(id.value(), 78910);
1802 }
1803
1804 #[test]
1805 fn test_project_id_display() {
1806 let id = ProjectID::from(0xdef456);
1807 assert_eq!(format!("{}", id), "p-def456");
1808 }
1809
1810 #[test]
1811 fn test_project_id_from_str_valid() {
1812 let id = ProjectID::from_str("p-def456").unwrap();
1813 assert_eq!(id.value(), 0xdef456);
1814 }
1815
1816 #[test]
1817 fn test_project_id_try_from_str_valid() {
1818 let id = ProjectID::try_from("p-123abc").unwrap();
1819 assert_eq!(id.value(), 0x123abc);
1820 }
1821
1822 #[test]
1823 fn test_project_id_try_from_string_valid() {
1824 let id = ProjectID::try_from("p-456def".to_string()).unwrap();
1825 assert_eq!(id.value(), 0x456def);
1826 }
1827
1828 #[test]
1829 fn test_project_id_from_str_invalid_prefix() {
1830 let result = ProjectID::from_str("proj-123");
1831 assert!(result.is_err());
1832 match result {
1833 Err(Error::InvalidParameters(msg)) => {
1834 assert!(msg.contains("must start with 'p-'"));
1835 }
1836 _ => panic!("Expected InvalidParameters error"),
1837 }
1838 }
1839
1840 #[test]
1841 fn test_project_id_from_str_invalid_hex() {
1842 let result = ProjectID::from_str("p-notahex");
1843 assert!(result.is_err());
1844 }
1845
1846 #[test]
1847 fn test_project_id_into_u64() {
1848 let id = ProjectID::from(99999);
1849 let value: u64 = id.into();
1850 assert_eq!(value, 99999);
1851 }
1852
1853 #[test]
1855 fn test_experiment_id_from_u64() {
1856 let id = ExperimentID::from(1193046);
1857 assert_eq!(id.value(), 1193046);
1858 }
1859
1860 #[test]
1861 fn test_experiment_id_display() {
1862 let id = ExperimentID::from(0x123abc);
1863 assert_eq!(format!("{}", id), "exp-123abc");
1864 }
1865
1866 #[test]
1867 fn test_experiment_id_from_str_valid() {
1868 let id = ExperimentID::from_str("exp-456def").unwrap();
1869 assert_eq!(id.value(), 0x456def);
1870 }
1871
1872 #[test]
1873 fn test_experiment_id_try_from_str_valid() {
1874 let id = ExperimentID::try_from("exp-789abc").unwrap();
1875 assert_eq!(id.value(), 0x789abc);
1876 }
1877
1878 #[test]
1879 fn test_experiment_id_try_from_string_valid() {
1880 let id = ExperimentID::try_from("exp-fedcba".to_string()).unwrap();
1881 assert_eq!(id.value(), 0xfedcba);
1882 }
1883
1884 #[test]
1885 fn test_experiment_id_from_str_invalid_prefix() {
1886 let result = ExperimentID::from_str("experiment-123");
1887 assert!(result.is_err());
1888 match result {
1889 Err(Error::InvalidParameters(msg)) => {
1890 assert!(msg.contains("must start with 'exp-'"));
1891 }
1892 _ => panic!("Expected InvalidParameters error"),
1893 }
1894 }
1895
1896 #[test]
1897 fn test_experiment_id_from_str_invalid_hex() {
1898 let result = ExperimentID::from_str("exp-zzz");
1899 assert!(result.is_err());
1900 }
1901
1902 #[test]
1903 fn test_experiment_id_into_u64() {
1904 let id = ExperimentID::from(777777);
1905 let value: u64 = id.into();
1906 assert_eq!(value, 777777);
1907 }
1908
1909 #[test]
1911 fn test_training_session_id_from_u64() {
1912 let id = TrainingSessionID::from(7901234);
1913 assert_eq!(id.value(), 7901234);
1914 }
1915
1916 #[test]
1917 fn test_training_session_id_display() {
1918 let id = TrainingSessionID::from(0xabc123);
1919 assert_eq!(format!("{}", id), "t-abc123");
1920 }
1921
1922 #[test]
1923 fn test_training_session_id_from_str_valid() {
1924 let id = TrainingSessionID::from_str("t-abc123").unwrap();
1925 assert_eq!(id.value(), 0xabc123);
1926 }
1927
1928 #[test]
1929 fn test_training_session_id_try_from_str_valid() {
1930 let id = TrainingSessionID::try_from("t-deadbeef").unwrap();
1931 assert_eq!(id.value(), 0xdeadbeef);
1932 }
1933
1934 #[test]
1935 fn test_training_session_id_try_from_string_valid() {
1936 let id = TrainingSessionID::try_from("t-cafebabe".to_string()).unwrap();
1937 assert_eq!(id.value(), 0xcafebabe);
1938 }
1939
1940 #[test]
1941 fn test_training_session_id_from_str_invalid_prefix() {
1942 let result = TrainingSessionID::from_str("training-123");
1943 assert!(result.is_err());
1944 match result {
1945 Err(Error::InvalidParameters(msg)) => {
1946 assert!(msg.contains("must start with 't-'"));
1947 }
1948 _ => panic!("Expected InvalidParameters error"),
1949 }
1950 }
1951
1952 #[test]
1953 fn test_training_session_id_from_str_invalid_hex() {
1954 let result = TrainingSessionID::from_str("t-qqq");
1955 assert!(result.is_err());
1956 }
1957
1958 #[test]
1959 fn test_training_session_id_into_u64() {
1960 let id = TrainingSessionID::from(123456);
1961 let value: u64 = id.into();
1962 assert_eq!(value, 123456);
1963 }
1964
1965 #[test]
1967 fn test_validation_session_id_from_u64() {
1968 let id = ValidationSessionID::from(3456789);
1969 assert_eq!(id.value(), 3456789);
1970 }
1971
1972 #[test]
1973 fn test_validation_session_id_display() {
1974 let id = ValidationSessionID::from(0x34c985);
1975 assert_eq!(format!("{}", id), "v-34c985");
1976 }
1977
1978 #[test]
1979 fn test_validation_session_id_try_from_str_valid() {
1980 let id = ValidationSessionID::try_from("v-deadbeef").unwrap();
1981 assert_eq!(id.value(), 0xdeadbeef);
1982 }
1983
1984 #[test]
1985 fn test_validation_session_id_try_from_string_valid() {
1986 let id = ValidationSessionID::try_from("v-12345678".to_string()).unwrap();
1987 assert_eq!(id.value(), 0x12345678);
1988 }
1989
1990 #[test]
1991 fn test_validation_session_id_try_from_str_invalid_prefix() {
1992 let result = ValidationSessionID::try_from("validation-123");
1993 assert!(result.is_err());
1994 match result {
1995 Err(Error::InvalidParameters(msg)) => {
1996 assert!(msg.contains("must start with 'v-'"));
1997 }
1998 _ => panic!("Expected InvalidParameters error"),
1999 }
2000 }
2001
2002 #[test]
2003 fn test_validation_session_id_try_from_str_invalid_hex() {
2004 let result = ValidationSessionID::try_from("v-xyz");
2005 assert!(result.is_err());
2006 }
2007
2008 #[test]
2009 fn test_validation_session_id_into_u64() {
2010 let id = ValidationSessionID::from(987654);
2011 let value: u64 = id.into();
2012 assert_eq!(value, 987654);
2013 }
2014
2015 #[test]
2017 fn test_snapshot_id_from_u64() {
2018 let id = SnapshotID::from(111222);
2019 assert_eq!(id.value(), 111222);
2020 }
2021
2022 #[test]
2023 fn test_snapshot_id_display() {
2024 let id = SnapshotID::from(0xaabbcc);
2025 assert_eq!(format!("{}", id), "ss-aabbcc");
2026 }
2027
2028 #[test]
2029 fn test_snapshot_id_try_from_str_valid() {
2030 let id = SnapshotID::try_from("ss-aabbcc").unwrap();
2031 assert_eq!(id.value(), 0xaabbcc);
2032 }
2033
2034 #[test]
2035 fn test_snapshot_id_try_from_str_invalid_prefix() {
2036 let result = SnapshotID::try_from("snapshot-123");
2037 assert!(result.is_err());
2038 match result {
2039 Err(Error::InvalidParameters(msg)) => {
2040 assert!(msg.contains("must start with 'ss-'"));
2041 }
2042 _ => panic!("Expected InvalidParameters error"),
2043 }
2044 }
2045
2046 #[test]
2047 fn test_snapshot_id_try_from_str_invalid_hex() {
2048 let result = SnapshotID::try_from("ss-ggg");
2049 assert!(result.is_err());
2050 }
2051
2052 #[test]
2053 fn test_snapshot_id_into_u64() {
2054 let id = SnapshotID::from(333444);
2055 let value: u64 = id.into();
2056 assert_eq!(value, 333444);
2057 }
2058
2059 #[test]
2061 fn test_task_id_from_u64() {
2062 let id = TaskID::from(555666);
2063 assert_eq!(id.value(), 555666);
2064 }
2065
2066 #[test]
2067 fn test_task_id_display() {
2068 let id = TaskID::from(0x123456);
2069 assert_eq!(format!("{}", id), "task-123456");
2070 }
2071
2072 #[test]
2073 fn test_task_id_from_str_valid() {
2074 let id = TaskID::from_str("task-123456").unwrap();
2075 assert_eq!(id.value(), 0x123456);
2076 }
2077
2078 #[test]
2079 fn test_task_id_try_from_str_valid() {
2080 let id = TaskID::try_from("task-abcdef").unwrap();
2081 assert_eq!(id.value(), 0xabcdef);
2082 }
2083
2084 #[test]
2085 fn test_task_id_try_from_string_valid() {
2086 let id = TaskID::try_from("task-fedcba".to_string()).unwrap();
2087 assert_eq!(id.value(), 0xfedcba);
2088 }
2089
2090 #[test]
2091 fn test_task_id_from_str_invalid_prefix() {
2092 let result = TaskID::from_str("t-123");
2093 assert!(result.is_err());
2094 match result {
2095 Err(Error::InvalidParameters(msg)) => {
2096 assert!(msg.contains("must start with 'task-'"));
2097 }
2098 _ => panic!("Expected InvalidParameters error"),
2099 }
2100 }
2101
2102 #[test]
2103 fn test_task_id_from_str_invalid_hex() {
2104 let result = TaskID::from_str("task-zzz");
2105 assert!(result.is_err());
2106 }
2107
2108 #[test]
2109 fn test_task_id_into_u64() {
2110 let id = TaskID::from(777888);
2111 let value: u64 = id.into();
2112 assert_eq!(value, 777888);
2113 }
2114
2115 #[test]
2117 fn test_dataset_id_from_u64() {
2118 let id = DatasetID::from(1193046);
2119 assert_eq!(id.value(), 1193046);
2120 }
2121
2122 #[test]
2123 fn test_dataset_id_display() {
2124 let id = DatasetID::from(0x123abc);
2125 assert_eq!(format!("{}", id), "ds-123abc");
2126 }
2127
2128 #[test]
2129 fn test_dataset_id_from_str_valid() {
2130 let id = DatasetID::from_str("ds-456def").unwrap();
2131 assert_eq!(id.value(), 0x456def);
2132 }
2133
2134 #[test]
2135 fn test_dataset_id_try_from_str_valid() {
2136 let id = DatasetID::try_from("ds-789abc").unwrap();
2137 assert_eq!(id.value(), 0x789abc);
2138 }
2139
2140 #[test]
2141 fn test_dataset_id_try_from_string_valid() {
2142 let id = DatasetID::try_from("ds-fedcba".to_string()).unwrap();
2143 assert_eq!(id.value(), 0xfedcba);
2144 }
2145
2146 #[test]
2147 fn test_dataset_id_from_str_invalid_prefix() {
2148 let result = DatasetID::from_str("dataset-123");
2149 assert!(result.is_err());
2150 match result {
2151 Err(Error::InvalidParameters(msg)) => {
2152 assert!(msg.contains("must start with 'ds-'"));
2153 }
2154 _ => panic!("Expected InvalidParameters error"),
2155 }
2156 }
2157
2158 #[test]
2159 fn test_dataset_id_from_str_invalid_hex() {
2160 let result = DatasetID::from_str("ds-zzz");
2161 assert!(result.is_err());
2162 }
2163
2164 #[test]
2165 fn test_dataset_id_into_u64() {
2166 let id = DatasetID::from(111111);
2167 let value: u64 = id.into();
2168 assert_eq!(value, 111111);
2169 }
2170
2171 #[test]
2173 fn test_annotation_set_id_from_u64() {
2174 let id = AnnotationSetID::from(222333);
2175 assert_eq!(id.value(), 222333);
2176 }
2177
2178 #[test]
2179 fn test_annotation_set_id_display() {
2180 let id = AnnotationSetID::from(0xabcdef);
2181 assert_eq!(format!("{}", id), "as-abcdef");
2182 }
2183
2184 #[test]
2185 fn test_annotation_set_id_from_str_valid() {
2186 let id = AnnotationSetID::from_str("as-abcdef").unwrap();
2187 assert_eq!(id.value(), 0xabcdef);
2188 }
2189
2190 #[test]
2191 fn test_annotation_set_id_try_from_str_valid() {
2192 let id = AnnotationSetID::try_from("as-123456").unwrap();
2193 assert_eq!(id.value(), 0x123456);
2194 }
2195
2196 #[test]
2197 fn test_annotation_set_id_try_from_string_valid() {
2198 let id = AnnotationSetID::try_from("as-fedcba".to_string()).unwrap();
2199 assert_eq!(id.value(), 0xfedcba);
2200 }
2201
2202 #[test]
2203 fn test_annotation_set_id_from_str_invalid_prefix() {
2204 let result = AnnotationSetID::from_str("annotation-123");
2205 assert!(result.is_err());
2206 match result {
2207 Err(Error::InvalidParameters(msg)) => {
2208 assert!(msg.contains("must start with 'as-'"));
2209 }
2210 _ => panic!("Expected InvalidParameters error"),
2211 }
2212 }
2213
2214 #[test]
2215 fn test_annotation_set_id_from_str_invalid_hex() {
2216 let result = AnnotationSetID::from_str("as-zzz");
2217 assert!(result.is_err());
2218 }
2219
2220 #[test]
2221 fn test_annotation_set_id_into_u64() {
2222 let id = AnnotationSetID::from(444555);
2223 let value: u64 = id.into();
2224 assert_eq!(value, 444555);
2225 }
2226
2227 #[test]
2229 fn test_sample_id_from_u64() {
2230 let id = SampleID::from(666777);
2231 assert_eq!(id.value(), 666777);
2232 }
2233
2234 #[test]
2235 fn test_sample_id_display() {
2236 let id = SampleID::from(0x987654);
2237 assert_eq!(format!("{}", id), "s-987654");
2238 }
2239
2240 #[test]
2241 fn test_sample_id_try_from_str_valid() {
2242 let id = SampleID::try_from("s-987654").unwrap();
2243 assert_eq!(id.value(), 0x987654);
2244 }
2245
2246 #[test]
2247 fn test_sample_id_try_from_str_invalid_prefix() {
2248 let result = SampleID::try_from("sample-123");
2249 assert!(result.is_err());
2250 match result {
2251 Err(Error::InvalidParameters(msg)) => {
2252 assert!(msg.contains("must start with 's-'"));
2253 }
2254 _ => panic!("Expected InvalidParameters error"),
2255 }
2256 }
2257
2258 #[test]
2259 fn test_sample_id_try_from_str_invalid_hex() {
2260 let result = SampleID::try_from("s-zzz");
2261 assert!(result.is_err());
2262 }
2263
2264 #[test]
2265 fn test_sample_id_into_u64() {
2266 let id = SampleID::from(888999);
2267 let value: u64 = id.into();
2268 assert_eq!(value, 888999);
2269 }
2270
2271 #[test]
2273 fn test_app_id_from_u64() {
2274 let id = AppId::from(123123);
2275 assert_eq!(id.value(), 123123);
2276 }
2277
2278 #[test]
2279 fn test_app_id_display() {
2280 let id = AppId::from(0x456789);
2281 assert_eq!(format!("{}", id), "app-456789");
2282 }
2283
2284 #[test]
2285 fn test_app_id_try_from_str_valid() {
2286 let id = AppId::try_from("app-456789").unwrap();
2287 assert_eq!(id.value(), 0x456789);
2288 }
2289
2290 #[test]
2291 fn test_app_id_try_from_str_invalid_prefix() {
2292 let result = AppId::try_from("application-123");
2293 assert!(result.is_err());
2294 match result {
2295 Err(Error::InvalidParameters(msg)) => {
2296 assert!(msg.contains("must start with 'app-'"));
2297 }
2298 _ => panic!("Expected InvalidParameters error"),
2299 }
2300 }
2301
2302 #[test]
2303 fn test_app_id_try_from_str_invalid_hex() {
2304 let result = AppId::try_from("app-zzz");
2305 assert!(result.is_err());
2306 }
2307
2308 #[test]
2309 fn test_app_id_into_u64() {
2310 let id = AppId::from(321321);
2311 let value: u64 = id.into();
2312 assert_eq!(value, 321321);
2313 }
2314
2315 #[test]
2317 fn test_image_id_from_u64() {
2318 let id = ImageId::from(789789);
2319 assert_eq!(id.value(), 789789);
2320 }
2321
2322 #[test]
2323 fn test_image_id_display() {
2324 let id = ImageId::from(0xabcd1234);
2325 assert_eq!(format!("{}", id), "im-abcd1234");
2326 }
2327
2328 #[test]
2329 fn test_image_id_try_from_str_valid() {
2330 let id = ImageId::try_from("im-abcd1234").unwrap();
2331 assert_eq!(id.value(), 0xabcd1234);
2332 }
2333
2334 #[test]
2335 fn test_image_id_try_from_str_invalid_prefix() {
2336 let result = ImageId::try_from("image-123");
2337 assert!(result.is_err());
2338 match result {
2339 Err(Error::InvalidParameters(msg)) => {
2340 assert!(msg.contains("must start with 'im-'"));
2341 }
2342 _ => panic!("Expected InvalidParameters error"),
2343 }
2344 }
2345
2346 #[test]
2347 fn test_image_id_try_from_str_invalid_hex() {
2348 let result = ImageId::try_from("im-zzz");
2349 assert!(result.is_err());
2350 }
2351
2352 #[test]
2353 fn test_image_id_into_u64() {
2354 let id = ImageId::from(987987);
2355 let value: u64 = id.into();
2356 assert_eq!(value, 987987);
2357 }
2358
2359 #[test]
2361 fn test_id_types_equality() {
2362 let id1 = ProjectID::from(12345);
2363 let id2 = ProjectID::from(12345);
2364 let id3 = ProjectID::from(54321);
2365
2366 assert_eq!(id1, id2);
2367 assert_ne!(id1, id3);
2368 }
2369
2370 #[test]
2371 fn test_id_types_hash() {
2372 use std::collections::HashSet;
2373
2374 let mut set = HashSet::new();
2375 set.insert(DatasetID::from(100));
2376 set.insert(DatasetID::from(200));
2377 set.insert(DatasetID::from(100)); assert_eq!(set.len(), 2);
2380 assert!(set.contains(&DatasetID::from(100)));
2381 assert!(set.contains(&DatasetID::from(200)));
2382 }
2383
2384 #[test]
2385 fn test_id_types_copy_clone() {
2386 let id1 = ExperimentID::from(999);
2387 let id2 = id1; let id3 = id1; assert_eq!(id1, id2);
2391 assert_eq!(id1, id3);
2392 }
2393
2394 #[test]
2396 fn test_id_zero_value() {
2397 let id = ProjectID::from(0);
2398 assert_eq!(format!("{}", id), "p-0");
2399 assert_eq!(id.value(), 0);
2400 }
2401
2402 #[test]
2403 fn test_id_max_value() {
2404 let id = ProjectID::from(u64::MAX);
2405 assert_eq!(format!("{}", id), "p-ffffffffffffffff");
2406 assert_eq!(id.value(), u64::MAX);
2407 }
2408
2409 #[test]
2410 fn test_id_round_trip_conversion() {
2411 let original = 0xdeadbeef_u64;
2412 let id = TrainingSessionID::from(original);
2413 let back: u64 = id.into();
2414 assert_eq!(original, back);
2415 }
2416
2417 #[test]
2418 fn test_id_case_insensitive_hex() {
2419 let id1 = DatasetID::from_str("ds-ABCDEF").unwrap();
2421 let id2 = DatasetID::from_str("ds-abcdef").unwrap();
2422 assert_eq!(id1.value(), id2.value());
2423 }
2424
2425 #[test]
2426 fn test_id_with_leading_zeros() {
2427 let id = ProjectID::from_str("p-00001234").unwrap();
2428 assert_eq!(id.value(), 0x1234);
2429 }
2430
2431 #[test]
2433 fn test_parameter_integer() {
2434 let param = Parameter::Integer(42);
2435 match param {
2436 Parameter::Integer(val) => assert_eq!(val, 42),
2437 _ => panic!("Expected Integer variant"),
2438 }
2439 }
2440
2441 #[test]
2442 fn test_parameter_real() {
2443 let param = Parameter::Real(2.5);
2444 match param {
2445 Parameter::Real(val) => assert_eq!(val, 2.5),
2446 _ => panic!("Expected Real variant"),
2447 }
2448 }
2449
2450 #[test]
2451 fn test_parameter_boolean() {
2452 let param = Parameter::Boolean(true);
2453 match param {
2454 Parameter::Boolean(val) => assert!(val),
2455 _ => panic!("Expected Boolean variant"),
2456 }
2457 }
2458
2459 #[test]
2460 fn test_parameter_string() {
2461 let param = Parameter::String("test".to_string());
2462 match param {
2463 Parameter::String(val) => assert_eq!(val, "test"),
2464 _ => panic!("Expected String variant"),
2465 }
2466 }
2467
2468 #[test]
2469 fn test_parameter_array() {
2470 let param = Parameter::Array(vec![
2471 Parameter::Integer(1),
2472 Parameter::Integer(2),
2473 Parameter::Integer(3),
2474 ]);
2475 match param {
2476 Parameter::Array(arr) => assert_eq!(arr.len(), 3),
2477 _ => panic!("Expected Array variant"),
2478 }
2479 }
2480
2481 #[test]
2482 fn test_parameter_object() {
2483 let mut map = HashMap::new();
2484 map.insert("key".to_string(), Parameter::Integer(100));
2485 let param = Parameter::Object(map);
2486 match param {
2487 Parameter::Object(obj) => {
2488 assert_eq!(obj.len(), 1);
2489 assert!(obj.contains_key("key"));
2490 }
2491 _ => panic!("Expected Object variant"),
2492 }
2493 }
2494
2495 #[test]
2496 fn test_parameter_clone() {
2497 let param1 = Parameter::Integer(42);
2498 let param2 = param1.clone();
2499 assert_eq!(param1, param2);
2500 }
2501
2502 #[test]
2503 fn test_parameter_nested() {
2504 let inner_array = Parameter::Array(vec![Parameter::Integer(1), Parameter::Integer(2)]);
2505 let outer_array = Parameter::Array(vec![inner_array.clone(), inner_array]);
2506
2507 match outer_array {
2508 Parameter::Array(arr) => {
2509 assert_eq!(arr.len(), 2);
2510 }
2511 _ => panic!("Expected Array variant"),
2512 }
2513 }
2514}