cosmic_space/
kind.rs

1use core::str::FromStr;
2
3use convert_case::{Case, Casing};
4use nom::combinator::all_consuming;
5use serde::{Deserialize, Serialize};
6
7use cosmic_nom::new_span;
8
9use crate::hyper::ChildRegistry;
10use crate::loc::{
11    ProvisionAffinity, StarKey, ToBaseKind, Version, CONTROL_WAVE_TRAVERSAL_PLAN,
12    MECHTRON_WAVE_TRAVERSAL_PLAN, PORTAL_WAVE_TRAVERSAL_PLAN, STAR_WAVE_TRAVERSAL_PLAN,
13    STD_WAVE_TRAVERSAL_PLAN,
14};
15use crate::parse::error::result;
16use crate::parse::{kind_parts, specific, CamelCase, Domain, SkewerCase};
17use crate::particle::traversal::TraversalPlan;
18use crate::selector::{
19    KindSelector, KindSelectorDef, Pattern, SpecificSelector, SubKindSelector, VersionReq,
20};
21use crate::util::ValuePattern;
22use crate::{KindTemplate, SpaceErr};
23
24impl ToBaseKind for KindParts {
25    fn to_base(&self) -> BaseKind {
26        self.base.clone()
27    }
28}
29
30impl Tks for KindParts {
31    fn base(&self) -> BaseKind {
32        self.base.clone()
33    }
34
35    fn sub(&self) -> Option<CamelCase> {
36        self.sub.clone()
37    }
38
39    fn specific(&self) -> Option<Specific> {
40        self.specific.clone()
41    }
42
43    fn matches(&self, tks: &dyn Tks) -> bool {
44        self.base == tks.base() && self.sub == tks.sub() && self.specific == tks.specific()
45    }
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
49pub struct KindParts {
50    pub base: BaseKind,
51    pub sub: Option<CamelCase>,
52    pub specific: Option<Specific>,
53}
54
55impl KindParts {
56    pub fn root() -> Self {
57        Self {
58            base: BaseKind::Root,
59            sub: None,
60            specific: None,
61        }
62    }
63}
64
65impl ToString for KindParts {
66    fn to_string(&self) -> String {
67        if self.sub.is_some() && self.specific.is_some() {
68            format!(
69                "{}<{}<{}>>",
70                self.base.to_string(),
71                self.sub.as_ref().expect("sub").to_string(),
72                self.specific.as_ref().expect("specific").to_string()
73            )
74        } else if self.sub.is_some() {
75            format!(
76                "{}<{}>",
77                self.base.to_string(),
78                self.sub.as_ref().expect("sub").to_string()
79            )
80        } else {
81            self.base.to_string()
82        }
83    }
84}
85
86impl FromStr for KindParts {
87    type Err = SpaceErr;
88
89    fn from_str(s: &str) -> Result<Self, Self::Err> {
90        let (_, kind) = all_consuming(kind_parts)(new_span(s))?;
91
92        Ok(kind)
93    }
94}
95
96impl KindParts {
97    pub fn new(kind: BaseKind, sub: Option<CamelCase>, specific: Option<Specific>) -> Self {
98        Self {
99            base: kind,
100            sub,
101            specific,
102        }
103    }
104}
105
106#[derive(
107    Debug,
108    Clone,
109    Serialize,
110    Deserialize,
111    Eq,
112    PartialEq,
113    Hash,
114    strum_macros::Display,
115    strum_macros::EnumString,
116)]
117pub enum BaseKind {
118    Root,
119    Space,
120    UserBase,
121    Base,
122    User,
123    App,
124    Mechtron,
125    FileSystem,
126    File,
127    Database,
128    Repo,
129    BundleSeries,
130    Bundle,
131    Artifact,
132    Control,
133    Portal,
134    Star,
135    Driver,
136    Global,
137    Host,
138    Guest,
139    Native,
140}
141
142impl BaseKind {
143    pub fn to_skewer(&self) -> SkewerCase {
144        SkewerCase::from_str(self.to_string().to_case(Case::Kebab).as_str()).unwrap()
145    }
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash, strum_macros::Display)]
149pub enum Sub {
150    None,
151    Database(DatabaseSubKind),
152    File(FileSubKind),
153    Artifact(ArtifactSubKind),
154    UserBase(UserBaseSubKind),
155    Star(StarSub),
156    Native(NativeSub),
157}
158
159impl Sub {
160    pub fn to_camel_case(&self) -> Option<CamelCase> {
161        match self {
162            Sub::None => None,
163            Sub::Database(d) => Some(CamelCase::from_str(d.to_string().as_str()).unwrap()),
164            Sub::File(x) => Some(CamelCase::from_str(x.to_string().as_str()).unwrap()),
165            Sub::Artifact(x) => Some(CamelCase::from_str(x.to_string().as_str()).unwrap()),
166            Sub::UserBase(x) => Some(CamelCase::from_str(x.to_string().as_str()).unwrap()),
167            Sub::Star(x) => Some(CamelCase::from_str(x.to_string().as_str()).unwrap()),
168            Sub::Native(x) => Some(CamelCase::from_str(x.to_string().as_str()).unwrap()),
169        }
170    }
171
172    pub fn specific(&self) -> Option<&Specific> {
173        match self {
174            Sub::Database(sub) => sub.specific(),
175            Sub::UserBase(sub) => sub.specific(),
176            _ => None,
177        }
178    }
179}
180
181impl Sub {
182    pub fn to_skewer(&self) -> SkewerCase {
183        SkewerCase::from_str(self.to_string().to_case(Case::Kebab).as_str()).unwrap()
184    }
185}
186
187impl Into<Option<CamelCase>> for Sub {
188    fn into(self) -> Option<CamelCase> {
189        match self {
190            Sub::None => None,
191            Sub::Database(d) => d.into(),
192            Sub::File(f) => f.into(),
193            Sub::Artifact(a) => a.into(),
194            Sub::UserBase(u) => u.into(),
195            Sub::Star(s) => s.into(),
196            Sub::Native(s) => s.into(),
197        }
198    }
199}
200
201impl Into<Option<String>> for Sub {
202    fn into(self) -> Option<String> {
203        match self {
204            Sub::None => None,
205            Sub::Database(d) => d.into(),
206            Sub::File(f) => f.into(),
207            Sub::Artifact(a) => a.into(),
208            Sub::UserBase(u) => u.into(),
209            Sub::Star(s) => s.into(),
210            Sub::Native(s) => s.into(),
211        }
212    }
213}
214
215impl ToBaseKind for BaseKind {
216    fn to_base(&self) -> BaseKind {
217        self.clone()
218    }
219}
220
221impl TryFrom<CamelCase> for BaseKind {
222    type Error = SpaceErr;
223
224    fn try_from(base: CamelCase) -> Result<Self, Self::Error> {
225        Ok(BaseKind::from_str(base.as_str())?)
226    }
227}
228
229/// Kind defines the behavior and properties of a Particle.  Each particle has a Kind.
230/// At minimum a Kind must have a BaseKind, it can also have a SubKind and a Specific.
231/// A Particle's complete Kind definition is used to match it with a Driver in the Hyperverse
232#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash, strum_macros::Display)]
233pub enum Kind {
234    Root,
235    Space,
236    User,
237    App,
238    Mechtron,
239    FileSystem,
240    Repo,
241    BundleSeries,
242    Bundle,
243    Control,
244    Portal,
245    Driver,
246    File(FileSubKind),
247    Artifact(ArtifactSubKind),
248    Database(DatabaseSubKind),
249    Base,
250    UserBase(UserBaseSubKind),
251    Star(StarSub),
252    Global,
253    Host,
254    Guest,
255    Native(NativeSub),
256}
257
258impl ToBaseKind for Kind {
259    fn to_base(&self) -> BaseKind {
260        match self {
261            Kind::Root => BaseKind::Root,
262            Kind::Space => BaseKind::Space,
263            Kind::User => BaseKind::User,
264            Kind::App => BaseKind::App,
265            Kind::Mechtron => BaseKind::Mechtron,
266            Kind::FileSystem => BaseKind::FileSystem,
267            Kind::BundleSeries => BaseKind::BundleSeries,
268            Kind::Bundle => BaseKind::Bundle,
269            Kind::Control => BaseKind::Control,
270            Kind::Portal => BaseKind::Portal,
271            Kind::UserBase(_) => BaseKind::UserBase,
272            Kind::File(_) => BaseKind::File,
273            Kind::Artifact(_) => BaseKind::Artifact,
274            Kind::Database(_) => BaseKind::Database,
275            Kind::Native(_) => BaseKind::Native,
276            Kind::Base => BaseKind::Base,
277            Kind::Repo => BaseKind::Repo,
278            Kind::Star(_) => BaseKind::Star,
279            Kind::Driver => BaseKind::Driver,
280            Kind::Global => BaseKind::Global,
281            Kind::Host => BaseKind::Host,
282            Kind::Guest => BaseKind::Guest,
283        }
284    }
285}
286
287impl Kind {
288    pub fn to_template(&self) -> KindTemplate {
289        KindTemplate {
290            base: self.to_base(),
291            sub: self.sub().to_camel_case(),
292            specific: self.specific_selector(),
293        }
294    }
295
296    pub fn provision_affinity(&self) -> ProvisionAffinity {
297        match self.to_base() {
298            BaseKind::Base => ProvisionAffinity::Local,
299            _ => ProvisionAffinity::Wrangle,
300        }
301    }
302
303    pub fn is_auto_provision(&self) -> bool {
304        match self {
305            Kind::Bundle => true,
306            Kind::Artifact(_) => true,
307            Kind::Mechtron => true,
308            Kind::Host => true,
309            Kind::Native(NativeSub::Web) => true,
310            _ => false,
311        }
312    }
313
314    pub fn as_point_segments(&self) -> String {
315        if Sub::None != self.sub() {
316            if let Some(specific) = self.specific() {
317                format!(
318                    "{}:{}:{}",
319                    self.to_base().to_skewer().to_string(),
320                    self.sub().to_skewer().to_string(),
321                    specific.to_string()
322                )
323            } else {
324                format!(
325                    "{}:{}",
326                    self.to_base().to_skewer().to_string(),
327                    self.sub().to_skewer().to_string()
328                )
329            }
330        } else {
331            format!("{}", self.to_base().to_skewer().to_string())
332        }
333    }
334
335    pub fn sub(&self) -> Sub {
336        match self {
337            Kind::File(s) => s.clone().into(),
338            Kind::Artifact(s) => s.clone().into(),
339            Kind::Database(s) => s.clone().into(),
340            Kind::UserBase(s) => s.clone().into(),
341            Kind::Star(s) => s.clone().into(),
342            _ => Sub::None,
343        }
344    }
345
346    pub fn specific(&self) -> Option<Specific> {
347        let sub = self.sub();
348        sub.specific().cloned()
349    }
350
351    pub fn specific_selector(&self) -> Option<SpecificSelector> {
352        match self.specific() {
353            None => None,
354            Some(specific) => Some(specific.to_selector()),
355        }
356    }
357
358    pub fn wave_traversal_plan(&self) -> &TraversalPlan {
359        match self {
360            Kind::Mechtron => &MECHTRON_WAVE_TRAVERSAL_PLAN,
361            Kind::Portal => &PORTAL_WAVE_TRAVERSAL_PLAN,
362            Kind::Control => &CONTROL_WAVE_TRAVERSAL_PLAN,
363            Kind::Star(_) => &STAR_WAVE_TRAVERSAL_PLAN,
364            _ => &STD_WAVE_TRAVERSAL_PLAN,
365        }
366    }
367}
368
369impl TryFrom<KindParts> for Kind {
370    type Error = SpaceErr;
371
372    fn try_from(value: KindParts) -> Result<Self, Self::Error> {
373        Ok(match value.base {
374            BaseKind::Database => {
375                match value.sub.ok_or("Database<?> requires a Sub Kind")?.as_str() {
376                    "Relational" => Kind::Database(DatabaseSubKind::Relational(
377                        value
378                            .specific
379                            .ok_or("Database<Relational<?>> requires a Specific")?,
380                    )),
381                    what => {
382                        return Err(SpaceErr::from(format!(
383                            "unexpected Database SubKind '{}'",
384                            what
385                        )));
386                    }
387                }
388            }
389            BaseKind::UserBase => {
390                match value.sub.ok_or("UserBase<?> requires a Sub Kind")?.as_str() {
391                    "OAuth" => Kind::UserBase(UserBaseSubKind::OAuth(
392                        value
393                            .specific
394                            .ok_or("UserBase<OAuth<?>> requires a Specific")?,
395                    )),
396                    what => {
397                        return Err(SpaceErr::from(format!(
398                            "unexpected Database SubKind '{}'",
399                            what
400                        )));
401                    }
402                }
403            }
404            BaseKind::Base => Kind::Base,
405            BaseKind::File => Kind::File(FileSubKind::from_str(
406                value.sub.ok_or("File<?> requires a Sub Kind")?.as_str(),
407            )?),
408            BaseKind::Artifact => Kind::Artifact(ArtifactSubKind::from_str(
409                value.sub.ok_or("Artifact<?> requires a sub kind")?.as_str(),
410            )?),
411
412            BaseKind::Star => Kind::Star(StarSub::from_str(
413                value.sub.ok_or("Star<?> requires a sub kind")?.as_str(),
414            )?),
415            BaseKind::Native => Kind::Native(NativeSub::from_str(
416                value.sub.ok_or("Native<?> requires a sub kind")?.as_str(),
417            )?),
418
419            BaseKind::Root => Kind::Root,
420            BaseKind::Space => Kind::Space,
421            BaseKind::User => Kind::User,
422            BaseKind::App => Kind::App,
423            BaseKind::Mechtron => Kind::Mechtron,
424            BaseKind::FileSystem => Kind::FileSystem,
425
426            BaseKind::BundleSeries => Kind::BundleSeries,
427            BaseKind::Bundle => Kind::Bundle,
428            BaseKind::Control => Kind::Control,
429            BaseKind::Portal => Kind::Portal,
430            BaseKind::Repo => Kind::Repo,
431            BaseKind::Driver => Kind::Driver,
432            BaseKind::Global => Kind::Global,
433            BaseKind::Host => Kind::Host,
434            BaseKind::Guest => Kind::Guest,
435        })
436    }
437}
438
439/// Stands for "Type, Kind, Specific"
440pub trait Tks {
441    fn base(&self) -> BaseKind;
442    fn sub(&self) -> Option<CamelCase>;
443    fn specific(&self) -> Option<Specific>;
444    fn matches(&self, tks: &dyn Tks) -> bool;
445}
446
447#[derive(
448    Clone,
449    Debug,
450    Eq,
451    PartialEq,
452    Hash,
453    Serialize,
454    Deserialize,
455    strum_macros::Display,
456    strum_macros::EnumString,
457)]
458pub enum NativeSub {
459    Web,
460}
461
462#[derive(
463    Clone,
464    Debug,
465    Eq,
466    PartialEq,
467    Hash,
468    Serialize,
469    Deserialize,
470    strum_macros::Display,
471    strum_macros::EnumString,
472)]
473pub enum StarSub {
474    Central,
475    Super, // Wrangles nearby Stars... manages Assigning Particles to Stars, Moving, Icing, etc.
476    Nexus, // Relays Waves from Star to Star
477    Maelstrom, // Where executables are run
478    Scribe, // requires durable filesystem (Artifact Bundles, Files...)
479    Jump, // for entry into the Mesh/Fabric for an external connection (client ingress... http for example)
480    Fold, // exit from the Mesh.. maintains connections etc to Databases, Keycloak, etc.... Like A Space Fold out of the Fabric..
481    Machine, // every Machine has one and only one Machine star... it handles messaging for the Machine
482}
483
484impl StarSub {
485    pub fn to_selector(&self) -> KindSelector {
486        KindSelector {
487            base: Pattern::Exact(BaseKind::Star),
488            sub: SubKindSelector::Exact(Some(self.to_camel_case())),
489            specific: ValuePattern::Any,
490        }
491    }
492
493    pub fn to_camel_case(&self) -> CamelCase {
494        CamelCase::from_str(self.to_string().as_str()).unwrap()
495    }
496
497    pub fn is_forwarder(&self) -> bool {
498        match self {
499            StarSub::Nexus => true,
500            StarSub::Central => false,
501            StarSub::Super => true,
502            StarSub::Maelstrom => true,
503            StarSub::Scribe => true,
504            StarSub::Jump => true,
505            StarSub::Fold => true,
506            StarSub::Machine => false,
507        }
508    }
509
510    pub fn can_be_wrangled(&self) -> bool {
511        match self {
512            StarSub::Nexus => false,
513            StarSub::Machine => false,
514            _ => true,
515        }
516    }
517}
518
519impl Into<Sub> for NativeSub {
520    fn into(self) -> Sub {
521        Sub::Native(self)
522    }
523}
524
525impl Into<Option<CamelCase>> for NativeSub {
526    fn into(self) -> Option<CamelCase> {
527        Some(CamelCase::from_str(self.to_string().as_str()).unwrap())
528    }
529}
530
531impl Into<Option<String>> for NativeSub {
532    fn into(self) -> Option<String> {
533        Some(self.to_string())
534    }
535}
536
537impl Into<Sub> for StarSub {
538    fn into(self) -> Sub {
539        Sub::Star(self)
540    }
541}
542
543impl Into<Option<CamelCase>> for StarSub {
544    fn into(self) -> Option<CamelCase> {
545        Some(CamelCase::from_str(self.to_string().as_str()).unwrap())
546    }
547}
548
549impl Into<Option<String>> for StarSub {
550    fn into(self) -> Option<String> {
551        Some(self.to_string())
552    }
553}
554
555#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, strum_macros::Display)]
556pub enum UserBaseSubKind {
557    OAuth(Specific),
558}
559
560impl UserBaseSubKind {
561    pub fn specific(&self) -> Option<&Specific> {
562        match self {
563            UserBaseSubKind::OAuth(specific) => Option::Some(specific),
564        }
565    }
566}
567
568impl Into<Sub> for UserBaseSubKind {
569    fn into(self) -> Sub {
570        Sub::UserBase(self)
571    }
572}
573
574impl Into<Option<CamelCase>> for UserBaseSubKind {
575    fn into(self) -> Option<CamelCase> {
576        Some(CamelCase::from_str(self.to_string().as_str()).unwrap())
577    }
578}
579
580impl Into<Option<String>> for UserBaseSubKind {
581    fn into(self) -> Option<String> {
582        Some(self.to_string())
583    }
584}
585
586#[derive(
587    Clone,
588    Debug,
589    Eq,
590    PartialEq,
591    Hash,
592    Serialize,
593    Deserialize,
594    strum_macros::Display,
595    strum_macros::EnumString,
596)]
597pub enum FileSubKind {
598    File,
599    Dir,
600}
601
602impl Into<Sub> for FileSubKind {
603    fn into(self) -> Sub {
604        Sub::File(self)
605    }
606}
607
608impl Into<Option<CamelCase>> for FileSubKind {
609    fn into(self) -> Option<CamelCase> {
610        Some(CamelCase::from_str(self.to_string().as_str()).unwrap())
611    }
612}
613
614impl Into<Option<String>> for FileSubKind {
615    fn into(self) -> Option<String> {
616        Some(self.to_string())
617    }
618}
619
620#[derive(
621    Clone,
622    Debug,
623    Eq,
624    PartialEq,
625    Hash,
626    Serialize,
627    Deserialize,
628    strum_macros::Display,
629    strum_macros::EnumString,
630)]
631pub enum ArtifactSubKind {
632    Raw,
633    ParticleConfig,
634    Bind,
635    Wasm,
636    Dir,
637}
638
639impl Into<Sub> for ArtifactSubKind {
640    fn into(self) -> Sub {
641        Sub::Artifact(self)
642    }
643}
644
645impl Into<Option<CamelCase>> for ArtifactSubKind {
646    fn into(self) -> Option<CamelCase> {
647        Some(CamelCase::from_str(self.to_string().as_str()).unwrap())
648    }
649}
650
651impl Into<Option<String>> for ArtifactSubKind {
652    fn into(self) -> Option<String> {
653        Some(self.to_string())
654    }
655}
656
657#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, strum_macros::Display)]
658pub enum DatabaseSubKind {
659    Relational(Specific),
660}
661
662impl DatabaseSubKind {
663    pub fn specific(&self) -> Option<&Specific> {
664        match self {
665            DatabaseSubKind::Relational(specific) => Some(specific),
666        }
667    }
668}
669
670impl Into<Sub> for DatabaseSubKind {
671    fn into(self) -> Sub {
672        Sub::Database(self)
673    }
674}
675
676impl Into<Option<CamelCase>> for DatabaseSubKind {
677    fn into(self) -> Option<CamelCase> {
678        Some(CamelCase::from_str(self.to_string().as_str()).unwrap())
679    }
680}
681
682impl Into<Option<String>> for DatabaseSubKind {
683    fn into(self) -> Option<String> {
684        Some(self.to_string())
685    }
686}
687
688impl BaseKind {
689    pub fn child_resource_registry_handler(&self) -> ChildRegistry {
690        match self {
691            Self::UserBase => ChildRegistry::Core,
692            _ => ChildRegistry::Shell,
693        }
694    }
695}
696
697#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
698pub struct StarStub {
699    pub key: StarKey,
700    pub kind: StarSub,
701}
702
703impl StarStub {
704    pub fn new(key: StarKey, kind: StarSub) -> Self {
705        Self { key, kind }
706    }
707}
708
709/// A Specific is used to extend the Kind system in The Cosmic Initiative to a very exact level.
710/// when a Kind has a specific it is not only referencing something general like a Database,
711/// but the vendor, product and version of that database among other things.
712/// The Specific def looks like this `provider.url:vendor.url:product:variant:version`
713/// * **provider** - this is the domain name of the person or entity that provided the driver
714///                  that this specific defines
715/// * **vendor** - the vendor that provides the product which may have had nothing to do with
716///                creating the driver
717/// * **product** - the product
718/// * **variant** - many products have variation and here it is where it is specificied
719/// * **version** - this is a SemVer describing the exact version of the Specific
720///
721/// ## Example:
722/// `mechtronhub.com:postgres.org:postgres:gis:8.0.0`
723/// And the above would be embedde into the appropriate Base Kind and Sub Kind:
724/// `<Database<Rel<mechtronhub.com:postgres.org:postgres:gis:8.0.0>>>`
725#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Hash)]
726pub struct Specific {
727    pub provider: Domain,
728    pub vendor: Domain,
729    pub product: SkewerCase,
730    pub variant: SkewerCase,
731    pub version: Version,
732}
733
734impl Specific {
735    pub fn to_selector(&self) -> SpecificSelector {
736        SpecificSelector::from_str(self.to_string().as_str()).unwrap()
737    }
738}
739
740impl ToString for Specific {
741    fn to_string(&self) -> String {
742        format!(
743            "{}:{}:{}:{}:{}",
744            self.provider,
745            self.vendor,
746            self.product,
747            self.variant,
748            self.version.to_string()
749        )
750    }
751}
752
753impl FromStr for Specific {
754    type Err = SpaceErr;
755
756    fn from_str(s: &str) -> Result<Self, Self::Err> {
757        result(specific(new_span(s)))
758    }
759}
760
761impl TryInto<SpecificSelector> for Specific {
762    type Error = SpaceErr;
763
764    fn try_into(self) -> Result<SpecificSelector, Self::Error> {
765        Ok(SpecificSelector {
766            provider: Pattern::Exact(self.provider),
767            vendor: Pattern::Exact(self.vendor),
768            product: Pattern::Exact(self.product),
769            variant: Pattern::Exact(self.variant),
770            version: VersionReq::from_str(self.version.to_string().as_str())?,
771        })
772    }
773}
774
775#[cfg(test)]
776pub mod test {
777    use crate::parse::kind_selector;
778    use crate::selector::KindSelector;
779    use crate::{Kind, SpaceErr, StarSub};
780    use core::str::FromStr;
781
782    #[test]
783    pub fn selector() -> Result<(), SpaceErr> {
784        let kind = Kind::Star(StarSub::Fold);
785        let selector = KindSelector::from_str("<Star<Fold>>")?;
786        assert!(selector.matches(&kind));
787        Ok(())
788    }
789}