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#[non_exhaustive]
11#[must_use]
12pub enum Entity {
13 Invalid,
15 Test(String),
17 Server(String),
19 Operation(String, String),
21 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 pub const LOCAL: &'static str = "__local__";
109
110 pub fn operation<T: Into<String>, U: Into<String>>(ns: T, name: U) -> Self {
112 Self::Operation(ns.into(), name.into())
113 }
114
115 pub fn local<T: Into<String>>(name: T) -> Self {
118 Self::Operation(Self::LOCAL.to_owned(), name.into())
119 }
120
121 pub fn test<T: Into<String>>(msg: T) -> Self {
123 Self::Test(msg.into())
124 }
125
126 pub fn component<T: Into<String>>(id: T) -> Self {
128 Self::Component(id.into())
129 }
130
131 pub fn server<T: Into<String>>(id: T) -> Self {
133 Self::Server(id.into())
134 }
135
136 #[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 #[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 #[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}