// file: crates/ksp-job-api/src/identity.rs // version: 1 /// Maximum UTF-8 byte length admitted for one Job identifier. pub const MAX_JOB_ID_BYTES: usize = 128; /// Maximum UTF-8 byte length admitted for one Job kind code. pub const MAX_JOB_KIND_CODE_BYTES: usize = 128; /// Bounded caller-supplied identity of one logical Job and its controlled resumptions. #[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct JobId(std::string::String); impl JobId { /// Creates one non-empty Job identifier using the KSP safe-code alphabet. pub fn new(value: impl std::convert::Into) -> crate::Result { let value = value.into(); if !valid_job_code(value.as_str(), crate::MAX_JOB_ID_BYTES) { return std::result::Result::Err(identity_error(crate::ERROR_CODE_JOB_ID_INVALID, "job_id")); } return std::result::Result::Ok(Self(value)); } /// Returns the validated Job identifier. #[must_use] pub fn as_str(&self) -> &str { return self.0.as_str(); } } impl std::fmt::Debug for JobId { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter.write_str("JobId(..)"); } } /// Bounded stable code identifying one concrete family of Jobs. #[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct JobKindCode(std::string::String); impl JobKindCode { /// Creates one non-empty Job kind using the KSP safe-code alphabet. pub fn new(value: impl std::convert::Into) -> crate::Result { let value = value.into(); if !valid_job_code(value.as_str(), crate::MAX_JOB_KIND_CODE_BYTES) { return std::result::Result::Err(identity_error(crate::ERROR_CODE_JOB_KIND_INVALID, "job_kind")); } return std::result::Result::Ok(Self(value)); } /// Returns the validated stable Job kind code. #[must_use] pub fn as_str(&self) -> &str { return self.0.as_str(); } } impl std::fmt::Debug for JobKindCode { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return formatter.debug_tuple("JobKindCode").field(&self.0).finish(); } } fn identity_error(code: crate::ErrorCode, field: &'static str) -> crate::Error { return crate::Error::new(code, "invalid bounded Job identity").with_context("field", field); } fn valid_job_code(value: &str, maximum_len: usize) -> bool { if value.is_empty() || value.len() > maximum_len { return false; } return value.bytes().all(|byte| return byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-' | b'.' | b':')); } #[cfg(test)] #[path = "../unit_tests/identity.rs"] mod tests;