ferrum_interfaces/vnext/
identity.rs1use 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#[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}