wick_packet/
entity.rs

1use std::fmt::Display;
2use std::str::FromStr;
3
4use serde::{Deserialize, Deserializer, Serialize};
5
6use crate::error::ParseError as Error;
7
8#[derive(Debug, Clone, PartialEq)]
9/// The entity being referenced across systems or services.
10#[non_exhaustive]
11#[must_use]
12pub enum Entity {
13  /// An invalid entity. Used only for situations where a default is necessary.
14  Invalid,
15  /// A "test" entity. Used as the originating entity for tests.
16  Test(String),
17  /// A server or host entity (i.e. for requests).
18  Server(String),
19  /// An operation or anything that can be invoked like an operation.
20  Operation(String, String),
21  /// A component that hosts a collection of operations.
22  Component(String),
23}
24
25impl Serialize for Entity {
26  fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
27  where
28    S: serde::Serializer,
29  {
30    serializer.collect_str(&self)
31  }
32}
33
34impl<'de> Deserialize<'de> for Entity {
35  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
36  where
37    D: Deserializer<'de>,
38  {
39    let s = String::deserialize(deserializer)?;
40    FromStr::from_str(&s).map_err(serde::de::Error::custom)
41  }
42}
43
44impl Default for Entity {
45  fn default() -> Self {
46    Self::Test("default".to_owned())
47  }
48}
49
50impl Display for Entity {
51  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52    write!(f, "{}", self.url())
53  }
54}
55
56pub(crate) const URL_SCHEME: &str = "wick";
57
58impl FromStr for Entity {
59  type Err = Error;
60
61  fn from_str(s: &str) -> Result<Self, Self::Err> {
62    use url::Url;
63    let url = Url::parse(s).map_err(Error::Parse)?;
64    if url.scheme() != URL_SCHEME {
65      return Err(Error::Scheme(url.scheme().to_owned()));
66    }
67    let host = url.host_str().ok_or(Error::Authority)?;
68    if let Some((id, kind)) = host.split_once('.') {
69      if kind == "host" {
70        return Ok(Entity::server(id));
71      }
72      return Err(Error::InvalidAuthority(host.to_owned()));
73    }
74
75    match host {
76      "__test__" => {
77        let (_, msg) = url
78          .query_pairs()
79          .find(|(k, _v)| k == "msg")
80          .unwrap_or(("".into(), "".into()));
81        Ok(Entity::test(msg))
82      }
83      "__invalid__" => Ok(Entity::Invalid),
84      _ => {
85        if let Some(mut segments) = url.path_segments() {
86          if let Some(name) = segments.next() {
87            if !name.is_empty() {
88              return Ok(Entity::operation(host, name));
89            }
90          }
91        }
92        Ok(Entity::component(host))
93      }
94    }
95  }
96}
97
98impl TryFrom<&str> for Entity {
99  type Error = Error;
100
101  fn try_from(value: &str) -> Result<Self, Self::Error> {
102    Self::from_str(value)
103  }
104}
105
106impl Entity {
107  /// Namespace for components local to a collection.
108  pub const LOCAL: &'static str = "__local__";
109
110  /// Constructor for [Entity::Component].
111  pub fn operation<T: Into<String>, U: Into<String>>(ns: T, name: U) -> Self {
112    Self::Operation(ns.into(), name.into())
113  }
114
115  /// Constructor for [Entity::Component] on the local namespace, used when
116  /// the namespace is irrelevant. Caution: this is not portable.
117  pub fn local<T: Into<String>>(name: T) -> Self {
118    Self::Operation(Self::LOCAL.to_owned(), name.into())
119  }
120
121  /// Constructor for an [Entity::Test].
122  pub fn test<T: Into<String>>(msg: T) -> Self {
123    Self::Test(msg.into())
124  }
125
126  /// Constructor for an [Entity::Component].
127  pub fn component<T: Into<String>>(id: T) -> Self {
128    Self::Component(id.into())
129  }
130
131  /// Constructor for [Entity::Server].
132  pub fn server<T: Into<String>>(id: T) -> Self {
133    Self::Server(id.into())
134  }
135
136  /// The URL of the entity.
137  #[must_use]
138  pub fn url(&self) -> String {
139    match self {
140      Entity::Test(msg) => format!("{}://__test__/?msg={}", URL_SCHEME, msg),
141      Entity::Operation(ns, id) => format!("{}://{}/{}", URL_SCHEME, ns, id),
142      Entity::Component(name) => format!("{}://{}/", URL_SCHEME, name),
143      Entity::Server(id) => format!("{}://{}.host/", URL_SCHEME, id),
144      Entity::Invalid => format!("{}://__invalid__/", URL_SCHEME),
145    }
146  }
147
148  /// The name of the entity.
149  #[must_use]
150  pub fn operation_id(&self) -> &str {
151    match self {
152      Entity::Test(_) => "",
153      Entity::Operation(_, id) => id,
154      Entity::Component(_) => "",
155      Entity::Server(_) => "",
156      Entity::Invalid => "",
157    }
158  }
159
160  pub fn set_operation<T: Into<String>>(&mut self, id: T) {
161    match self {
162      Entity::Test(_) => {}
163      Entity::Operation(_, op_id) => *op_id = id.into(),
164      Entity::Component(comp_id) => *self = Entity::operation(comp_id.clone(), id.into()),
165      Entity::Server(_) => {}
166      Entity::Invalid => {}
167    }
168  }
169
170  /// The id of the component entity.
171  #[must_use]
172  pub fn component_id(&self) -> &str {
173    match self {
174      Entity::Test(_) => "test",
175      Entity::Operation(id, _) => id,
176      Entity::Component(name) => name,
177      Entity::Server(id) => id,
178      Entity::Invalid => "<invalid>",
179    }
180  }
181}
182
183#[cfg(test)]
184mod tests {
185
186  use super::*;
187  #[test]
188  fn test() -> Result<(), Error> {
189    let entity = Entity::from_str("wick://namespace/comp_name")?;
190    assert_eq!(entity, Entity::operation("namespace", "comp_name"));
191
192    let entity = Entity::from_str("wick://some_ns/")?;
193    assert_eq!(entity, Entity::component("some_ns"));
194
195    let entity = Entity::from_str("wick://client_id.host/")?;
196    assert_eq!(entity, Entity::server("client_id"));
197
198    let entity = Entity::from_str("wick://__test__/?msg=Hello")?;
199    assert_eq!(entity, Entity::test("Hello"));
200
201    Ok(())
202  }
203}