Skip to main content

ferrum_interfaces/vnext/
identity.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4use super::VNextError;
5
6fn validate_identity(kind: &'static str, value: &str) -> Result<(), VNextError> {
7    if value.is_empty() {
8        return Err(VNextError::InvalidIdentity {
9            kind,
10            value: value.to_owned(),
11            reason: "identity must not be empty",
12        });
13    }
14    if value.len() > 160 {
15        return Err(VNextError::InvalidIdentity {
16            kind,
17            value: value.to_owned(),
18            reason: "identity exceeds 160 bytes",
19        });
20    }
21    if !value.bytes().all(|byte| {
22        byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-' | b':' | b'/')
23    }) {
24        return Err(VNextError::InvalidIdentity {
25            kind,
26            value: value.to_owned(),
27            reason: "identity contains a non-portable character",
28        });
29    }
30    Ok(())
31}
32
33macro_rules! stable_identity {
34    ($name:ident, $kind:literal) => {
35        #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
36        #[serde(try_from = "String", into = "String")]
37        pub struct $name(String);
38
39        impl $name {
40            pub fn new(value: impl Into<String>) -> Result<Self, VNextError> {
41                let value = value.into();
42                validate_identity($kind, &value)?;
43                Ok(Self(value))
44            }
45
46            pub fn as_str(&self) -> &str {
47                &self.0
48            }
49        }
50
51        impl fmt::Display for $name {
52            fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
53                formatter.write_str(&self.0)
54            }
55        }
56
57        impl TryFrom<String> for $name {
58            type Error = VNextError;
59
60            fn try_from(value: String) -> Result<Self, Self::Error> {
61                Self::new(value)
62            }
63        }
64
65        impl From<$name> for String {
66            fn from(value: $name) -> Self {
67                value.0
68            }
69        }
70    };
71}
72
73stable_identity!(CapabilityId, "capability");
74stable_identity!(DeviceId, "device");
75stable_identity!(ExternalModelMetadataId, "external model metadata");
76stable_identity!(ModelFamilyId, "model family");
77stable_identity!(NodeId, "node");
78stable_identity!(OperationId, "operation");
79stable_identity!(PlanId, "plan");
80stable_identity!(ProgramValueId, "program value");
81stable_identity!(ProviderId, "provider");
82stable_identity!(QuantizationFormatId, "quantization format");
83stable_identity!(RequestIdentity, "request");
84stable_identity!(ResourceId, "resource");
85stable_identity!(RunId, "run");
86stable_identity!(SpanId, "span");
87stable_identity!(StateId, "state");
88stable_identity!(TensorId, "tensor");
89stable_identity!(TokenizerId, "tokenizer");
90stable_identity!(TransactionId, "transaction");
91stable_identity!(WeightId, "weight");
92stable_identity!(WeightFormatId, "weight format");
93stable_identity!(WeightLayoutId, "weight layout");
94stable_identity!(WeightMaterializerId, "weight materializer");
95
96/// A versioned contract uses major for breaking and minor for additive changes.
97#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
98pub struct ContractVersion {
99    pub major: u16,
100    pub minor: u16,
101}
102
103impl ContractVersion {
104    pub const fn new(major: u16, minor: u16) -> Self {
105        Self { major, minor }
106    }
107
108    pub const fn satisfies(self, required: Self) -> bool {
109        self.major == required.major && self.minor >= required.minor
110    }
111}
112
113impl fmt::Display for ContractVersion {
114    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
115        write!(formatter, "{}.{}", self.major, self.minor)
116    }
117}