architect_api/utils/
component_id.rs1#[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#[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#[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 {}