1use core::str::FromStr;
2use std::collections::HashMap;
3use std::ops::Deref;
4
5use validator::validate_email;
6
7use crate::command::common::PropertyMod;
8use crate::point::Point;
9use crate::parse::SkewerCase;
10use crate::{Kind, SetProperties, SpaceErr};
11
12pub struct PropertyDef {
13 pub pattern: Box<dyn PropertyPattern>,
14 pub required: bool,
15 pub mutable: bool,
16 pub source: PropertySource,
17 pub default: Option<String>,
18 pub constant: bool,
19 pub permits: Vec<PropertyPermit>,
20}
21
22impl PropertyDef {
23 pub fn new(
24 pattern: Box<dyn PropertyPattern>,
25 required: bool,
26 mutable: bool,
27 source: PropertySource,
28 default: Option<String>,
29 constant: bool,
30 permits: Vec<PropertyPermit>,
31 ) -> Result<Self, SpaceErr> {
32 if constant {
33 default
34 .as_ref()
35 .ok_or("if PropertyDef is a constant then 'default' value must be set")?;
36 }
37
38 if let Some(value) = default.as_ref() {
39 match pattern.is_match(value) {
40 Ok(_) => {}
41 Err(err) => {
42 return Err(format!(
43 "default value does not match pattern: {}",
44 err.to_string()
45 )
46 .into());
47 }
48 }
49 }
50
51 Ok(Self {
52 pattern,
53 required,
54 mutable,
55 source,
56 default,
57 constant,
58 permits,
59 })
60 }
61}
62
63pub trait PropertyPattern: Send + Sync + 'static {
64 fn is_match(&self, value: &String) -> Result<(), SpaceErr>;
65}
66
67#[derive(Clone)]
68pub struct AnythingPattern {}
69
70impl PropertyPattern for AnythingPattern {
71 fn is_match(&self, value: &String) -> Result<(), SpaceErr> {
72 Ok(())
73 }
74}
75
76#[derive(Clone)]
77pub struct PointPattern {}
78
79impl PropertyPattern for PointPattern {
80 fn is_match(&self, value: &String) -> Result<(), SpaceErr> {
81 use std::str::FromStr;
82 Point::from_str(value.as_str())?;
83 Ok(())
84 }
85}
86
87#[derive(Clone)]
88pub struct U64Pattern {}
89
90impl PropertyPattern for U64Pattern {
91 fn is_match(&self, value: &String) -> Result<(), SpaceErr> {
92 use std::str::FromStr;
93 match u64::from_str(value.as_str()) {
94 Ok(_) => Ok(()),
95 Err(err) => Err(err.to_string().into()),
96 }
97 }
98}
99
100#[derive(Clone)]
101pub struct BoolPattern {}
102
103impl PropertyPattern for BoolPattern {
104 fn is_match(&self, value: &String) -> Result<(), SpaceErr> {
105 use std::str::FromStr;
106 match bool::from_str(value.as_str()) {
107 Ok(_) => Ok(()),
108 Err(err) => Err(err.to_string().into()),
109 }
110 }
111}
112
113#[derive(Clone)]
114pub struct UsernamePattern {}
115
116impl PropertyPattern for UsernamePattern {
117 fn is_match(&self, value: &String) -> Result<(), SpaceErr> {
118 SkewerCase::from_str(value.as_str())?;
119 Ok(())
120 }
121}
122
123#[derive(Clone)]
124pub struct EmailPattern {}
125
126impl PropertyPattern for EmailPattern {
127 fn is_match(&self, value: &String) -> Result<(), SpaceErr> {
128 if !validate_email(value) {
129 Err(format!("not a valid email: '{}'", value).into())
130 } else {
131 Ok(())
132 }
133 }
134}
135
136#[derive(Clone)]
137pub enum PropertySource {
138 Shell,
139 Core,
140 CoreReadOnly,
141 CoreSecret,
142}
143
144pub struct PropertiesConfig {
145 pub properties: HashMap<String, PropertyDef>,
146 pub kind: Kind,
147}
148
149impl Deref for PropertiesConfig {
150 type Target = HashMap<String, PropertyDef>;
151
152 fn deref(&self) -> &Self::Target {
153 &self.properties
154 }
155}
156
157impl PropertiesConfig {
158 pub fn new(kind: Kind) -> PropertiesConfig {
159 Self {
160 properties: HashMap::new(),
161 kind,
162 }
163 }
164
165 pub fn builder() -> PropertiesConfigBuilder {
166 PropertiesConfigBuilder {
167 kind: None,
168 properties: HashMap::new(),
169 }
170 }
171
172 pub fn required(&self) -> Vec<String> {
173 let mut rtn = vec![];
174 for (key, def) in &self.properties {
175 if def.required {
176 rtn.push(key.clone());
177 }
178 }
179 rtn
180 }
181
182 pub fn defaults(&self) -> Vec<String> {
183 let mut rtn = vec![];
184 for (key, def) in &self.properties {
185 if def.default.is_some() {
186 rtn.push(key.clone());
187 }
188 }
189 rtn
190 }
191
192 pub fn check_create(&self, set: &SetProperties) -> Result<(), SpaceErr> {
193 for req in self.required() {
194 if !set.contains_key(&req) {
195 return Err(format!(
196 "{} missing required property: '{}'",
197 self.kind.to_string(),
198 req
199 )
200 .into());
201 }
202 }
203
204 for (key, propmod) in &set.map {
205 let def = self.get(key).ok_or(format!(
206 "{} illegal property: '{}'",
207 self.kind.to_string(),
208 key
209 ))?;
210 match propmod {
211 PropertyMod::Set { key, value, lock } => {
212 if def.constant && def.default.as_ref().unwrap().clone() != value.clone() {
213 return Err(format!(
214 "{} property: '{}' is constant and cannot be set",
215 self.kind.to_string(),
216 key
217 )
218 .into());
219 }
220 def.pattern.is_match(value)?;
221 match def.source {
222 PropertySource::CoreReadOnly => {
223 return Err(format!("{} property '{}' is flagged CoreReadOnly and cannot be set within the Mesh",self.kind.to_string(), key).into());
224 }
225 _ => {}
226 }
227 }
228 PropertyMod::UnSet(_) => {
229 return Err(format!("cannot unset: '{}' during particle create", key).into());
230 }
231 }
232 }
233 Ok(())
234 }
235
236 pub fn check_update(&self, set: &SetProperties) -> Result<(), SpaceErr> {
237 for (key, propmod) in &set.map {
238 let def = self
239 .get(key)
240 .ok_or(format!("illegal property: '{}'", key))?;
241 match propmod {
242 PropertyMod::Set { key, value, lock } => {
243 if def.constant {
244 return Err(
245 format!("property: '{}' is constant and cannot be set", key).into()
246 );
247 }
248 def.pattern.is_match(value)?;
249 match def.source {
250 PropertySource::CoreReadOnly => {
251 return Err(format!("property '{}' is flagged CoreReadOnly and cannot be set within the Mesh",key).into());
252 }
253 _ => {}
254 }
255 }
256 PropertyMod::UnSet(_) => {
257 if !def.mutable {
258 return Err(format!("property '{}' is immutable and cannot be changed after particle creation",key).into());
259 }
260 if def.required {
261 return Err(
262 format!("property '{}' is required and cannot be unset", key).into(),
263 );
264 }
265 }
266 }
267 }
268 Ok(())
269 }
270
271 pub fn check_read(&self, keys: &Vec<String>) -> Result<(), SpaceErr> {
272 for key in keys {
273 let def = self
274 .get(key)
275 .ok_or(format!("illegal property: '{}'", key))?;
276 match def.source {
277 PropertySource::CoreSecret => {
278 return Err(format!(
279 "property '{}' is flagged CoreSecret and cannot be read within the Mesh",
280 key
281 )
282 .into());
283 }
284 _ => {}
285 }
286 }
287 Ok(())
288 }
289
290 pub fn fill_create_defaults(&self, set: &SetProperties) -> Result<SetProperties, SpaceErr> {
291 let mut rtn = set.clone();
292 let defaults = self.defaults();
293 for d in defaults {
294 if !rtn.contains_key(&d) {
295 let def = self
296 .get(&d)
297 .ok_or(format!("expected default property def: {}", &d))?;
298 let value = def
299 .default
300 .as_ref()
301 .ok_or(format!("expected default property def: {}", &d))?
302 .clone();
303 rtn.push(PropertyMod::Set {
304 key: d,
305 value,
306 lock: false,
307 });
308 }
309 }
310 Ok(rtn)
311 }
312}
313
314pub enum PropertyPermit {
315 Read,
316 Write,
317}
318
319pub struct PropertiesConfigBuilder {
320 kind: Option<Kind>,
321 properties: HashMap<String, PropertyDef>,
322}
323
324impl PropertiesConfigBuilder {
325 pub fn new() -> Self {
326 let mut rtn = Self {
327 kind: None,
328 properties: HashMap::new(),
329 };
330 rtn.add_point("bind", false, true).unwrap();
331 rtn
332 }
333
334 pub fn build(self) -> Result<PropertiesConfig, SpaceErr> {
335 Ok(PropertiesConfig {
336 kind: self.kind.ok_or(SpaceErr::server_error(
337 "kind must be set before PropertiesConfig can be built",
338 ))?,
339 properties: self.properties,
340 })
341 }
342
343 pub fn kind(&mut self, kind: Kind) {
344 self.kind.replace(kind);
345 }
346
347 pub fn add(
348 &mut self,
349 name: &str,
350 pattern: Box<dyn PropertyPattern>,
351 required: bool,
352 mutable: bool,
353 source: PropertySource,
354 default: Option<String>,
355 constant: bool,
356 permits: Vec<PropertyPermit>,
357 ) -> Result<(), SpaceErr> {
358 let def = PropertyDef::new(
359 pattern, required, mutable, source, default, constant, permits,
360 )?;
361 self.properties.insert(name.to_string(), def);
362 Ok(())
363 }
364
365 pub fn add_string(&mut self, name: &str) -> Result<(), SpaceErr> {
366 let def = PropertyDef::new(
367 Box::new(AnythingPattern {}),
368 false,
369 true,
370 PropertySource::Shell,
371 None,
372 false,
373 vec![],
374 )?;
375 self.properties.insert(name.to_string(), def);
376 Ok(())
377 }
378
379 pub fn add_point(&mut self, name: &str, required: bool, mutable: bool) -> Result<(), SpaceErr> {
380 let def = PropertyDef::new(
381 Box::new(PointPattern {}),
382 required,
383 mutable,
384 PropertySource::Shell,
385 None,
386 false,
387 vec![],
388 )?;
389 self.properties.insert(name.to_string(), def);
390 Ok(())
391 }
392}