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 progress: TaskProgress,
1263 #[serde(rename = "created_date")]
1264 created: DateTime<Utc>,
1265 #[serde(rename = "end_date")]
1266 completed: DateTime<Utc>,
1267}
1268
1269impl Display for TaskInfo {
1270 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1271 write!(f, "{} {}: {}", self.id, self.workflow(), self.description())
1272 }
1273}
1274
1275impl TaskInfo {
1276 pub fn id(&self) -> TaskID {
1277 self.id
1278 }
1279
1280 pub fn project_id(&self) -> Option<ProjectID> {
1281 self.project_id
1282 }
1283
1284 pub fn description(&self) -> &str {
1285 &self.description
1286 }
1287
1288 pub fn workflow(&self) -> &str {
1289 &self.workflow
1290 }
1291
1292 pub fn status(&self) -> &Option<String> {
1293 &self.status
1294 }
1295
1296 pub async fn set_status(&mut self, client: &Client, status: &str) -> Result<(), Error> {
1297 let t = client.task_status(self.id(), status).await?;
1298 self.status = Some(t.status);
1299 Ok(())
1300 }
1301
1302 pub fn stages(&self) -> HashMap<String, Stage> {
1303 match &self.progress.stages {
1304 Some(stages) => stages.clone(),
1305 None => HashMap::new(),
1306 }
1307 }
1308
1309 pub async fn update_stage(
1310 &mut self,
1311 client: &Client,
1312 stage: &str,
1313 status: &str,
1314 message: &str,
1315 percentage: u8,
1316 ) -> Result<(), Error> {
1317 client
1318 .update_stage(self.id(), stage, status, message, percentage)
1319 .await?;
1320 let t = client.task_info(self.id()).await?;
1321 self.progress.stages = Some(t.progress.stages.unwrap_or_default());
1322 Ok(())
1323 }
1324
1325 pub async fn set_stages(
1326 &mut self,
1327 client: &Client,
1328 stages: &[(&str, &str)],
1329 ) -> Result<(), Error> {
1330 client.set_stages(self.id(), stages).await?;
1331 let t = client.task_info(self.id()).await?;
1332 self.progress.stages = Some(t.progress.stages.unwrap_or_default());
1333 Ok(())
1334 }
1335
1336 pub fn created(&self) -> &DateTime<Utc> {
1337 &self.created
1338 }
1339
1340 pub fn completed(&self) -> &DateTime<Utc> {
1341 &self.completed
1342 }
1343}
1344
1345#[derive(Deserialize, Debug)]
1346pub struct TaskProgress {
1347 stages: Option<HashMap<String, Stage>>,
1348}
1349
1350#[derive(Serialize, Debug, Clone)]
1351pub struct TaskStatus {
1352 #[serde(rename = "docker_task_id")]
1353 pub task_id: TaskID,
1354 pub status: String,
1355}
1356
1357#[derive(Serialize, Deserialize, Debug, Clone)]
1358pub struct Stage {
1359 #[serde(rename = "docker_task_id", skip_serializing_if = "Option::is_none")]
1360 task_id: Option<TaskID>,
1361 stage: String,
1362 #[serde(skip_serializing_if = "Option::is_none")]
1363 status: Option<String>,
1364 #[serde(skip_serializing_if = "Option::is_none")]
1365 description: Option<String>,
1366 #[serde(skip_serializing_if = "Option::is_none")]
1367 message: Option<String>,
1368 percentage: u8,
1369}
1370
1371impl Stage {
1372 pub fn new(
1373 task_id: Option<TaskID>,
1374 stage: String,
1375 status: Option<String>,
1376 message: Option<String>,
1377 percentage: u8,
1378 ) -> Self {
1379 Stage {
1380 task_id,
1381 stage,
1382 status,
1383 description: None,
1384 message,
1385 percentage,
1386 }
1387 }
1388
1389 pub fn task_id(&self) -> &Option<TaskID> {
1390 &self.task_id
1391 }
1392
1393 pub fn stage(&self) -> &str {
1394 &self.stage
1395 }
1396
1397 pub fn status(&self) -> &Option<String> {
1398 &self.status
1399 }
1400
1401 pub fn description(&self) -> &Option<String> {
1402 &self.description
1403 }
1404
1405 pub fn message(&self) -> &Option<String> {
1406 &self.message
1407 }
1408
1409 pub fn percentage(&self) -> u8 {
1410 self.percentage
1411 }
1412}
1413
1414#[derive(Serialize, Debug)]
1415pub struct TaskStages {
1416 #[serde(rename = "docker_task_id")]
1417 pub task_id: TaskID,
1418 #[serde(skip_serializing_if = "Vec::is_empty")]
1419 pub stages: Vec<HashMap<String, String>>,
1420}
1421
1422#[derive(Deserialize, Debug)]
1423pub struct Artifact {
1424 name: String,
1425 #[serde(rename = "modelType")]
1426 model_type: String,
1427}
1428
1429impl Artifact {
1430 pub fn name(&self) -> &str {
1431 &self.name
1432 }
1433
1434 pub fn model_type(&self) -> &str {
1435 &self.model_type
1436 }
1437}
1438
1439#[cfg(test)]
1440mod tests {
1441 use super::*;
1442
1443 #[test]
1445 fn test_organization_id_from_u64() {
1446 let id = OrganizationID::from(12345);
1447 assert_eq!(id.value(), 12345);
1448 }
1449
1450 #[test]
1451 fn test_organization_id_display() {
1452 let id = OrganizationID::from(0xabc123);
1453 assert_eq!(format!("{}", id), "org-abc123");
1454 }
1455
1456 #[test]
1457 fn test_organization_id_try_from_str_valid() {
1458 let id = OrganizationID::try_from("org-abc123").unwrap();
1459 assert_eq!(id.value(), 0xabc123);
1460 }
1461
1462 #[test]
1463 fn test_organization_id_try_from_str_invalid_prefix() {
1464 let result = OrganizationID::try_from("invalid-abc123");
1465 assert!(result.is_err());
1466 match result {
1467 Err(Error::InvalidParameters(msg)) => {
1468 assert!(msg.contains("must start with 'org-'"));
1469 }
1470 _ => panic!("Expected InvalidParameters error"),
1471 }
1472 }
1473
1474 #[test]
1475 fn test_organization_id_try_from_str_invalid_hex() {
1476 let result = OrganizationID::try_from("org-xyz");
1477 assert!(result.is_err());
1478 }
1479
1480 #[test]
1481 fn test_organization_id_try_from_str_empty() {
1482 let result = OrganizationID::try_from("org-");
1483 assert!(result.is_err());
1484 }
1485
1486 #[test]
1487 fn test_organization_id_into_u64() {
1488 let id = OrganizationID::from(54321);
1489 let value: u64 = id.into();
1490 assert_eq!(value, 54321);
1491 }
1492
1493 #[test]
1495 fn test_project_id_from_u64() {
1496 let id = ProjectID::from(78910);
1497 assert_eq!(id.value(), 78910);
1498 }
1499
1500 #[test]
1501 fn test_project_id_display() {
1502 let id = ProjectID::from(0xdef456);
1503 assert_eq!(format!("{}", id), "p-def456");
1504 }
1505
1506 #[test]
1507 fn test_project_id_from_str_valid() {
1508 let id = ProjectID::from_str("p-def456").unwrap();
1509 assert_eq!(id.value(), 0xdef456);
1510 }
1511
1512 #[test]
1513 fn test_project_id_try_from_str_valid() {
1514 let id = ProjectID::try_from("p-123abc").unwrap();
1515 assert_eq!(id.value(), 0x123abc);
1516 }
1517
1518 #[test]
1519 fn test_project_id_try_from_string_valid() {
1520 let id = ProjectID::try_from("p-456def".to_string()).unwrap();
1521 assert_eq!(id.value(), 0x456def);
1522 }
1523
1524 #[test]
1525 fn test_project_id_from_str_invalid_prefix() {
1526 let result = ProjectID::from_str("proj-123");
1527 assert!(result.is_err());
1528 match result {
1529 Err(Error::InvalidParameters(msg)) => {
1530 assert!(msg.contains("must start with 'p-'"));
1531 }
1532 _ => panic!("Expected InvalidParameters error"),
1533 }
1534 }
1535
1536 #[test]
1537 fn test_project_id_from_str_invalid_hex() {
1538 let result = ProjectID::from_str("p-notahex");
1539 assert!(result.is_err());
1540 }
1541
1542 #[test]
1543 fn test_project_id_into_u64() {
1544 let id = ProjectID::from(99999);
1545 let value: u64 = id.into();
1546 assert_eq!(value, 99999);
1547 }
1548
1549 #[test]
1551 fn test_experiment_id_from_u64() {
1552 let id = ExperimentID::from(1193046);
1553 assert_eq!(id.value(), 1193046);
1554 }
1555
1556 #[test]
1557 fn test_experiment_id_display() {
1558 let id = ExperimentID::from(0x123abc);
1559 assert_eq!(format!("{}", id), "exp-123abc");
1560 }
1561
1562 #[test]
1563 fn test_experiment_id_from_str_valid() {
1564 let id = ExperimentID::from_str("exp-456def").unwrap();
1565 assert_eq!(id.value(), 0x456def);
1566 }
1567
1568 #[test]
1569 fn test_experiment_id_try_from_str_valid() {
1570 let id = ExperimentID::try_from("exp-789abc").unwrap();
1571 assert_eq!(id.value(), 0x789abc);
1572 }
1573
1574 #[test]
1575 fn test_experiment_id_try_from_string_valid() {
1576 let id = ExperimentID::try_from("exp-fedcba".to_string()).unwrap();
1577 assert_eq!(id.value(), 0xfedcba);
1578 }
1579
1580 #[test]
1581 fn test_experiment_id_from_str_invalid_prefix() {
1582 let result = ExperimentID::from_str("experiment-123");
1583 assert!(result.is_err());
1584 match result {
1585 Err(Error::InvalidParameters(msg)) => {
1586 assert!(msg.contains("must start with 'exp-'"));
1587 }
1588 _ => panic!("Expected InvalidParameters error"),
1589 }
1590 }
1591
1592 #[test]
1593 fn test_experiment_id_from_str_invalid_hex() {
1594 let result = ExperimentID::from_str("exp-zzz");
1595 assert!(result.is_err());
1596 }
1597
1598 #[test]
1599 fn test_experiment_id_into_u64() {
1600 let id = ExperimentID::from(777777);
1601 let value: u64 = id.into();
1602 assert_eq!(value, 777777);
1603 }
1604
1605 #[test]
1607 fn test_training_session_id_from_u64() {
1608 let id = TrainingSessionID::from(7901234);
1609 assert_eq!(id.value(), 7901234);
1610 }
1611
1612 #[test]
1613 fn test_training_session_id_display() {
1614 let id = TrainingSessionID::from(0xabc123);
1615 assert_eq!(format!("{}", id), "t-abc123");
1616 }
1617
1618 #[test]
1619 fn test_training_session_id_from_str_valid() {
1620 let id = TrainingSessionID::from_str("t-abc123").unwrap();
1621 assert_eq!(id.value(), 0xabc123);
1622 }
1623
1624 #[test]
1625 fn test_training_session_id_try_from_str_valid() {
1626 let id = TrainingSessionID::try_from("t-deadbeef").unwrap();
1627 assert_eq!(id.value(), 0xdeadbeef);
1628 }
1629
1630 #[test]
1631 fn test_training_session_id_try_from_string_valid() {
1632 let id = TrainingSessionID::try_from("t-cafebabe".to_string()).unwrap();
1633 assert_eq!(id.value(), 0xcafebabe);
1634 }
1635
1636 #[test]
1637 fn test_training_session_id_from_str_invalid_prefix() {
1638 let result = TrainingSessionID::from_str("training-123");
1639 assert!(result.is_err());
1640 match result {
1641 Err(Error::InvalidParameters(msg)) => {
1642 assert!(msg.contains("must start with 't-'"));
1643 }
1644 _ => panic!("Expected InvalidParameters error"),
1645 }
1646 }
1647
1648 #[test]
1649 fn test_training_session_id_from_str_invalid_hex() {
1650 let result = TrainingSessionID::from_str("t-qqq");
1651 assert!(result.is_err());
1652 }
1653
1654 #[test]
1655 fn test_training_session_id_into_u64() {
1656 let id = TrainingSessionID::from(123456);
1657 let value: u64 = id.into();
1658 assert_eq!(value, 123456);
1659 }
1660
1661 #[test]
1663 fn test_validation_session_id_from_u64() {
1664 let id = ValidationSessionID::from(3456789);
1665 assert_eq!(id.value(), 3456789);
1666 }
1667
1668 #[test]
1669 fn test_validation_session_id_display() {
1670 let id = ValidationSessionID::from(0x34c985);
1671 assert_eq!(format!("{}", id), "v-34c985");
1672 }
1673
1674 #[test]
1675 fn test_validation_session_id_try_from_str_valid() {
1676 let id = ValidationSessionID::try_from("v-deadbeef").unwrap();
1677 assert_eq!(id.value(), 0xdeadbeef);
1678 }
1679
1680 #[test]
1681 fn test_validation_session_id_try_from_string_valid() {
1682 let id = ValidationSessionID::try_from("v-12345678".to_string()).unwrap();
1683 assert_eq!(id.value(), 0x12345678);
1684 }
1685
1686 #[test]
1687 fn test_validation_session_id_try_from_str_invalid_prefix() {
1688 let result = ValidationSessionID::try_from("validation-123");
1689 assert!(result.is_err());
1690 match result {
1691 Err(Error::InvalidParameters(msg)) => {
1692 assert!(msg.contains("must start with 'v-'"));
1693 }
1694 _ => panic!("Expected InvalidParameters error"),
1695 }
1696 }
1697
1698 #[test]
1699 fn test_validation_session_id_try_from_str_invalid_hex() {
1700 let result = ValidationSessionID::try_from("v-xyz");
1701 assert!(result.is_err());
1702 }
1703
1704 #[test]
1705 fn test_validation_session_id_into_u64() {
1706 let id = ValidationSessionID::from(987654);
1707 let value: u64 = id.into();
1708 assert_eq!(value, 987654);
1709 }
1710
1711 #[test]
1713 fn test_snapshot_id_from_u64() {
1714 let id = SnapshotID::from(111222);
1715 assert_eq!(id.value(), 111222);
1716 }
1717
1718 #[test]
1719 fn test_snapshot_id_display() {
1720 let id = SnapshotID::from(0xaabbcc);
1721 assert_eq!(format!("{}", id), "ss-aabbcc");
1722 }
1723
1724 #[test]
1725 fn test_snapshot_id_try_from_str_valid() {
1726 let id = SnapshotID::try_from("ss-aabbcc").unwrap();
1727 assert_eq!(id.value(), 0xaabbcc);
1728 }
1729
1730 #[test]
1731 fn test_snapshot_id_try_from_str_invalid_prefix() {
1732 let result = SnapshotID::try_from("snapshot-123");
1733 assert!(result.is_err());
1734 match result {
1735 Err(Error::InvalidParameters(msg)) => {
1736 assert!(msg.contains("must start with 'ss-'"));
1737 }
1738 _ => panic!("Expected InvalidParameters error"),
1739 }
1740 }
1741
1742 #[test]
1743 fn test_snapshot_id_try_from_str_invalid_hex() {
1744 let result = SnapshotID::try_from("ss-ggg");
1745 assert!(result.is_err());
1746 }
1747
1748 #[test]
1749 fn test_snapshot_id_into_u64() {
1750 let id = SnapshotID::from(333444);
1751 let value: u64 = id.into();
1752 assert_eq!(value, 333444);
1753 }
1754
1755 #[test]
1757 fn test_task_id_from_u64() {
1758 let id = TaskID::from(555666);
1759 assert_eq!(id.value(), 555666);
1760 }
1761
1762 #[test]
1763 fn test_task_id_display() {
1764 let id = TaskID::from(0x123456);
1765 assert_eq!(format!("{}", id), "task-123456");
1766 }
1767
1768 #[test]
1769 fn test_task_id_from_str_valid() {
1770 let id = TaskID::from_str("task-123456").unwrap();
1771 assert_eq!(id.value(), 0x123456);
1772 }
1773
1774 #[test]
1775 fn test_task_id_try_from_str_valid() {
1776 let id = TaskID::try_from("task-abcdef").unwrap();
1777 assert_eq!(id.value(), 0xabcdef);
1778 }
1779
1780 #[test]
1781 fn test_task_id_try_from_string_valid() {
1782 let id = TaskID::try_from("task-fedcba".to_string()).unwrap();
1783 assert_eq!(id.value(), 0xfedcba);
1784 }
1785
1786 #[test]
1787 fn test_task_id_from_str_invalid_prefix() {
1788 let result = TaskID::from_str("t-123");
1789 assert!(result.is_err());
1790 match result {
1791 Err(Error::InvalidParameters(msg)) => {
1792 assert!(msg.contains("must start with 'task-'"));
1793 }
1794 _ => panic!("Expected InvalidParameters error"),
1795 }
1796 }
1797
1798 #[test]
1799 fn test_task_id_from_str_invalid_hex() {
1800 let result = TaskID::from_str("task-zzz");
1801 assert!(result.is_err());
1802 }
1803
1804 #[test]
1805 fn test_task_id_into_u64() {
1806 let id = TaskID::from(777888);
1807 let value: u64 = id.into();
1808 assert_eq!(value, 777888);
1809 }
1810
1811 #[test]
1813 fn test_dataset_id_from_u64() {
1814 let id = DatasetID::from(1193046);
1815 assert_eq!(id.value(), 1193046);
1816 }
1817
1818 #[test]
1819 fn test_dataset_id_display() {
1820 let id = DatasetID::from(0x123abc);
1821 assert_eq!(format!("{}", id), "ds-123abc");
1822 }
1823
1824 #[test]
1825 fn test_dataset_id_from_str_valid() {
1826 let id = DatasetID::from_str("ds-456def").unwrap();
1827 assert_eq!(id.value(), 0x456def);
1828 }
1829
1830 #[test]
1831 fn test_dataset_id_try_from_str_valid() {
1832 let id = DatasetID::try_from("ds-789abc").unwrap();
1833 assert_eq!(id.value(), 0x789abc);
1834 }
1835
1836 #[test]
1837 fn test_dataset_id_try_from_string_valid() {
1838 let id = DatasetID::try_from("ds-fedcba".to_string()).unwrap();
1839 assert_eq!(id.value(), 0xfedcba);
1840 }
1841
1842 #[test]
1843 fn test_dataset_id_from_str_invalid_prefix() {
1844 let result = DatasetID::from_str("dataset-123");
1845 assert!(result.is_err());
1846 match result {
1847 Err(Error::InvalidParameters(msg)) => {
1848 assert!(msg.contains("must start with 'ds-'"));
1849 }
1850 _ => panic!("Expected InvalidParameters error"),
1851 }
1852 }
1853
1854 #[test]
1855 fn test_dataset_id_from_str_invalid_hex() {
1856 let result = DatasetID::from_str("ds-zzz");
1857 assert!(result.is_err());
1858 }
1859
1860 #[test]
1861 fn test_dataset_id_into_u64() {
1862 let id = DatasetID::from(111111);
1863 let value: u64 = id.into();
1864 assert_eq!(value, 111111);
1865 }
1866
1867 #[test]
1869 fn test_annotation_set_id_from_u64() {
1870 let id = AnnotationSetID::from(222333);
1871 assert_eq!(id.value(), 222333);
1872 }
1873
1874 #[test]
1875 fn test_annotation_set_id_display() {
1876 let id = AnnotationSetID::from(0xabcdef);
1877 assert_eq!(format!("{}", id), "as-abcdef");
1878 }
1879
1880 #[test]
1881 fn test_annotation_set_id_from_str_valid() {
1882 let id = AnnotationSetID::from_str("as-abcdef").unwrap();
1883 assert_eq!(id.value(), 0xabcdef);
1884 }
1885
1886 #[test]
1887 fn test_annotation_set_id_try_from_str_valid() {
1888 let id = AnnotationSetID::try_from("as-123456").unwrap();
1889 assert_eq!(id.value(), 0x123456);
1890 }
1891
1892 #[test]
1893 fn test_annotation_set_id_try_from_string_valid() {
1894 let id = AnnotationSetID::try_from("as-fedcba".to_string()).unwrap();
1895 assert_eq!(id.value(), 0xfedcba);
1896 }
1897
1898 #[test]
1899 fn test_annotation_set_id_from_str_invalid_prefix() {
1900 let result = AnnotationSetID::from_str("annotation-123");
1901 assert!(result.is_err());
1902 match result {
1903 Err(Error::InvalidParameters(msg)) => {
1904 assert!(msg.contains("must start with 'as-'"));
1905 }
1906 _ => panic!("Expected InvalidParameters error"),
1907 }
1908 }
1909
1910 #[test]
1911 fn test_annotation_set_id_from_str_invalid_hex() {
1912 let result = AnnotationSetID::from_str("as-zzz");
1913 assert!(result.is_err());
1914 }
1915
1916 #[test]
1917 fn test_annotation_set_id_into_u64() {
1918 let id = AnnotationSetID::from(444555);
1919 let value: u64 = id.into();
1920 assert_eq!(value, 444555);
1921 }
1922
1923 #[test]
1925 fn test_sample_id_from_u64() {
1926 let id = SampleID::from(666777);
1927 assert_eq!(id.value(), 666777);
1928 }
1929
1930 #[test]
1931 fn test_sample_id_display() {
1932 let id = SampleID::from(0x987654);
1933 assert_eq!(format!("{}", id), "s-987654");
1934 }
1935
1936 #[test]
1937 fn test_sample_id_try_from_str_valid() {
1938 let id = SampleID::try_from("s-987654").unwrap();
1939 assert_eq!(id.value(), 0x987654);
1940 }
1941
1942 #[test]
1943 fn test_sample_id_try_from_str_invalid_prefix() {
1944 let result = SampleID::try_from("sample-123");
1945 assert!(result.is_err());
1946 match result {
1947 Err(Error::InvalidParameters(msg)) => {
1948 assert!(msg.contains("must start with 's-'"));
1949 }
1950 _ => panic!("Expected InvalidParameters error"),
1951 }
1952 }
1953
1954 #[test]
1955 fn test_sample_id_try_from_str_invalid_hex() {
1956 let result = SampleID::try_from("s-zzz");
1957 assert!(result.is_err());
1958 }
1959
1960 #[test]
1961 fn test_sample_id_into_u64() {
1962 let id = SampleID::from(888999);
1963 let value: u64 = id.into();
1964 assert_eq!(value, 888999);
1965 }
1966
1967 #[test]
1969 fn test_app_id_from_u64() {
1970 let id = AppId::from(123123);
1971 assert_eq!(id.value(), 123123);
1972 }
1973
1974 #[test]
1975 fn test_app_id_display() {
1976 let id = AppId::from(0x456789);
1977 assert_eq!(format!("{}", id), "app-456789");
1978 }
1979
1980 #[test]
1981 fn test_app_id_try_from_str_valid() {
1982 let id = AppId::try_from("app-456789").unwrap();
1983 assert_eq!(id.value(), 0x456789);
1984 }
1985
1986 #[test]
1987 fn test_app_id_try_from_str_invalid_prefix() {
1988 let result = AppId::try_from("application-123");
1989 assert!(result.is_err());
1990 match result {
1991 Err(Error::InvalidParameters(msg)) => {
1992 assert!(msg.contains("must start with 'app-'"));
1993 }
1994 _ => panic!("Expected InvalidParameters error"),
1995 }
1996 }
1997
1998 #[test]
1999 fn test_app_id_try_from_str_invalid_hex() {
2000 let result = AppId::try_from("app-zzz");
2001 assert!(result.is_err());
2002 }
2003
2004 #[test]
2005 fn test_app_id_into_u64() {
2006 let id = AppId::from(321321);
2007 let value: u64 = id.into();
2008 assert_eq!(value, 321321);
2009 }
2010
2011 #[test]
2013 fn test_image_id_from_u64() {
2014 let id = ImageId::from(789789);
2015 assert_eq!(id.value(), 789789);
2016 }
2017
2018 #[test]
2019 fn test_image_id_display() {
2020 let id = ImageId::from(0xabcd1234);
2021 assert_eq!(format!("{}", id), "im-abcd1234");
2022 }
2023
2024 #[test]
2025 fn test_image_id_try_from_str_valid() {
2026 let id = ImageId::try_from("im-abcd1234").unwrap();
2027 assert_eq!(id.value(), 0xabcd1234);
2028 }
2029
2030 #[test]
2031 fn test_image_id_try_from_str_invalid_prefix() {
2032 let result = ImageId::try_from("image-123");
2033 assert!(result.is_err());
2034 match result {
2035 Err(Error::InvalidParameters(msg)) => {
2036 assert!(msg.contains("must start with 'im-'"));
2037 }
2038 _ => panic!("Expected InvalidParameters error"),
2039 }
2040 }
2041
2042 #[test]
2043 fn test_image_id_try_from_str_invalid_hex() {
2044 let result = ImageId::try_from("im-zzz");
2045 assert!(result.is_err());
2046 }
2047
2048 #[test]
2049 fn test_image_id_into_u64() {
2050 let id = ImageId::from(987987);
2051 let value: u64 = id.into();
2052 assert_eq!(value, 987987);
2053 }
2054
2055 #[test]
2057 fn test_id_types_equality() {
2058 let id1 = ProjectID::from(12345);
2059 let id2 = ProjectID::from(12345);
2060 let id3 = ProjectID::from(54321);
2061
2062 assert_eq!(id1, id2);
2063 assert_ne!(id1, id3);
2064 }
2065
2066 #[test]
2067 fn test_id_types_hash() {
2068 use std::collections::HashSet;
2069
2070 let mut set = HashSet::new();
2071 set.insert(DatasetID::from(100));
2072 set.insert(DatasetID::from(200));
2073 set.insert(DatasetID::from(100)); assert_eq!(set.len(), 2);
2076 assert!(set.contains(&DatasetID::from(100)));
2077 assert!(set.contains(&DatasetID::from(200)));
2078 }
2079
2080 #[test]
2081 fn test_id_types_copy_clone() {
2082 let id1 = ExperimentID::from(999);
2083 let id2 = id1; let id3 = id1; assert_eq!(id1, id2);
2087 assert_eq!(id1, id3);
2088 }
2089
2090 #[test]
2092 fn test_id_zero_value() {
2093 let id = ProjectID::from(0);
2094 assert_eq!(format!("{}", id), "p-0");
2095 assert_eq!(id.value(), 0);
2096 }
2097
2098 #[test]
2099 fn test_id_max_value() {
2100 let id = ProjectID::from(u64::MAX);
2101 assert_eq!(format!("{}", id), "p-ffffffffffffffff");
2102 assert_eq!(id.value(), u64::MAX);
2103 }
2104
2105 #[test]
2106 fn test_id_round_trip_conversion() {
2107 let original = 0xdeadbeef_u64;
2108 let id = TrainingSessionID::from(original);
2109 let back: u64 = id.into();
2110 assert_eq!(original, back);
2111 }
2112
2113 #[test]
2114 fn test_id_case_insensitive_hex() {
2115 let id1 = DatasetID::from_str("ds-ABCDEF").unwrap();
2117 let id2 = DatasetID::from_str("ds-abcdef").unwrap();
2118 assert_eq!(id1.value(), id2.value());
2119 }
2120
2121 #[test]
2122 fn test_id_with_leading_zeros() {
2123 let id = ProjectID::from_str("p-00001234").unwrap();
2124 assert_eq!(id.value(), 0x1234);
2125 }
2126
2127 #[test]
2129 fn test_parameter_integer() {
2130 let param = Parameter::Integer(42);
2131 match param {
2132 Parameter::Integer(val) => assert_eq!(val, 42),
2133 _ => panic!("Expected Integer variant"),
2134 }
2135 }
2136
2137 #[test]
2138 fn test_parameter_real() {
2139 let param = Parameter::Real(2.5);
2140 match param {
2141 Parameter::Real(val) => assert_eq!(val, 2.5),
2142 _ => panic!("Expected Real variant"),
2143 }
2144 }
2145
2146 #[test]
2147 fn test_parameter_boolean() {
2148 let param = Parameter::Boolean(true);
2149 match param {
2150 Parameter::Boolean(val) => assert!(val),
2151 _ => panic!("Expected Boolean variant"),
2152 }
2153 }
2154
2155 #[test]
2156 fn test_parameter_string() {
2157 let param = Parameter::String("test".to_string());
2158 match param {
2159 Parameter::String(val) => assert_eq!(val, "test"),
2160 _ => panic!("Expected String variant"),
2161 }
2162 }
2163
2164 #[test]
2165 fn test_parameter_array() {
2166 let param = Parameter::Array(vec![
2167 Parameter::Integer(1),
2168 Parameter::Integer(2),
2169 Parameter::Integer(3),
2170 ]);
2171 match param {
2172 Parameter::Array(arr) => assert_eq!(arr.len(), 3),
2173 _ => panic!("Expected Array variant"),
2174 }
2175 }
2176
2177 #[test]
2178 fn test_parameter_object() {
2179 let mut map = HashMap::new();
2180 map.insert("key".to_string(), Parameter::Integer(100));
2181 let param = Parameter::Object(map);
2182 match param {
2183 Parameter::Object(obj) => {
2184 assert_eq!(obj.len(), 1);
2185 assert!(obj.contains_key("key"));
2186 }
2187 _ => panic!("Expected Object variant"),
2188 }
2189 }
2190
2191 #[test]
2192 fn test_parameter_clone() {
2193 let param1 = Parameter::Integer(42);
2194 let param2 = param1.clone();
2195 assert_eq!(param1, param2);
2196 }
2197
2198 #[test]
2199 fn test_parameter_nested() {
2200 let inner_array = Parameter::Array(vec![Parameter::Integer(1), Parameter::Integer(2)]);
2201 let outer_array = Parameter::Array(vec![inner_array.clone(), inner_array]);
2202
2203 match outer_array {
2204 Parameter::Array(arr) => {
2205 assert_eq!(arr.len(), 2);
2206 }
2207 _ => panic!("Expected Array variant"),
2208 }
2209 }
2210
2211 macro_rules! test_typeid_conversions {
2214 ($test_name:ident, $type:ty, $prefix:literal, $wrong_prefix:literal) => {
2215 #[test]
2216 fn $test_name() {
2217 let id = <$type>::from(0xabc123);
2219 assert_eq!(id.value(), 0xabc123);
2220
2221 assert_eq!(format!("{}", id), concat!($prefix, "-abc123"));
2223
2224 let id: $type = concat!($prefix, "-abc123").parse().unwrap();
2226 assert_eq!(id.value(), 0xabc123);
2227
2228 assert!(concat!($wrong_prefix, "-abc").parse::<$type>().is_err());
2230
2231 assert!("abc123".parse::<$type>().is_err());
2233
2234 assert!(concat!($prefix, "-xyz").parse::<$type>().is_err());
2236
2237 let id = <$type>::try_from(concat!($prefix, "-abc123")).unwrap();
2239 assert_eq!(id.value(), 0xabc123);
2240
2241 let id = <$type>::try_from(concat!($prefix, "-abc123").to_string()).unwrap();
2243 assert_eq!(id.value(), 0xabc123);
2244
2245 let id = <$type>::from(0xabc123);
2247 let json = serde_json::to_string(&id).unwrap();
2248 let parsed: $type = serde_json::from_str(&json).unwrap();
2249 assert_eq!(id, parsed);
2250
2251 let id = <$type>::from(0xabc123);
2253 let val: u64 = id.into();
2254 assert_eq!(val, 0xabc123);
2255 }
2256 };
2257 }
2258
2259 test_typeid_conversions!(test_organization_id_conversions, OrganizationID, "org", "p");
2260 test_typeid_conversions!(test_project_id_conversions, ProjectID, "p", "org");
2261 test_typeid_conversions!(test_experiment_id_conversions, ExperimentID, "exp", "p");
2262 test_typeid_conversions!(
2263 test_training_session_id_conversions,
2264 TrainingSessionID,
2265 "t",
2266 "v"
2267 );
2268 test_typeid_conversions!(
2269 test_validation_session_id_conversions,
2270 ValidationSessionID,
2271 "v",
2272 "t"
2273 );
2274 test_typeid_conversions!(test_snapshot_id_conversions, SnapshotID, "ss", "ds");
2275 test_typeid_conversions!(test_task_id_conversions, TaskID, "task", "t");
2276 test_typeid_conversions!(test_dataset_id_conversions, DatasetID, "ds", "ss");
2277 test_typeid_conversions!(
2278 test_annotation_set_id_conversions,
2279 AnnotationSetID,
2280 "as",
2281 "ds"
2282 );
2283 test_typeid_conversions!(test_sample_id_conversions, SampleID, "s", "p");
2284 test_typeid_conversions!(test_app_id_conversions, AppId, "app", "p");
2285 test_typeid_conversions!(test_image_id_conversions, ImageId, "im", "se");
2286 test_typeid_conversions!(test_sequence_id_conversions, SequenceId, "se", "im");
2287}