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#[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
439pub 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, Nexus, Maelstrom, Scribe, Jump, Fold, Machine, }
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#[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}