Skip to main content

cloud_sdk/operation/
operation_id.rs

1//! Bounded provider operation identifiers.
2
3use core::fmt;
4
5/// Maximum bytes in a provider operation identifier.
6pub const MAX_OPERATION_ID_BYTES: usize = 128;
7
8/// Invalid provider operation identifier.
9#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10pub enum OperationIdError {
11    /// The identifier is empty.
12    Empty,
13    /// The identifier exceeds [`MAX_OPERATION_ID_BYTES`].
14    TooLong,
15    /// The identifier contains a byte outside lowercase ASCII, digits, or `_`.
16    InvalidByte,
17}
18
19impl fmt::Display for OperationIdError {
20    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
21        formatter.write_str(match self {
22            Self::Empty => "operation identifier is empty",
23            Self::TooLong => "operation identifier is too long",
24            Self::InvalidByte => "operation identifier contains an invalid byte",
25        })
26    }
27}
28
29impl core::error::Error for OperationIdError {}
30
31/// Validated static identifier assigned by a provider specification.
32#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
33pub struct OperationId(&'static str);
34
35impl OperationId {
36    /// Validates a provider operation identifier.
37    pub fn new(value: &'static str) -> Result<Self, OperationIdError> {
38        if value.is_empty() {
39            return Err(OperationIdError::Empty);
40        }
41        if value.len() > MAX_OPERATION_ID_BYTES {
42            return Err(OperationIdError::TooLong);
43        }
44        if value
45            .bytes()
46            .any(|byte| !byte.is_ascii_lowercase() && !byte.is_ascii_digit() && byte != b'_')
47        {
48            return Err(OperationIdError::InvalidByte);
49        }
50        Ok(Self(value))
51    }
52
53    /// Returns the validated identifier.
54    #[must_use]
55    pub const fn as_str(self) -> &'static str {
56        self.0
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::{OperationId, OperationIdError};
63
64    #[test]
65    fn accepts_source_style_identifiers_and_rejects_ambiguous_text() {
66        assert_eq!(
67            OperationId::new("get_server").map(OperationId::as_str),
68            Ok("get_server")
69        );
70        assert_eq!(OperationId::new(""), Err(OperationIdError::Empty));
71        assert_eq!(
72            OperationId::new("Get-Server"),
73            Err(OperationIdError::InvalidByte)
74        );
75    }
76}