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#[must_use]
11pub enum Entity {
12 Invalid,
14 System(SystemEntity),
16 Test(String),
18 Client(String),
20 Host(String),
22 Component(String, String),
24 Collection(String),
26 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)]
50pub struct SystemEntity {
52 pub name: String,
54 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 pub const LOCAL: &'static str = "__local__";
117
118 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 pub fn local<T: AsRef<str>>(name: T) -> Self {
126 Self::Component(Self::LOCAL.to_owned(), name.as_ref().to_owned())
127 }
128
129 #[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 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 pub fn test<T: AsRef<str>>(msg: T) -> Self {
146 Self::Test(msg.as_ref().to_owned())
147 }
148
149 pub fn collection<T: AsRef<str>>(id: T) -> Self {
151 Self::Collection(id.as_ref().to_owned())
152 }
153
154 pub fn host<T: AsRef<str>>(id: T) -> Self {
156 Self::Host(id.as_ref().to_owned())
157 }
158
159 pub fn client<T: AsRef<str>>(id: T) -> Self {
161 Self::Client(id.as_ref().to_owned())
162 }
163
164 pub fn reference<T: AsRef<str>>(id: T) -> Self {
166 Self::Reference(id.as_ref().to_owned())
167 }
168
169 #[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 #[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 #[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}