Skip to main content

cosmic_space/particle/
property.rs

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}