wasmflow_entity/
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#[must_use]
11pub enum Entity {
12  /// A [SystemEntity] with the name "invalid". Used only for situations where a default is necessary.
13  Invalid,
14  /// The [SystemEntity] is used when communicating to or from the internals of another component. Used mostly by library developers.
15  System(SystemEntity),
16  /// A [SystemEntity] with the name "test". Used as the originating entity for tests.
17  Test(String),
18  /// A client entity used for requests.
19  Client(String),
20  /// A Host entity used for entities that serve responses to requests.
21  Host(String),
22  /// A component or anything that can be invoked like a component.
23  Component(String, String),
24  /// A collection of components.
25  Collection(String),
26  /// A reference to an instance of an entity.
27  Reference(String),
28}
29
30impl Serialize for Entity {
31  fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
32  where
33    S: serde::Serializer,
34  {
35    serializer.collect_str(&self)
36  }
37}
38
39impl<'de> Deserialize<'de> for Entity {
40  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
41  where
42    D: Deserializer<'de>,
43  {
44    let s = String::deserialize(deserializer)?;
45    FromStr::from_str(&s).map_err(serde::de::Error::custom)
46  }
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
50/// A struct to hold additional data for [SystemEntity]s.
51pub struct SystemEntity {
52  /// The name of the [SystemEntity].
53  pub name: String,
54  /// A freefrom string.
55  pub value: String,
56}
57
58impl Default for Entity {
59  fn default() -> Self {
60    Self::Test("default".to_owned())
61  }
62}
63
64impl Display for Entity {
65  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66    write!(f, "{}", self.url())
67  }
68}
69
70pub(crate) const URL_SCHEME: &str = "wafl";
71
72impl FromStr for Entity {
73  type Err = Error;
74
75  fn from_str(s: &str) -> Result<Self, Self::Err> {
76    use url::Url;
77    let url = Url::parse(s).map_err(Error::Parse)?;
78    ensure!(url.scheme() == URL_SCHEME, Error::Scheme(url.scheme().to_owned()));
79    let host = url.host_str().ok_or(Error::Authority)?;
80    let (id, kind) = host
81      .split_once('.')
82      .ok_or_else(|| Error::InvalidAuthority(host.to_owned()))?;
83    match kind {
84      "sys" => {
85        let (_, msg) = url
86          .query_pairs()
87          .find(|(k, _v)| k == "msg")
88          .unwrap_or(("".into(), "".into()));
89
90        if id == "test" {
91          Ok(Entity::test(msg))
92        } else {
93          Ok(Entity::system(id, msg))
94        }
95      }
96      "ref" => Ok(Entity::reference(id)),
97      "coll" => {
98        if let Some(mut segments) = url.path_segments() {
99          if let Some(name) = segments.next() {
100            if !name.is_empty() {
101              return Ok(Entity::component(id, name));
102            }
103          }
104        }
105
106        Ok(Entity::collection(id))
107      }
108      "client" => Ok(Entity::client(id)),
109      "host" => Ok(Entity::host(id)),
110      _ => Err(Error::InvalidAuthorityKind(kind.to_owned())),
111    }
112  }
113}
114impl Entity {
115  /// Namespace for components local to a collection.
116  pub const LOCAL: &'static str = "__local__";
117
118  /// Constructor for [Entity::Component].
119  pub fn component<T: AsRef<str>, U: AsRef<str>>(ns: T, name: U) -> Self {
120    Self::Component(ns.as_ref().to_owned(), name.as_ref().to_owned())
121  }
122
123  /// Constructor for [Entity::Component] on the local namespace, used when
124  /// the namespace is irrelevant. Caution: this is not portable.
125  pub fn local<T: AsRef<str>>(name: T) -> Self {
126    Self::Component(Self::LOCAL.to_owned(), name.as_ref().to_owned())
127  }
128
129  /// Constructor for [Entity::Component] without a namespace, used when
130  /// the namespace is irrelevant. Caution: this is not portable.
131  #[deprecated(note = "please use `local()` instead")]
132  pub fn component_direct<T: AsRef<str>>(name: T) -> Self {
133    Self::Component(Self::LOCAL.to_owned(), name.as_ref().to_owned())
134  }
135
136  /// Constructor for [Entity::System].
137  pub fn system<T: AsRef<str>, U: AsRef<str>>(name: T, value: U) -> Self {
138    Self::System(SystemEntity {
139      name: name.as_ref().to_owned(),
140      value: value.as_ref().to_owned(),
141    })
142  }
143
144  /// Constructor for an [Entity::Test].
145  pub fn test<T: AsRef<str>>(msg: T) -> Self {
146    Self::Test(msg.as_ref().to_owned())
147  }
148
149  /// Constructor for an [Entity::Collection].
150  pub fn collection<T: AsRef<str>>(id: T) -> Self {
151    Self::Collection(id.as_ref().to_owned())
152  }
153
154  /// Constructor for [Entity::Host].
155  pub fn host<T: AsRef<str>>(id: T) -> Self {
156    Self::Host(id.as_ref().to_owned())
157  }
158
159  /// Constructor for [Entity::Client].
160  pub fn client<T: AsRef<str>>(id: T) -> Self {
161    Self::Client(id.as_ref().to_owned())
162  }
163
164  /// Constructor for [Entity::Client].
165  pub fn reference<T: AsRef<str>>(id: T) -> Self {
166    Self::Reference(id.as_ref().to_owned())
167  }
168
169  /// The URL of the entity.
170  #[must_use]
171  pub fn url(&self) -> String {
172    match self {
173      Entity::Test(msg) => format!("{}://test.sys/?msg={}", URL_SCHEME, msg),
174      Entity::Component(ns, id) => format!("{}://{}.coll/{}", URL_SCHEME, ns, id),
175      Entity::Collection(name) => format!("{}://{}.coll/", URL_SCHEME, name),
176      Entity::Client(id) => format!("{}://{}.client/", URL_SCHEME, id),
177      Entity::Host(id) => format!("{}://{}.host/", URL_SCHEME, id),
178      Entity::System(e) => format!("{}://{}.sys/?msg={}", URL_SCHEME, e.name, e.value),
179      Entity::Invalid => format!("{}://invalid.sys/", URL_SCHEME),
180      Entity::Reference(id) => format!("{}://{}.ref/", URL_SCHEME, id),
181    }
182  }
183
184  /// The name of the entity.
185  #[must_use]
186  pub fn name(&self) -> &str {
187    match self {
188      Entity::Test(_) => "test",
189      Entity::Component(_, id) => id,
190      Entity::Collection(name) => name,
191      Entity::Client(id) => id,
192      Entity::Host(id) => id,
193      Entity::System(e) => &e.name,
194      Entity::Invalid => "<invalid>",
195      Entity::Reference(id) => id,
196    }
197  }
198
199  /// The namespace for the entity.
200  #[must_use]
201  pub fn namespace(&self) -> &str {
202    match self {
203      Entity::Test(_) => "test",
204      Entity::Component(ns, _) => ns,
205      Entity::Collection(name) => name,
206      Entity::Client(id) => id,
207      Entity::Host(id) => id,
208      Entity::System(e) => &e.name,
209      Entity::Invalid => "<invalid>",
210      Entity::Reference(id) => id,
211    }
212  }
213}
214
215#[cfg(test)]
216mod tests {
217
218  use super::*;
219  #[test]
220  fn test() -> Result<(), Error> {
221    let entity = Entity::from_str("wafl://namespace.coll/comp_name")?;
222    assert_eq!(entity, Entity::component("namespace", "comp_name"));
223
224    let entity = Entity::from_str("wafl://some_ns.coll/")?;
225    assert_eq!(entity, Entity::collection("some_ns"));
226
227    let entity = Entity::from_str("wafl://host_id.host/")?;
228    assert_eq!(entity, Entity::host("host_id"));
229
230    let entity = Entity::from_str("wafl://host_id.ref/")?;
231    assert_eq!(entity, Entity::reference("host_id"));
232
233    let entity = Entity::from_str("wafl://client_id.client/")?;
234    assert_eq!(entity, Entity::client("client_id"));
235
236    let entity = Entity::from_str("wafl://test.sys/?msg=Hello")?;
237    assert_eq!(entity, Entity::test("Hello"));
238
239    let entity = Entity::from_str("wafl://other.sys/?msg=Else")?;
240    assert_eq!(
241      entity,
242      Entity::System(SystemEntity {
243        name: "other".into(),
244        value: "Else".into()
245      })
246    );
247
248    Ok(())
249  }
250}