solti_model/domain/identity/
task.rs1use crate::error::ModelError;
7
8pub const TASK_ID_MAX_LEN: usize = crate::validation::DNS1123_SUBDOMAIN_MAX_LEN;
10
11arc_str_newtype! {
12 #[cfg_attr(feature = "schema", schemars(schema_with = "crate::schema::task_id"))]
13 pub struct TaskId;
25}
26
27impl TaskId {
28 pub fn validate_format(&self) -> Result<(), ModelError> {
43 crate::validation::validate_dns1123_subdomain("task_id", self.as_str())
44 }
45}
46
47#[cfg(test)]
48mod tests {
49 use super::*;
50 use std::sync::Arc;
51
52 #[test]
53 fn exposes_string_identity_hashing_and_shared_clones() {
54 use std::collections::HashSet;
55
56 let id = TaskId::new("id-1").unwrap();
57 assert_eq!(id.as_str(), "id-1");
58 assert_eq!(format!("{id}"), "id-1");
59
60 let mut set = HashSet::new();
61 set.insert(id.clone());
62 set.insert(TaskId::new("id-2").unwrap());
63 set.insert(TaskId::new("id-1").unwrap());
64 assert_eq!(set.len(), 2);
65
66 let cloned = id.clone();
67 let a: Arc<str> = id.into_inner();
68 let b: Arc<str> = cloned.into_inner();
69 assert!(Arc::ptr_eq(&a, &b));
70 }
71
72 #[test]
73 fn serde_is_transparent_and_validated() {
74 let id = TaskId::new("runner-slot-ff").unwrap();
75 let json = serde_json::to_string(&id).unwrap();
76 assert_eq!(json, r#""runner-slot-ff""#);
77 assert_eq!(serde_json::from_str::<TaskId>(&json).unwrap(), id);
78
79 for invalid in [
80 r#""a/b""#,
81 r#""""#,
82 r#"".""#,
83 r#""a b""#,
84 r#""UPPER""#,
85 r#""under_score""#,
86 ] {
87 assert!(
88 serde_json::from_str::<TaskId>(invalid).is_err(),
89 "must reject {invalid}"
90 );
91 }
92 }
93
94 #[test]
95 fn validation_matches_kubernetes_resource_names_and_length_limit() {
96 for valid in ["subprocess-build-1", "subprocess-build.frontend-ff"] {
97 TaskId::new(valid).unwrap();
98 }
99 for invalid in [
100 "",
101 "with/slash",
102 "with space",
103 "UPPER",
104 "with_underscore",
105 "-leading",
106 "trailing-",
107 "empty..label",
108 ] {
109 assert!(TaskId::new(invalid).is_err(), "must reject {invalid:?}");
110 }
111 assert!(TaskId::new("x".repeat(64)).is_ok());
112 let max = "a".repeat(TASK_ID_MAX_LEN);
113 assert!(TaskId::new(&max).is_ok());
114 assert!(TaskId::new(format!("{max}e")).is_err());
115 }
116}