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
62macro_rules! typeid {
71 ($(#[$meta:meta])* $name:ident, $prefix:literal) => {
72 $(#[$meta])*
73 #[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
74 pub struct $name(u64);
75
76 impl Display for $name {
77 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
78 write!(f, concat!($prefix, "-{:x}"), self.0)
79 }
80 }
81
82 impl From<u64> for $name {
83 fn from(id: u64) -> Self {
84 $name(id)
85 }
86 }
87
88 impl From<$name> for u64 {
89 fn from(val: $name) -> Self {
90 val.0
91 }
92 }
93
94 impl $name {
95 pub fn value(&self) -> u64 {
97 self.0
98 }
99 }
100
101 impl TryFrom<&str> for $name {
102 type Error = Error;
103
104 fn try_from(s: &str) -> Result<Self, Self::Error> {
105 $name::from_str(s)
106 }
107 }
108
109 impl TryFrom<String> for $name {
110 type Error = Error;
111
112 fn try_from(s: String) -> Result<Self, Self::Error> {
113 $name::from_str(&s)
114 }
115 }
116
117 impl FromStr for $name {
118 type Err = Error;
119
120 fn from_str(s: &str) -> Result<Self, Self::Err> {
121 let hex_part =
122 s.strip_prefix(concat!($prefix, "-")).ok_or_else(|| {
123 Error::InvalidParameters(format!(
124 "{} must start with '{}-' prefix",
125 stringify!($name),
126 $prefix
127 ))
128 })?;
129 let id = u64::from_str_radix(hex_part, 16)?;
130 Ok($name(id))
131 }
132 }
133 };
134}
135
136typeid!(
137 OrganizationID,
157 "org"
158);
159
160#[derive(Deserialize, Clone, Debug)]
181pub struct Organization {
182 id: OrganizationID,
183 name: String,
184 #[serde(rename = "latest_credit")]
185 credits: i64,
186}
187
188impl Display for Organization {
189 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
190 write!(f, "{}", self.name())
191 }
192}
193
194impl Organization {
195 pub fn id(&self) -> OrganizationID {
196 self.id
197 }
198
199 pub fn name(&self) -> &str {
200 &self.name
201 }
202
203 pub fn credits(&self) -> i64 {
204 self.credits
205 }
206}
207
208typeid!(
209 ProjectID,
230 "p"
231);
232
233typeid!(
234 ExperimentID,
255 "exp"
256);
257
258typeid!(
259 TrainingSessionID,
280 "t"
281);
282
283typeid!(
284 ValidationSessionID,
304 "v"
305);
306
307typeid!(
308 SnapshotID,
324 "ss"
325);
326
327typeid!(
328 TaskID,
344 "task"
345);
346
347typeid!(
348 DatasetID,
369 "ds"
370);
371
372typeid!(
373 AnnotationSetID,
389 "as"
390);
391
392typeid!(
393 SampleID,
409 "s"
410);
411
412typeid!(
413 AppId,
419 "app"
420);
421
422typeid!(
423 ImageId,
429 "im"
430);
431
432typeid!(
433 SequenceId,
439 "se"
440);
441
442#[derive(Deserialize, Clone, Debug)]
446pub struct Project {
447 id: ProjectID,
448 name: String,
449 description: String,
450}
451
452impl Display for Project {
453 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
454 write!(f, "{} {}", self.id(), self.name())
455 }
456}
457
458impl Project {
459 pub fn id(&self) -> ProjectID {
460 self.id
461 }
462
463 pub fn name(&self) -> &str {
464 &self.name
465 }
466
467 pub fn description(&self) -> &str {
468 &self.description
469 }
470
471 pub async fn datasets(
472 &self,
473 client: &client::Client,
474 name: Option<&str>,
475 ) -> Result<Vec<Dataset>, Error> {
476 client.datasets(self.id, name).await
477 }
478
479 pub async fn experiments(
480 &self,
481 client: &client::Client,
482 name: Option<&str>,
483 ) -> Result<Vec<Experiment>, Error> {
484 client.experiments(self.id, name).await
485 }
486}
487
488#[derive(Deserialize, Debug)]
489pub struct SamplesCountResult {
490 pub total: u64,
491}
492
493#[derive(Serialize, Clone, Debug)]
494pub struct SamplesListParams {
495 pub dataset_id: DatasetID,
496 #[serde(skip_serializing_if = "Option::is_none")]
497 pub annotation_set_id: Option<AnnotationSetID>,
498 #[serde(skip_serializing_if = "Option::is_none")]
499 pub continue_token: Option<String>,
500 #[serde(skip_serializing_if = "Vec::is_empty")]
501 pub types: Vec<String>,
502 #[serde(skip_serializing_if = "Vec::is_empty")]
503 pub group_names: Vec<String>,
504}
505
506#[derive(Deserialize, Debug)]
507pub struct SamplesListResult {
508 pub samples: Vec<Sample>,
509 pub continue_token: Option<String>,
510}
511
512#[derive(Serialize, Clone, Debug)]
517pub struct SamplesPopulateParams {
518 pub dataset_id: DatasetID,
519 #[serde(skip_serializing_if = "Option::is_none")]
520 pub annotation_set_id: Option<AnnotationSetID>,
521 #[serde(skip_serializing_if = "Option::is_none")]
522 pub presigned_urls: Option<bool>,
523 pub samples: Vec<Sample>,
524}
525
526#[derive(Deserialize, Debug, Clone)]
532pub struct SamplesPopulateResult {
533 pub uuid: String,
535 pub urls: Vec<PresignedUrl>,
537}
538
539#[derive(Deserialize, Debug, Clone)]
541pub struct PresignedUrl {
542 pub filename: String,
544 pub key: String,
546 pub url: String,
548}
549
550#[derive(Serialize, Clone, Debug)]
563pub struct ServerAnnotation {
564 #[serde(skip_serializing_if = "Option::is_none")]
566 pub label_id: Option<u64>,
567 #[serde(skip_serializing_if = "Option::is_none")]
569 pub label_index: Option<u64>,
570 #[serde(skip_serializing_if = "Option::is_none")]
572 pub label_name: Option<String>,
573 #[serde(rename = "type")]
575 pub annotation_type: String,
576 pub x: f64,
578 pub y: f64,
580 pub w: f64,
582 pub h: f64,
584 pub score: f64,
586 #[serde(skip_serializing_if = "String::is_empty")]
588 pub polygon: String,
589 pub image_id: u64,
591 pub annotation_set_id: u64,
593 #[serde(skip_serializing_if = "Option::is_none")]
595 pub object_reference: Option<String>,
596}
597
598#[derive(Serialize, Debug)]
600pub struct AnnotationAddBulkParams {
601 pub annotation_set_id: u64,
602 pub annotations: Vec<ServerAnnotation>,
603}
604
605#[derive(Serialize, Debug)]
607pub struct AnnotationBulkDeleteParams {
608 pub annotation_set_id: u64,
609 pub annotation_types: Vec<String>,
610 #[serde(skip_serializing_if = "Vec::is_empty")]
612 pub image_ids: Vec<u64>,
613 #[serde(skip_serializing_if = "Option::is_none")]
615 pub delete_all: Option<bool>,
616}
617
618#[derive(Deserialize)]
619pub struct Snapshot {
620 id: SnapshotID,
621 description: String,
622 status: String,
623 path: String,
624 #[serde(rename = "date")]
625 created: DateTime<Utc>,
626}
627
628impl Display for Snapshot {
629 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
630 write!(f, "{} {}", self.id, self.description)
631 }
632}
633
634impl Snapshot {
635 pub fn id(&self) -> SnapshotID {
636 self.id
637 }
638
639 pub fn description(&self) -> &str {
640 &self.description
641 }
642
643 pub fn status(&self) -> &str {
644 &self.status
645 }
646
647 pub fn path(&self) -> &str {
648 &self.path
649 }
650
651 pub fn created(&self) -> &DateTime<Utc> {
652 &self.created
653 }
654}
655
656#[derive(Serialize, Debug)]
657pub struct SnapshotRestore {
658 pub project_id: ProjectID,
659 pub snapshot_id: SnapshotID,
660 pub fps: u64,
661 #[serde(rename = "enabled_topics", skip_serializing_if = "Vec::is_empty")]
662 pub topics: Vec<String>,
663 #[serde(rename = "label_names", skip_serializing_if = "Vec::is_empty")]
664 pub autolabel: Vec<String>,
665 #[serde(rename = "depth_gen")]
666 pub autodepth: bool,
667 pub agtg_pipeline: bool,
668 #[serde(skip_serializing_if = "Option::is_none")]
669 pub dataset_name: Option<String>,
670 #[serde(skip_serializing_if = "Option::is_none")]
671 pub dataset_description: Option<String>,
672}
673
674#[derive(Deserialize, Debug)]
675pub struct SnapshotRestoreResult {
676 pub id: SnapshotID,
677 pub description: String,
678 pub dataset_name: String,
679 pub dataset_id: DatasetID,
680 pub annotation_set_id: AnnotationSetID,
681 #[serde(default)]
682 pub task_id: Option<TaskID>,
683 pub date: DateTime<Utc>,
684}
685
686#[derive(Serialize, Debug)]
691pub struct SnapshotCreateFromDataset {
692 pub description: String,
694 pub dataset_id: DatasetID,
696 pub annotation_set_id: AnnotationSetID,
698}
699
700#[derive(Deserialize, Debug)]
704pub struct SnapshotFromDatasetResult {
705 #[serde(alias = "snapshot_id")]
707 pub id: SnapshotID,
708 #[serde(default)]
710 pub task_id: Option<TaskID>,
711}
712
713#[derive(Deserialize)]
714pub struct Experiment {
715 id: ExperimentID,
716 project_id: ProjectID,
717 name: String,
718 description: String,
719}
720
721impl Display for Experiment {
722 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
723 write!(f, "{} {}", self.id, self.name)
724 }
725}
726
727impl Experiment {
728 pub fn id(&self) -> ExperimentID {
729 self.id
730 }
731
732 pub fn project_id(&self) -> ProjectID {
733 self.project_id
734 }
735
736 pub fn name(&self) -> &str {
737 &self.name
738 }
739
740 pub fn description(&self) -> &str {
741 &self.description
742 }
743
744 pub async fn project(&self, client: &client::Client) -> Result<Project, Error> {
745 client.project(self.project_id).await
746 }
747
748 pub async fn training_sessions(
749 &self,
750 client: &client::Client,
751 name: Option<&str>,
752 ) -> Result<Vec<TrainingSession>, Error> {
753 client.training_sessions(self.id, name).await
754 }
755}
756
757#[derive(Serialize, Debug)]
758pub struct PublishMetrics {
759 #[serde(rename = "trainer_session_id", skip_serializing_if = "Option::is_none")]
760 pub trainer_session_id: Option<TrainingSessionID>,
761 #[serde(
762 rename = "validate_session_id",
763 skip_serializing_if = "Option::is_none"
764 )]
765 pub validate_session_id: Option<ValidationSessionID>,
766 pub metrics: HashMap<String, Parameter>,
767}
768
769#[derive(Deserialize)]
770struct TrainingSessionParams {
771 model_params: HashMap<String, Parameter>,
772 dataset_params: DatasetParams,
773}
774
775#[derive(Deserialize)]
776pub struct TrainingSession {
777 id: TrainingSessionID,
778 #[serde(rename = "trainer_id")]
779 experiment_id: ExperimentID,
780 model: String,
781 name: String,
782 description: String,
783 params: TrainingSessionParams,
784 #[serde(rename = "docker_task")]
785 task: Task,
786}
787
788impl Display for TrainingSession {
789 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
790 write!(f, "{} {}", self.id, self.name())
791 }
792}
793
794impl TrainingSession {
795 pub fn id(&self) -> TrainingSessionID {
796 self.id
797 }
798
799 pub fn name(&self) -> &str {
800 &self.name
801 }
802
803 pub fn description(&self) -> &str {
804 &self.description
805 }
806
807 pub fn model(&self) -> &str {
808 &self.model
809 }
810
811 pub fn experiment_id(&self) -> ExperimentID {
812 self.experiment_id
813 }
814
815 pub fn task(&self) -> Task {
816 self.task.clone()
817 }
818
819 pub fn model_params(&self) -> &HashMap<String, Parameter> {
820 &self.params.model_params
821 }
822
823 pub fn dataset_params(&self) -> &DatasetParams {
824 &self.params.dataset_params
825 }
826
827 pub fn train_group(&self) -> &str {
828 &self.params.dataset_params.train_group
829 }
830
831 pub fn val_group(&self) -> &str {
832 &self.params.dataset_params.val_group
833 }
834
835 pub async fn experiment(&self, client: &client::Client) -> Result<Experiment, Error> {
836 client.experiment(self.experiment_id).await
837 }
838
839 pub async fn dataset(&self, client: &client::Client) -> Result<Dataset, Error> {
840 client.dataset(self.params.dataset_params.dataset_id).await
841 }
842
843 pub async fn annotation_set(&self, client: &client::Client) -> Result<AnnotationSet, Error> {
844 client
845 .annotation_set(self.params.dataset_params.annotation_set_id)
846 .await
847 }
848
849 pub async fn artifacts(&self, client: &client::Client) -> Result<Vec<Artifact>, Error> {
850 client.artifacts(self.id).await
851 }
852
853 pub async fn metrics(
854 &self,
855 client: &client::Client,
856 ) -> Result<HashMap<String, Parameter>, Error> {
857 #[derive(Deserialize)]
858 #[serde(untagged, deny_unknown_fields, expecting = "map, empty map or string")]
859 enum Response {
860 Empty {},
861 Map(HashMap<String, Parameter>),
862 String(String),
863 }
864
865 let params = HashMap::from([("trainer_session_id", self.id().value())]);
866 let resp: Response = client
867 .rpc("trainer.session.metrics".to_owned(), Some(params))
868 .await?;
869
870 Ok(match resp {
871 Response::String(metrics) => serde_json::from_str(&metrics)?,
872 Response::Map(metrics) => metrics,
873 Response::Empty {} => HashMap::new(),
874 })
875 }
876
877 pub async fn set_metrics(
878 &self,
879 client: &client::Client,
880 metrics: HashMap<String, Parameter>,
881 ) -> Result<(), Error> {
882 let metrics = PublishMetrics {
883 trainer_session_id: Some(self.id()),
884 validate_session_id: None,
885 metrics,
886 };
887
888 let _: String = client
889 .rpc("trainer.session.metrics".to_owned(), Some(metrics))
890 .await?;
891
892 Ok(())
893 }
894
895 pub async fn download_artifact(
897 &self,
898 client: &client::Client,
899 filename: &str,
900 ) -> Result<Vec<u8>, Error> {
901 client
902 .fetch(&format!(
903 "download_model?training_session_id={}&file={}",
904 self.id().value(),
905 filename
906 ))
907 .await
908 }
909
910 pub async fn upload_artifact(
914 &self,
915 client: &client::Client,
916 filename: &str,
917 path: PathBuf,
918 ) -> Result<(), Error> {
919 self.upload(client, &[(format!("artifacts/{}", filename), path)])
920 .await
921 }
922
923 pub async fn download_checkpoint(
925 &self,
926 client: &client::Client,
927 filename: &str,
928 ) -> Result<Vec<u8>, Error> {
929 client
930 .fetch(&format!(
931 "download_checkpoint?folder=checkpoints&training_session_id={}&file={}",
932 self.id().value(),
933 filename
934 ))
935 .await
936 }
937
938 pub async fn upload_checkpoint(
942 &self,
943 client: &client::Client,
944 filename: &str,
945 path: PathBuf,
946 ) -> Result<(), Error> {
947 self.upload(client, &[(format!("checkpoints/{}", filename), path)])
948 .await
949 }
950
951 pub async fn download(&self, client: &client::Client, filename: &str) -> Result<String, Error> {
955 #[derive(Serialize)]
956 struct DownloadRequest {
957 session_id: TrainingSessionID,
958 file_path: String,
959 }
960
961 let params = DownloadRequest {
962 session_id: self.id(),
963 file_path: filename.to_string(),
964 };
965
966 client
967 .rpc("trainer.download.file".to_owned(), Some(params))
968 .await
969 }
970
971 pub async fn upload(
972 &self,
973 client: &client::Client,
974 files: &[(String, PathBuf)],
975 ) -> Result<(), Error> {
976 let mut parts = Form::new().part(
977 "params",
978 Part::text(format!("{{ \"session_id\": {} }}", self.id().value())),
979 );
980
981 for (name, path) in files {
982 let file_part = Part::file(path).await?.file_name(name.to_owned());
983 parts = parts.part("file", file_part);
984 }
985
986 let result = client.post_multipart("trainer.upload.files", parts).await?;
987 trace!("TrainingSession::upload: {:?}", result);
988 Ok(())
989 }
990}
991
992#[derive(Deserialize, Clone, Debug)]
993pub struct ValidationSession {
994 id: ValidationSessionID,
995 description: String,
996 dataset_id: DatasetID,
997 experiment_id: ExperimentID,
998 training_session_id: TrainingSessionID,
999 #[serde(rename = "gt_annotation_set_id")]
1000 annotation_set_id: AnnotationSetID,
1001 #[serde(deserialize_with = "validation_session_params")]
1002 params: HashMap<String, Parameter>,
1003 #[serde(rename = "docker_task")]
1004 task: Task,
1005}
1006
1007fn validation_session_params<'de, D>(
1008 deserializer: D,
1009) -> Result<HashMap<String, Parameter>, D::Error>
1010where
1011 D: Deserializer<'de>,
1012{
1013 #[derive(Deserialize)]
1014 struct ModelParams {
1015 validation: Option<HashMap<String, Parameter>>,
1016 }
1017
1018 #[derive(Deserialize)]
1019 struct ValidateParams {
1020 model: String,
1021 }
1022
1023 #[derive(Deserialize)]
1024 struct Params {
1025 model_params: ModelParams,
1026 validate_params: ValidateParams,
1027 }
1028
1029 let params = Params::deserialize(deserializer)?;
1030 let params = match params.model_params.validation {
1031 Some(mut map) => {
1032 map.insert(
1033 "model".to_string(),
1034 Parameter::String(params.validate_params.model),
1035 );
1036 map
1037 }
1038 None => HashMap::from([(
1039 "model".to_string(),
1040 Parameter::String(params.validate_params.model),
1041 )]),
1042 };
1043
1044 Ok(params)
1045}
1046
1047impl ValidationSession {
1048 pub fn id(&self) -> ValidationSessionID {
1049 self.id
1050 }
1051
1052 pub fn name(&self) -> &str {
1053 self.task.name()
1054 }
1055
1056 pub fn description(&self) -> &str {
1057 &self.description
1058 }
1059
1060 pub fn dataset_id(&self) -> DatasetID {
1061 self.dataset_id
1062 }
1063
1064 pub fn experiment_id(&self) -> ExperimentID {
1065 self.experiment_id
1066 }
1067
1068 pub fn training_session_id(&self) -> TrainingSessionID {
1069 self.training_session_id
1070 }
1071
1072 pub fn annotation_set_id(&self) -> AnnotationSetID {
1073 self.annotation_set_id
1074 }
1075
1076 pub fn params(&self) -> &HashMap<String, Parameter> {
1077 &self.params
1078 }
1079
1080 pub fn task(&self) -> &Task {
1081 &self.task
1082 }
1083
1084 pub async fn metrics(
1085 &self,
1086 client: &client::Client,
1087 ) -> Result<HashMap<String, Parameter>, Error> {
1088 #[derive(Deserialize)]
1089 #[serde(untagged, deny_unknown_fields, expecting = "map, empty map or string")]
1090 enum Response {
1091 Empty {},
1092 Map(HashMap<String, Parameter>),
1093 String(String),
1094 }
1095
1096 let params = HashMap::from([("validate_session_id", self.id().value())]);
1097 let resp: Response = client
1098 .rpc("validate.session.metrics".to_owned(), Some(params))
1099 .await?;
1100
1101 Ok(match resp {
1102 Response::String(metrics) => serde_json::from_str(&metrics)?,
1103 Response::Map(metrics) => metrics,
1104 Response::Empty {} => HashMap::new(),
1105 })
1106 }
1107
1108 pub async fn set_metrics(
1109 &self,
1110 client: &client::Client,
1111 metrics: HashMap<String, Parameter>,
1112 ) -> Result<(), Error> {
1113 let metrics = PublishMetrics {
1114 trainer_session_id: None,
1115 validate_session_id: Some(self.id()),
1116 metrics,
1117 };
1118
1119 let _: String = client
1120 .rpc("validate.session.metrics".to_owned(), Some(metrics))
1121 .await?;
1122
1123 Ok(())
1124 }
1125
1126 pub async fn upload(
1127 &self,
1128 client: &client::Client,
1129 files: &[(String, PathBuf)],
1130 ) -> Result<(), Error> {
1131 let mut parts = Form::new().part(
1132 "params",
1133 Part::text(format!("{{ \"session_id\": {} }}", self.id().value())),
1134 );
1135
1136 for (name, path) in files {
1137 let file_part = Part::file(path).await?.file_name(name.to_owned());
1138 parts = parts.part("file", file_part);
1139 }
1140
1141 let result = client
1142 .post_multipart("validate.upload.files", parts)
1143 .await?;
1144 trace!("ValidationSession::upload: {:?}", result);
1145 Ok(())
1146 }
1147}
1148
1149#[derive(Deserialize, Clone, Debug)]
1150pub struct DatasetParams {
1151 dataset_id: DatasetID,
1152 annotation_set_id: AnnotationSetID,
1153 #[serde(rename = "train_group_name")]
1154 train_group: String,
1155 #[serde(rename = "val_group_name")]
1156 val_group: String,
1157}
1158
1159impl DatasetParams {
1160 pub fn dataset_id(&self) -> DatasetID {
1161 self.dataset_id
1162 }
1163
1164 pub fn annotation_set_id(&self) -> AnnotationSetID {
1165 self.annotation_set_id
1166 }
1167
1168 pub fn train_group(&self) -> &str {
1169 &self.train_group
1170 }
1171
1172 pub fn val_group(&self) -> &str {
1173 &self.val_group
1174 }
1175}
1176
1177#[derive(Serialize, Debug, Clone)]
1178pub struct TasksListParams {
1179 #[serde(skip_serializing_if = "Option::is_none")]
1180 pub continue_token: Option<String>,
1181 #[serde(skip_serializing_if = "Option::is_none")]
1182 pub types: Option<Vec<String>>,
1183 #[serde(rename = "manage_types", skip_serializing_if = "Option::is_none")]
1184 pub manager: Option<Vec<String>>,
1185 #[serde(skip_serializing_if = "Option::is_none")]
1186 pub status: Option<Vec<String>>,
1187}
1188
1189#[derive(Deserialize, Debug, Clone)]
1190pub struct TasksListResult {
1191 pub tasks: Vec<Task>,
1192 pub continue_token: Option<String>,
1193}
1194
1195#[derive(Deserialize, Debug, Clone)]
1196pub struct Task {
1197 id: TaskID,
1198 name: String,
1199 #[serde(rename = "type")]
1200 workflow: String,
1201 status: String,
1202 #[serde(rename = "manage_type")]
1203 manager: Option<String>,
1204 #[serde(rename = "instance_type")]
1205 instance: String,
1206 #[serde(rename = "date")]
1207 created: DateTime<Utc>,
1208}
1209
1210impl Task {
1211 pub fn id(&self) -> TaskID {
1212 self.id
1213 }
1214
1215 pub fn name(&self) -> &str {
1216 &self.name
1217 }
1218
1219 pub fn workflow(&self) -> &str {
1220 &self.workflow
1221 }
1222
1223 pub fn status(&self) -> &str {
1224 &self.status
1225 }
1226
1227 pub fn manager(&self) -> Option<&str> {
1228 self.manager.as_deref()
1229 }
1230
1231 pub fn instance(&self) -> &str {
1232 &self.instance
1233 }
1234
1235 pub fn created(&self) -> &DateTime<Utc> {
1236 &self.created
1237 }
1238}
1239
1240impl Display for Task {
1241 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1242 write!(
1243 f,
1244 "{} [{:?} {}] {}",
1245 self.id,
1246 self.manager(),
1247 self.workflow(),
1248 self.name()
1249 )
1250 }
1251}
1252
1253#[derive(Deserialize, Debug)]
1254pub struct TaskInfo {
1255 id: TaskID,
1256 project_id: Option<ProjectID>,
1257 #[serde(rename = "task_description")]
1258 description: String,
1259 #[serde(rename = "type")]
1260 workflow: String,
1261 status: Option<String>,
1262 #[serde(default)]
1263 progress: TaskProgress,
1264 #[serde(rename = "created_date")]
1265 created: DateTime<Utc>,
1266 #[serde(rename = "end_date")]
1267 completed: DateTime<Utc>,
1268}
1269
1270impl Display for TaskInfo {
1271 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1272 write!(f, "{} {}: {}", self.id, self.workflow(), self.description())
1273 }
1274}
1275
1276impl TaskInfo {
1277 pub fn id(&self) -> TaskID {
1278 self.id
1279 }
1280
1281 pub fn project_id(&self) -> Option<ProjectID> {
1282 self.project_id
1283 }
1284
1285 pub fn description(&self) -> &str {
1286 &self.description
1287 }
1288
1289 pub fn workflow(&self) -> &str {
1290 &self.workflow
1291 }
1292
1293 pub fn status(&self) -> &Option<String> {
1294 &self.status
1295 }
1296
1297 pub async fn set_status(&mut self, client: &Client, status: &str) -> Result<(), Error> {
1298 let t = client.task_status(self.id(), status).await?;
1299 self.status = Some(t.status);
1300 Ok(())
1301 }
1302
1303 pub fn stages(&self) -> HashMap<String, Stage> {
1304 match &self.progress.stages {
1305 Some(stages) => stages.clone(),
1306 None => HashMap::new(),
1307 }
1308 }
1309
1310 pub async fn update_stage(
1311 &mut self,
1312 client: &Client,
1313 stage: &str,
1314 status: &str,
1315 message: &str,
1316 percentage: u8,
1317 ) -> Result<(), Error> {
1318 client
1319 .update_stage(self.id(), stage, status, message, percentage)
1320 .await?;
1321 let t = client.task_info(self.id()).await?;
1322 self.progress.stages = Some(t.progress.stages.unwrap_or_default());
1323 Ok(())
1324 }
1325
1326 pub async fn set_stages(
1327 &mut self,
1328 client: &Client,
1329 stages: &[(&str, &str)],
1330 ) -> Result<(), Error> {
1331 client.set_stages(self.id(), stages).await?;
1332 let t = client.task_info(self.id()).await?;
1333 self.progress.stages = Some(t.progress.stages.unwrap_or_default());
1334 Ok(())
1335 }
1336
1337 pub fn created(&self) -> &DateTime<Utc> {
1338 &self.created
1339 }
1340
1341 pub fn completed(&self) -> &DateTime<Utc> {
1342 &self.completed
1343 }
1344}
1345
1346#[derive(Deserialize, Debug, Default)]
1347pub struct TaskProgress {
1348 stages: Option<HashMap<String, Stage>>,
1349}
1350
1351#[derive(Serialize, Debug, Clone)]
1352pub struct TaskStatus {
1353 #[serde(rename = "docker_task_id")]
1354 pub task_id: TaskID,
1355 pub status: String,
1356}
1357
1358#[derive(Serialize, Deserialize, Debug, Clone)]
1359pub struct Stage {
1360 #[serde(rename = "docker_task_id", skip_serializing_if = "Option::is_none")]
1361 task_id: Option<TaskID>,
1362 stage: String,
1363 #[serde(skip_serializing_if = "Option::is_none")]
1364 status: Option<String>,
1365 #[serde(skip_serializing_if = "Option::is_none")]
1366 description: Option<String>,
1367 #[serde(skip_serializing_if = "Option::is_none")]
1368 message: Option<String>,
1369 percentage: u8,
1370}
1371
1372impl Stage {
1373 pub fn new(
1374 task_id: Option<TaskID>,
1375 stage: String,
1376 status: Option<String>,
1377 message: Option<String>,
1378 percentage: u8,
1379 ) -> Self {
1380 Stage {
1381 task_id,
1382 stage,
1383 status,
1384 description: None,
1385 message,
1386 percentage,
1387 }
1388 }
1389
1390 pub fn task_id(&self) -> &Option<TaskID> {
1391 &self.task_id
1392 }
1393
1394 pub fn stage(&self) -> &str {
1395 &self.stage
1396 }
1397
1398 pub fn status(&self) -> &Option<String> {
1399 &self.status
1400 }
1401
1402 pub fn description(&self) -> &Option<String> {
1403 &self.description
1404 }
1405
1406 pub fn message(&self) -> &Option<String> {
1407 &self.message
1408 }
1409
1410 pub fn percentage(&self) -> u8 {
1411 self.percentage
1412 }
1413}
1414
1415#[derive(Serialize, Debug)]
1416pub struct TaskStages {
1417 #[serde(rename = "docker_task_id")]
1418 pub task_id: TaskID,
1419 #[serde(skip_serializing_if = "Vec::is_empty")]
1420 pub stages: Vec<HashMap<String, String>>,
1421}
1422
1423#[derive(Deserialize, Debug)]
1424pub struct Artifact {
1425 name: String,
1426 #[serde(rename = "modelType")]
1427 model_type: String,
1428}
1429
1430impl Artifact {
1431 pub fn name(&self) -> &str {
1432 &self.name
1433 }
1434
1435 pub fn model_type(&self) -> &str {
1436 &self.model_type
1437 }
1438}
1439
1440#[cfg(test)]
1441mod tests {
1442 use super::*;
1443
1444 #[test]
1446 fn test_organization_id_from_u64() {
1447 let id = OrganizationID::from(12345);
1448 assert_eq!(id.value(), 12345);
1449 }
1450
1451 #[test]
1452 fn test_organization_id_display() {
1453 let id = OrganizationID::from(0xabc123);
1454 assert_eq!(format!("{}", id), "org-abc123");
1455 }
1456
1457 #[test]
1458 fn test_organization_id_try_from_str_valid() {
1459 let id = OrganizationID::try_from("org-abc123").unwrap();
1460 assert_eq!(id.value(), 0xabc123);
1461 }
1462
1463 #[test]
1464 fn test_organization_id_try_from_str_invalid_prefix() {
1465 let result = OrganizationID::try_from("invalid-abc123");
1466 assert!(result.is_err());
1467 match result {
1468 Err(Error::InvalidParameters(msg)) => {
1469 assert!(msg.contains("must start with 'org-'"));
1470 }
1471 _ => panic!("Expected InvalidParameters error"),
1472 }
1473 }
1474
1475 #[test]
1476 fn test_organization_id_try_from_str_invalid_hex() {
1477 let result = OrganizationID::try_from("org-xyz");
1478 assert!(result.is_err());
1479 }
1480
1481 #[test]
1482 fn test_organization_id_try_from_str_empty() {
1483 let result = OrganizationID::try_from("org-");
1484 assert!(result.is_err());
1485 }
1486
1487 #[test]
1488 fn test_organization_id_into_u64() {
1489 let id = OrganizationID::from(54321);
1490 let value: u64 = id.into();
1491 assert_eq!(value, 54321);
1492 }
1493
1494 #[test]
1496 fn test_project_id_from_u64() {
1497 let id = ProjectID::from(78910);
1498 assert_eq!(id.value(), 78910);
1499 }
1500
1501 #[test]
1502 fn test_project_id_display() {
1503 let id = ProjectID::from(0xdef456);
1504 assert_eq!(format!("{}", id), "p-def456");
1505 }
1506
1507 #[test]
1508 fn test_project_id_from_str_valid() {
1509 let id = ProjectID::from_str("p-def456").unwrap();
1510 assert_eq!(id.value(), 0xdef456);
1511 }
1512
1513 #[test]
1514 fn test_project_id_try_from_str_valid() {
1515 let id = ProjectID::try_from("p-123abc").unwrap();
1516 assert_eq!(id.value(), 0x123abc);
1517 }
1518
1519 #[test]
1520 fn test_project_id_try_from_string_valid() {
1521 let id = ProjectID::try_from("p-456def".to_string()).unwrap();
1522 assert_eq!(id.value(), 0x456def);
1523 }
1524
1525 #[test]
1526 fn test_project_id_from_str_invalid_prefix() {
1527 let result = ProjectID::from_str("proj-123");
1528 assert!(result.is_err());
1529 match result {
1530 Err(Error::InvalidParameters(msg)) => {
1531 assert!(msg.contains("must start with 'p-'"));
1532 }
1533 _ => panic!("Expected InvalidParameters error"),
1534 }
1535 }
1536
1537 #[test]
1538 fn test_project_id_from_str_invalid_hex() {
1539 let result = ProjectID::from_str("p-notahex");
1540 assert!(result.is_err());
1541 }
1542
1543 #[test]
1544 fn test_project_id_into_u64() {
1545 let id = ProjectID::from(99999);
1546 let value: u64 = id.into();
1547 assert_eq!(value, 99999);
1548 }
1549
1550 #[test]
1552 fn test_experiment_id_from_u64() {
1553 let id = ExperimentID::from(1193046);
1554 assert_eq!(id.value(), 1193046);
1555 }
1556
1557 #[test]
1558 fn test_experiment_id_display() {
1559 let id = ExperimentID::from(0x123abc);
1560 assert_eq!(format!("{}", id), "exp-123abc");
1561 }
1562
1563 #[test]
1564 fn test_experiment_id_from_str_valid() {
1565 let id = ExperimentID::from_str("exp-456def").unwrap();
1566 assert_eq!(id.value(), 0x456def);
1567 }
1568
1569 #[test]
1570 fn test_experiment_id_try_from_str_valid() {
1571 let id = ExperimentID::try_from("exp-789abc").unwrap();
1572 assert_eq!(id.value(), 0x789abc);
1573 }
1574
1575 #[test]
1576 fn test_experiment_id_try_from_string_valid() {
1577 let id = ExperimentID::try_from("exp-fedcba".to_string()).unwrap();
1578 assert_eq!(id.value(), 0xfedcba);
1579 }
1580
1581 #[test]
1582 fn test_experiment_id_from_str_invalid_prefix() {
1583 let result = ExperimentID::from_str("experiment-123");
1584 assert!(result.is_err());
1585 match result {
1586 Err(Error::InvalidParameters(msg)) => {
1587 assert!(msg.contains("must start with 'exp-'"));
1588 }
1589 _ => panic!("Expected InvalidParameters error"),
1590 }
1591 }
1592
1593 #[test]
1594 fn test_experiment_id_from_str_invalid_hex() {
1595 let result = ExperimentID::from_str("exp-zzz");
1596 assert!(result.is_err());
1597 }
1598
1599 #[test]
1600 fn test_experiment_id_into_u64() {
1601 let id = ExperimentID::from(777777);
1602 let value: u64 = id.into();
1603 assert_eq!(value, 777777);
1604 }
1605
1606 #[test]
1608 fn test_training_session_id_from_u64() {
1609 let id = TrainingSessionID::from(7901234);
1610 assert_eq!(id.value(), 7901234);
1611 }
1612
1613 #[test]
1614 fn test_training_session_id_display() {
1615 let id = TrainingSessionID::from(0xabc123);
1616 assert_eq!(format!("{}", id), "t-abc123");
1617 }
1618
1619 #[test]
1620 fn test_training_session_id_from_str_valid() {
1621 let id = TrainingSessionID::from_str("t-abc123").unwrap();
1622 assert_eq!(id.value(), 0xabc123);
1623 }
1624
1625 #[test]
1626 fn test_training_session_id_try_from_str_valid() {
1627 let id = TrainingSessionID::try_from("t-deadbeef").unwrap();
1628 assert_eq!(id.value(), 0xdeadbeef);
1629 }
1630
1631 #[test]
1632 fn test_training_session_id_try_from_string_valid() {
1633 let id = TrainingSessionID::try_from("t-cafebabe".to_string()).unwrap();
1634 assert_eq!(id.value(), 0xcafebabe);
1635 }
1636
1637 #[test]
1638 fn test_training_session_id_from_str_invalid_prefix() {
1639 let result = TrainingSessionID::from_str("training-123");
1640 assert!(result.is_err());
1641 match result {
1642 Err(Error::InvalidParameters(msg)) => {
1643 assert!(msg.contains("must start with 't-'"));
1644 }
1645 _ => panic!("Expected InvalidParameters error"),
1646 }
1647 }
1648
1649 #[test]
1650 fn test_training_session_id_from_str_invalid_hex() {
1651 let result = TrainingSessionID::from_str("t-qqq");
1652 assert!(result.is_err());
1653 }
1654
1655 #[test]
1656 fn test_training_session_id_into_u64() {
1657 let id = TrainingSessionID::from(123456);
1658 let value: u64 = id.into();
1659 assert_eq!(value, 123456);
1660 }
1661
1662 #[test]
1664 fn test_validation_session_id_from_u64() {
1665 let id = ValidationSessionID::from(3456789);
1666 assert_eq!(id.value(), 3456789);
1667 }
1668
1669 #[test]
1670 fn test_validation_session_id_display() {
1671 let id = ValidationSessionID::from(0x34c985);
1672 assert_eq!(format!("{}", id), "v-34c985");
1673 }
1674
1675 #[test]
1676 fn test_validation_session_id_try_from_str_valid() {
1677 let id = ValidationSessionID::try_from("v-deadbeef").unwrap();
1678 assert_eq!(id.value(), 0xdeadbeef);
1679 }
1680
1681 #[test]
1682 fn test_validation_session_id_try_from_string_valid() {
1683 let id = ValidationSessionID::try_from("v-12345678".to_string()).unwrap();
1684 assert_eq!(id.value(), 0x12345678);
1685 }
1686
1687 #[test]
1688 fn test_validation_session_id_try_from_str_invalid_prefix() {
1689 let result = ValidationSessionID::try_from("validation-123");
1690 assert!(result.is_err());
1691 match result {
1692 Err(Error::InvalidParameters(msg)) => {
1693 assert!(msg.contains("must start with 'v-'"));
1694 }
1695 _ => panic!("Expected InvalidParameters error"),
1696 }
1697 }
1698
1699 #[test]
1700 fn test_validation_session_id_try_from_str_invalid_hex() {
1701 let result = ValidationSessionID::try_from("v-xyz");
1702 assert!(result.is_err());
1703 }
1704
1705 #[test]
1706 fn test_validation_session_id_into_u64() {
1707 let id = ValidationSessionID::from(987654);
1708 let value: u64 = id.into();
1709 assert_eq!(value, 987654);
1710 }
1711
1712 #[test]
1714 fn test_snapshot_id_from_u64() {
1715 let id = SnapshotID::from(111222);
1716 assert_eq!(id.value(), 111222);
1717 }
1718
1719 #[test]
1720 fn test_snapshot_id_display() {
1721 let id = SnapshotID::from(0xaabbcc);
1722 assert_eq!(format!("{}", id), "ss-aabbcc");
1723 }
1724
1725 #[test]
1726 fn test_snapshot_id_try_from_str_valid() {
1727 let id = SnapshotID::try_from("ss-aabbcc").unwrap();
1728 assert_eq!(id.value(), 0xaabbcc);
1729 }
1730
1731 #[test]
1732 fn test_snapshot_id_try_from_str_invalid_prefix() {
1733 let result = SnapshotID::try_from("snapshot-123");
1734 assert!(result.is_err());
1735 match result {
1736 Err(Error::InvalidParameters(msg)) => {
1737 assert!(msg.contains("must start with 'ss-'"));
1738 }
1739 _ => panic!("Expected InvalidParameters error"),
1740 }
1741 }
1742
1743 #[test]
1744 fn test_snapshot_id_try_from_str_invalid_hex() {
1745 let result = SnapshotID::try_from("ss-ggg");
1746 assert!(result.is_err());
1747 }
1748
1749 #[test]
1750 fn test_snapshot_id_into_u64() {
1751 let id = SnapshotID::from(333444);
1752 let value: u64 = id.into();
1753 assert_eq!(value, 333444);
1754 }
1755
1756 #[test]
1758 fn test_task_id_from_u64() {
1759 let id = TaskID::from(555666);
1760 assert_eq!(id.value(), 555666);
1761 }
1762
1763 #[test]
1764 fn test_task_id_display() {
1765 let id = TaskID::from(0x123456);
1766 assert_eq!(format!("{}", id), "task-123456");
1767 }
1768
1769 #[test]
1770 fn test_task_id_from_str_valid() {
1771 let id = TaskID::from_str("task-123456").unwrap();
1772 assert_eq!(id.value(), 0x123456);
1773 }
1774
1775 #[test]
1776 fn test_task_id_try_from_str_valid() {
1777 let id = TaskID::try_from("task-abcdef").unwrap();
1778 assert_eq!(id.value(), 0xabcdef);
1779 }
1780
1781 #[test]
1782 fn test_task_id_try_from_string_valid() {
1783 let id = TaskID::try_from("task-fedcba".to_string()).unwrap();
1784 assert_eq!(id.value(), 0xfedcba);
1785 }
1786
1787 #[test]
1788 fn test_task_id_from_str_invalid_prefix() {
1789 let result = TaskID::from_str("t-123");
1790 assert!(result.is_err());
1791 match result {
1792 Err(Error::InvalidParameters(msg)) => {
1793 assert!(msg.contains("must start with 'task-'"));
1794 }
1795 _ => panic!("Expected InvalidParameters error"),
1796 }
1797 }
1798
1799 #[test]
1800 fn test_task_id_from_str_invalid_hex() {
1801 let result = TaskID::from_str("task-zzz");
1802 assert!(result.is_err());
1803 }
1804
1805 #[test]
1806 fn test_task_id_into_u64() {
1807 let id = TaskID::from(777888);
1808 let value: u64 = id.into();
1809 assert_eq!(value, 777888);
1810 }
1811
1812 #[test]
1814 fn test_dataset_id_from_u64() {
1815 let id = DatasetID::from(1193046);
1816 assert_eq!(id.value(), 1193046);
1817 }
1818
1819 #[test]
1820 fn test_dataset_id_display() {
1821 let id = DatasetID::from(0x123abc);
1822 assert_eq!(format!("{}", id), "ds-123abc");
1823 }
1824
1825 #[test]
1826 fn test_dataset_id_from_str_valid() {
1827 let id = DatasetID::from_str("ds-456def").unwrap();
1828 assert_eq!(id.value(), 0x456def);
1829 }
1830
1831 #[test]
1832 fn test_dataset_id_try_from_str_valid() {
1833 let id = DatasetID::try_from("ds-789abc").unwrap();
1834 assert_eq!(id.value(), 0x789abc);
1835 }
1836
1837 #[test]
1838 fn test_dataset_id_try_from_string_valid() {
1839 let id = DatasetID::try_from("ds-fedcba".to_string()).unwrap();
1840 assert_eq!(id.value(), 0xfedcba);
1841 }
1842
1843 #[test]
1844 fn test_dataset_id_from_str_invalid_prefix() {
1845 let result = DatasetID::from_str("dataset-123");
1846 assert!(result.is_err());
1847 match result {
1848 Err(Error::InvalidParameters(msg)) => {
1849 assert!(msg.contains("must start with 'ds-'"));
1850 }
1851 _ => panic!("Expected InvalidParameters error"),
1852 }
1853 }
1854
1855 #[test]
1856 fn test_dataset_id_from_str_invalid_hex() {
1857 let result = DatasetID::from_str("ds-zzz");
1858 assert!(result.is_err());
1859 }
1860
1861 #[test]
1862 fn test_dataset_id_into_u64() {
1863 let id = DatasetID::from(111111);
1864 let value: u64 = id.into();
1865 assert_eq!(value, 111111);
1866 }
1867
1868 #[test]
1870 fn test_annotation_set_id_from_u64() {
1871 let id = AnnotationSetID::from(222333);
1872 assert_eq!(id.value(), 222333);
1873 }
1874
1875 #[test]
1876 fn test_annotation_set_id_display() {
1877 let id = AnnotationSetID::from(0xabcdef);
1878 assert_eq!(format!("{}", id), "as-abcdef");
1879 }
1880
1881 #[test]
1882 fn test_annotation_set_id_from_str_valid() {
1883 let id = AnnotationSetID::from_str("as-abcdef").unwrap();
1884 assert_eq!(id.value(), 0xabcdef);
1885 }
1886
1887 #[test]
1888 fn test_annotation_set_id_try_from_str_valid() {
1889 let id = AnnotationSetID::try_from("as-123456").unwrap();
1890 assert_eq!(id.value(), 0x123456);
1891 }
1892
1893 #[test]
1894 fn test_annotation_set_id_try_from_string_valid() {
1895 let id = AnnotationSetID::try_from("as-fedcba".to_string()).unwrap();
1896 assert_eq!(id.value(), 0xfedcba);
1897 }
1898
1899 #[test]
1900 fn test_annotation_set_id_from_str_invalid_prefix() {
1901 let result = AnnotationSetID::from_str("annotation-123");
1902 assert!(result.is_err());
1903 match result {
1904 Err(Error::InvalidParameters(msg)) => {
1905 assert!(msg.contains("must start with 'as-'"));
1906 }
1907 _ => panic!("Expected InvalidParameters error"),
1908 }
1909 }
1910
1911 #[test]
1912 fn test_annotation_set_id_from_str_invalid_hex() {
1913 let result = AnnotationSetID::from_str("as-zzz");
1914 assert!(result.is_err());
1915 }
1916
1917 #[test]
1918 fn test_annotation_set_id_into_u64() {
1919 let id = AnnotationSetID::from(444555);
1920 let value: u64 = id.into();
1921 assert_eq!(value, 444555);
1922 }
1923
1924 #[test]
1926 fn test_sample_id_from_u64() {
1927 let id = SampleID::from(666777);
1928 assert_eq!(id.value(), 666777);
1929 }
1930
1931 #[test]
1932 fn test_sample_id_display() {
1933 let id = SampleID::from(0x987654);
1934 assert_eq!(format!("{}", id), "s-987654");
1935 }
1936
1937 #[test]
1938 fn test_sample_id_try_from_str_valid() {
1939 let id = SampleID::try_from("s-987654").unwrap();
1940 assert_eq!(id.value(), 0x987654);
1941 }
1942
1943 #[test]
1944 fn test_sample_id_try_from_str_invalid_prefix() {
1945 let result = SampleID::try_from("sample-123");
1946 assert!(result.is_err());
1947 match result {
1948 Err(Error::InvalidParameters(msg)) => {
1949 assert!(msg.contains("must start with 's-'"));
1950 }
1951 _ => panic!("Expected InvalidParameters error"),
1952 }
1953 }
1954
1955 #[test]
1956 fn test_sample_id_try_from_str_invalid_hex() {
1957 let result = SampleID::try_from("s-zzz");
1958 assert!(result.is_err());
1959 }
1960
1961 #[test]
1962 fn test_sample_id_into_u64() {
1963 let id = SampleID::from(888999);
1964 let value: u64 = id.into();
1965 assert_eq!(value, 888999);
1966 }
1967
1968 #[test]
1970 fn test_app_id_from_u64() {
1971 let id = AppId::from(123123);
1972 assert_eq!(id.value(), 123123);
1973 }
1974
1975 #[test]
1976 fn test_app_id_display() {
1977 let id = AppId::from(0x456789);
1978 assert_eq!(format!("{}", id), "app-456789");
1979 }
1980
1981 #[test]
1982 fn test_app_id_try_from_str_valid() {
1983 let id = AppId::try_from("app-456789").unwrap();
1984 assert_eq!(id.value(), 0x456789);
1985 }
1986
1987 #[test]
1988 fn test_app_id_try_from_str_invalid_prefix() {
1989 let result = AppId::try_from("application-123");
1990 assert!(result.is_err());
1991 match result {
1992 Err(Error::InvalidParameters(msg)) => {
1993 assert!(msg.contains("must start with 'app-'"));
1994 }
1995 _ => panic!("Expected InvalidParameters error"),
1996 }
1997 }
1998
1999 #[test]
2000 fn test_app_id_try_from_str_invalid_hex() {
2001 let result = AppId::try_from("app-zzz");
2002 assert!(result.is_err());
2003 }
2004
2005 #[test]
2006 fn test_app_id_into_u64() {
2007 let id = AppId::from(321321);
2008 let value: u64 = id.into();
2009 assert_eq!(value, 321321);
2010 }
2011
2012 #[test]
2014 fn test_image_id_from_u64() {
2015 let id = ImageId::from(789789);
2016 assert_eq!(id.value(), 789789);
2017 }
2018
2019 #[test]
2020 fn test_image_id_display() {
2021 let id = ImageId::from(0xabcd1234);
2022 assert_eq!(format!("{}", id), "im-abcd1234");
2023 }
2024
2025 #[test]
2026 fn test_image_id_try_from_str_valid() {
2027 let id = ImageId::try_from("im-abcd1234").unwrap();
2028 assert_eq!(id.value(), 0xabcd1234);
2029 }
2030
2031 #[test]
2032 fn test_image_id_try_from_str_invalid_prefix() {
2033 let result = ImageId::try_from("image-123");
2034 assert!(result.is_err());
2035 match result {
2036 Err(Error::InvalidParameters(msg)) => {
2037 assert!(msg.contains("must start with 'im-'"));
2038 }
2039 _ => panic!("Expected InvalidParameters error"),
2040 }
2041 }
2042
2043 #[test]
2044 fn test_image_id_try_from_str_invalid_hex() {
2045 let result = ImageId::try_from("im-zzz");
2046 assert!(result.is_err());
2047 }
2048
2049 #[test]
2050 fn test_image_id_into_u64() {
2051 let id = ImageId::from(987987);
2052 let value: u64 = id.into();
2053 assert_eq!(value, 987987);
2054 }
2055
2056 #[test]
2058 fn test_id_types_equality() {
2059 let id1 = ProjectID::from(12345);
2060 let id2 = ProjectID::from(12345);
2061 let id3 = ProjectID::from(54321);
2062
2063 assert_eq!(id1, id2);
2064 assert_ne!(id1, id3);
2065 }
2066
2067 #[test]
2068 fn test_id_types_hash() {
2069 use std::collections::HashSet;
2070
2071 let mut set = HashSet::new();
2072 set.insert(DatasetID::from(100));
2073 set.insert(DatasetID::from(200));
2074 set.insert(DatasetID::from(100)); assert_eq!(set.len(), 2);
2077 assert!(set.contains(&DatasetID::from(100)));
2078 assert!(set.contains(&DatasetID::from(200)));
2079 }
2080
2081 #[test]
2082 fn test_id_types_copy_clone() {
2083 let id1 = ExperimentID::from(999);
2084 let id2 = id1; let id3 = id1; assert_eq!(id1, id2);
2088 assert_eq!(id1, id3);
2089 }
2090
2091 #[test]
2093 fn test_id_zero_value() {
2094 let id = ProjectID::from(0);
2095 assert_eq!(format!("{}", id), "p-0");
2096 assert_eq!(id.value(), 0);
2097 }
2098
2099 #[test]
2100 fn test_id_max_value() {
2101 let id = ProjectID::from(u64::MAX);
2102 assert_eq!(format!("{}", id), "p-ffffffffffffffff");
2103 assert_eq!(id.value(), u64::MAX);
2104 }
2105
2106 #[test]
2107 fn test_id_round_trip_conversion() {
2108 let original = 0xdeadbeef_u64;
2109 let id = TrainingSessionID::from(original);
2110 let back: u64 = id.into();
2111 assert_eq!(original, back);
2112 }
2113
2114 #[test]
2115 fn test_id_case_insensitive_hex() {
2116 let id1 = DatasetID::from_str("ds-ABCDEF").unwrap();
2118 let id2 = DatasetID::from_str("ds-abcdef").unwrap();
2119 assert_eq!(id1.value(), id2.value());
2120 }
2121
2122 #[test]
2123 fn test_id_with_leading_zeros() {
2124 let id = ProjectID::from_str("p-00001234").unwrap();
2125 assert_eq!(id.value(), 0x1234);
2126 }
2127
2128 #[test]
2130 fn test_parameter_integer() {
2131 let param = Parameter::Integer(42);
2132 match param {
2133 Parameter::Integer(val) => assert_eq!(val, 42),
2134 _ => panic!("Expected Integer variant"),
2135 }
2136 }
2137
2138 #[test]
2139 fn test_parameter_real() {
2140 let param = Parameter::Real(2.5);
2141 match param {
2142 Parameter::Real(val) => assert_eq!(val, 2.5),
2143 _ => panic!("Expected Real variant"),
2144 }
2145 }
2146
2147 #[test]
2148 fn test_parameter_boolean() {
2149 let param = Parameter::Boolean(true);
2150 match param {
2151 Parameter::Boolean(val) => assert!(val),
2152 _ => panic!("Expected Boolean variant"),
2153 }
2154 }
2155
2156 #[test]
2157 fn test_parameter_string() {
2158 let param = Parameter::String("test".to_string());
2159 match param {
2160 Parameter::String(val) => assert_eq!(val, "test"),
2161 _ => panic!("Expected String variant"),
2162 }
2163 }
2164
2165 #[test]
2166 fn test_parameter_array() {
2167 let param = Parameter::Array(vec![
2168 Parameter::Integer(1),
2169 Parameter::Integer(2),
2170 Parameter::Integer(3),
2171 ]);
2172 match param {
2173 Parameter::Array(arr) => assert_eq!(arr.len(), 3),
2174 _ => panic!("Expected Array variant"),
2175 }
2176 }
2177
2178 #[test]
2179 fn test_parameter_object() {
2180 let mut map = HashMap::new();
2181 map.insert("key".to_string(), Parameter::Integer(100));
2182 let param = Parameter::Object(map);
2183 match param {
2184 Parameter::Object(obj) => {
2185 assert_eq!(obj.len(), 1);
2186 assert!(obj.contains_key("key"));
2187 }
2188 _ => panic!("Expected Object variant"),
2189 }
2190 }
2191
2192 #[test]
2193 fn test_parameter_clone() {
2194 let param1 = Parameter::Integer(42);
2195 let param2 = param1.clone();
2196 assert_eq!(param1, param2);
2197 }
2198
2199 #[test]
2200 fn test_parameter_nested() {
2201 let inner_array = Parameter::Array(vec![Parameter::Integer(1), Parameter::Integer(2)]);
2202 let outer_array = Parameter::Array(vec![inner_array.clone(), inner_array]);
2203
2204 match outer_array {
2205 Parameter::Array(arr) => {
2206 assert_eq!(arr.len(), 2);
2207 }
2208 _ => panic!("Expected Array variant"),
2209 }
2210 }
2211
2212 macro_rules! test_typeid_conversions {
2215 ($test_name:ident, $type:ty, $prefix:literal, $wrong_prefix:literal) => {
2216 #[test]
2217 fn $test_name() {
2218 let id = <$type>::from(0xabc123);
2220 assert_eq!(id.value(), 0xabc123);
2221
2222 assert_eq!(format!("{}", id), concat!($prefix, "-abc123"));
2224
2225 let id: $type = concat!($prefix, "-abc123").parse().unwrap();
2227 assert_eq!(id.value(), 0xabc123);
2228
2229 assert!(concat!($wrong_prefix, "-abc").parse::<$type>().is_err());
2231
2232 assert!("abc123".parse::<$type>().is_err());
2234
2235 assert!(concat!($prefix, "-xyz").parse::<$type>().is_err());
2237
2238 let id = <$type>::try_from(concat!($prefix, "-abc123")).unwrap();
2240 assert_eq!(id.value(), 0xabc123);
2241
2242 let id = <$type>::try_from(concat!($prefix, "-abc123").to_string()).unwrap();
2244 assert_eq!(id.value(), 0xabc123);
2245
2246 let id = <$type>::from(0xabc123);
2248 let json = serde_json::to_string(&id).unwrap();
2249 let parsed: $type = serde_json::from_str(&json).unwrap();
2250 assert_eq!(id, parsed);
2251
2252 let id = <$type>::from(0xabc123);
2254 let val: u64 = id.into();
2255 assert_eq!(val, 0xabc123);
2256 }
2257 };
2258 }
2259
2260 test_typeid_conversions!(test_organization_id_conversions, OrganizationID, "org", "p");
2261 test_typeid_conversions!(test_project_id_conversions, ProjectID, "p", "org");
2262 test_typeid_conversions!(test_experiment_id_conversions, ExperimentID, "exp", "p");
2263 test_typeid_conversions!(
2264 test_training_session_id_conversions,
2265 TrainingSessionID,
2266 "t",
2267 "v"
2268 );
2269 test_typeid_conversions!(
2270 test_validation_session_id_conversions,
2271 ValidationSessionID,
2272 "v",
2273 "t"
2274 );
2275 test_typeid_conversions!(test_snapshot_id_conversions, SnapshotID, "ss", "ds");
2276 test_typeid_conversions!(test_task_id_conversions, TaskID, "task", "t");
2277 test_typeid_conversions!(test_dataset_id_conversions, DatasetID, "ds", "ss");
2278 test_typeid_conversions!(
2279 test_annotation_set_id_conversions,
2280 AnnotationSetID,
2281 "as",
2282 "ds"
2283 );
2284 test_typeid_conversions!(test_sample_id_conversions, SampleID, "s", "p");
2285 test_typeid_conversions!(test_app_id_conversions, AppId, "app", "p");
2286 test_typeid_conversions!(test_image_id_conversions, ImageId, "im", "se");
2287 test_typeid_conversions!(test_sequence_id_conversions, SequenceId, "se", "im");
2288}