architect_api/utils/
component_id.rs

1#[cfg(feature = "netidx")]
2use derive::FromValue;
3#[cfg(feature = "netidx")]
4use netidx_derive::Pack;
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7use std::{error::Error as StdError, fmt, str::FromStr};
8
9/// Components within an Architect installation are uniquely identified by a 16-bit integer
10/// in the range `1..<0xFFFF`.
11///
12/// The integers 0 and 0xFFFF are reserved as special values and MUST NOT be used as component IDs.
13///
14/// Canonical meanings of special values:
15///
16/// * `0x0` -- None/executor/broadcast
17/// * `0xFFFF` -- Self/loopback
18#[derive(
19    Debug,
20    Clone,
21    Copy,
22    PartialEq,
23    Eq,
24    PartialOrd,
25    Ord,
26    Hash,
27    Serialize,
28    Deserialize,
29    JsonSchema,
30)]
31#[cfg_attr(feature = "juniper", derive(juniper::GraphQLScalar))]
32#[cfg_attr(feature = "netidx", derive(Pack))]
33#[cfg_attr(feature = "netidx", derive(FromValue))]
34#[cfg_attr(feature = "netidx", pack(unwrapped))]
35#[repr(transparent)]
36pub struct ComponentId(pub(crate) u16);
37
38impl ComponentId {
39    pub fn new(id: u16) -> Result<Self, ComponentIdError> {
40        if id == 0 || id == u16::MAX {
41            Err(ComponentIdError::InvalidId)
42        } else {
43            Ok(Self(id))
44        }
45    }
46
47    #[inline(always)]
48    pub fn none() -> Self {
49        Self(0)
50    }
51
52    #[inline(always)]
53    pub fn is_none(&self) -> bool {
54        self.0 == 0
55    }
56
57    #[inline(always)]
58    pub fn loopback() -> Self {
59        Self(u16::MAX)
60    }
61
62    #[inline(always)]
63    pub fn is_loopback(&self) -> bool {
64        self.0 == u16::MAX
65    }
66
67    pub fn filename(&self) -> String {
68        format!("{}", self.0)
69    }
70}
71
72impl TryFrom<u16> for ComponentId {
73    type Error = ComponentIdError;
74
75    fn try_from(id: u16) -> Result<Self, Self::Error> {
76        Self::new(id)
77    }
78}
79
80impl fmt::Display for ComponentId {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        if self.is_none() {
83            write!(f, "#none")
84        } else if self.is_loopback() {
85            write!(f, "#loopback")
86        } else {
87            write!(f, "#{}", self.0)
88        }
89    }
90}
91
92impl FromStr for ComponentId {
93    type Err = ComponentIdError;
94
95    fn from_str(s: &str) -> Result<Self, Self::Err> {
96        if s.starts_with('#') {
97            let id = s[1..].parse::<u16>().map_err(|_| ComponentIdError::ParseError)?;
98            Self::new(id)
99        } else {
100            Err(ComponentIdError::ParseError)
101        }
102    }
103}
104
105// CR alee: JuniperScalarFromStr macro
106#[cfg(feature = "juniper")]
107impl ComponentId {
108    pub fn to_output<S: juniper::ScalarValue>(&self) -> juniper::Value<S> {
109        juniper::Value::scalar(self.to_string())
110    }
111
112    pub fn from_input<S>(v: &juniper::InputValue<S>) -> Result<Self, String>
113    where
114        S: juniper::ScalarValue,
115    {
116        v.as_string_value()
117            .map(|s| s.parse::<Self>())
118            .ok_or_else(|| format!("Expected `String`, found: {v}"))?
119            .map_err(|e| e.to_string())
120    }
121
122    pub fn parse_token<S>(
123        value: juniper::ScalarToken<'_>,
124    ) -> juniper::ParseScalarResult<S>
125    where
126        S: juniper::ScalarValue,
127    {
128        <String as juniper::ParseScalarValue<S>>::from_str(value)
129    }
130}
131
132#[derive(Debug, Clone)]
133pub enum ComponentIdError {
134    InvalidId,
135    ParseError,
136}
137
138impl fmt::Display for ComponentIdError {
139    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140        match self {
141            Self::InvalidId => {
142                write!(f, "invalid component id; must not be 0 or 0xFFFF")
143            }
144            Self::ParseError => {
145                write!(f, "invalid component id format; must be of the form #<id>")
146            }
147        }
148    }
149}
150
151impl StdError for ComponentIdError {}