pubky_common/
capabilities.rs1use std::fmt::Display;
4
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct Capability {
11 pub scope: String,
13 pub actions: Vec<Action>,
15}
16
17impl Capability {
18 pub fn root() -> Self {
20 Capability {
21 scope: "/".to_string(),
22 actions: vec![Action::Read, Action::Write],
23 }
24 }
25}
26
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub enum Action {
30 Read,
32 Write,
34 Unknown(char),
36}
37
38impl From<&Action> for char {
39 fn from(value: &Action) -> Self {
40 match value {
41 Action::Read => 'r',
42 Action::Write => 'w',
43 Action::Unknown(char) => char.to_owned(),
44 }
45 }
46}
47
48impl TryFrom<char> for Action {
49 type Error = Error;
50
51 fn try_from(value: char) -> Result<Self, Error> {
52 match value {
53 'r' => Ok(Self::Read),
54 'w' => Ok(Self::Write),
55 _ => Err(Error::InvalidAction),
56 }
57 }
58}
59
60impl Display for Capability {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 write!(
63 f,
64 "{}:{}",
65 self.scope,
66 self.actions.iter().map(char::from).collect::<String>()
67 )
68 }
69}
70
71impl TryFrom<String> for Capability {
72 type Error = Error;
73
74 fn try_from(value: String) -> Result<Self, Error> {
75 value.as_str().try_into()
76 }
77}
78
79impl TryFrom<&str> for Capability {
80 type Error = Error;
81
82 fn try_from(value: &str) -> Result<Self, Error> {
83 if value.matches(':').count() != 1 {
84 return Err(Error::InvalidFormat);
85 }
86
87 if !value.starts_with('/') {
88 return Err(Error::InvalidScope);
89 }
90
91 let actions_str = value.rsplit(':').next().unwrap_or("");
92
93 let mut actions = Vec::new();
94
95 for char in actions_str.chars() {
96 let ability = Action::try_from(char)?;
97
98 match actions.binary_search_by(|element| char::from(element).cmp(&char)) {
99 Ok(_) => {}
100 Err(index) => {
101 actions.insert(index, ability);
102 }
103 }
104 }
105
106 let scope = value[0..value.len() - actions_str.len() - 1].to_string();
107
108 Ok(Capability { scope, actions })
109 }
110}
111
112impl Serialize for Capability {
113 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
114 where
115 S: serde::Serializer,
116 {
117 let string = self.to_string();
118
119 string.serialize(serializer)
120 }
121}
122
123impl<'de> Deserialize<'de> for Capability {
124 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
125 where
126 D: serde::Deserializer<'de>,
127 {
128 let string: String = Deserialize::deserialize(deserializer)?;
129
130 string.try_into().map_err(serde::de::Error::custom)
131 }
132}
133
134#[derive(thiserror::Error, Debug, PartialEq, Eq)]
135pub enum Error {
137 #[error("Capability: Invalid scope: does not start with `/`")]
138 InvalidScope,
140 #[error("Capability: Invalid format should be <scope>:<abilities>")]
141 InvalidFormat,
143 #[error("Capability: Invalid Action")]
144 InvalidAction,
146 #[error("Capabilities: Invalid capabilities format")]
147 InvalidCapabilities,
149}
150
151#[derive(Clone, Default, Debug, PartialEq, Eq)]
152pub struct Capabilities(pub Vec<Capability>);
155
156impl Capabilities {
157 pub fn contains(&self, capability: &Capability) -> bool {
159 self.0.contains(capability)
160 }
161}
162
163impl From<Vec<Capability>> for Capabilities {
164 fn from(value: Vec<Capability>) -> Self {
165 Self(value)
166 }
167}
168
169impl From<Capabilities> for Vec<Capability> {
170 fn from(value: Capabilities) -> Self {
171 value.0
172 }
173}
174
175impl TryFrom<&str> for Capabilities {
176 type Error = Error;
177
178 fn try_from(value: &str) -> Result<Self, Self::Error> {
179 let mut caps = vec![];
180
181 for s in value.split(',') {
182 if let Ok(cap) = Capability::try_from(s) {
183 caps.push(cap);
184 };
185 }
186
187 Ok(Capabilities(caps))
188 }
189}
190
191impl Display for Capabilities {
192 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193 let string = self
194 .0
195 .iter()
196 .map(|c| c.to_string())
197 .collect::<Vec<_>>()
198 .join(",");
199
200 write!(f, "{string}")
201 }
202}
203
204impl Serialize for Capabilities {
205 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
206 where
207 S: serde::Serializer,
208 {
209 self.to_string().serialize(serializer)
210 }
211}
212
213impl<'de> Deserialize<'de> for Capabilities {
214 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
215 where
216 D: serde::Deserializer<'de>,
217 {
218 let string: String = Deserialize::deserialize(deserializer)?;
219
220 let mut caps = vec![];
221
222 for s in string.split(',') {
223 if let Ok(cap) = Capability::try_from(s) {
224 caps.push(cap);
225 };
226 }
227
228 Ok(Capabilities(caps))
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235
236 #[test]
237 fn pubky_caps() {
238 let cap = Capability {
239 scope: "/pub/pubky.app/".to_string(),
240 actions: vec![Action::Read, Action::Write],
241 };
242
243 let expected_string = "/pub/pubky.app/:rw";
245
246 assert_eq!(cap.to_string(), expected_string);
247
248 assert_eq!(Capability::try_from(expected_string), Ok(cap))
249 }
250}