1use anyhow::{bail, Result};
2use chrono::{DateTime, Utc};
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5use strum::{Display, EnumString};
6
7#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
8#[cfg_attr(feature = "k8s", derive(::kube::CustomResource))]
9#[cfg_attr(
10 feature = "k8s",
11 kube(
12 group = "cdl.ulagbulag.io",
13 version = "v1alpha1",
14 kind = "Model",
15 root = "ModelCrd",
16 status = "ModelStatus",
17 shortname = "m",
18 namespaced,
19 printcolumn = r#"{
20 "name": "state",
21 "type": "string",
22 "description": "state of the model",
23 "jsonPath": ".status.state"
24 }"#,
25 printcolumn = r#"{
26 "name": "created-at",
27 "type": "date",
28 "description": "created time",
29 "jsonPath": ".metadata.creationTimestamp"
30 }"#,
31 printcolumn = r#"{
32 "name": "updated-at",
33 "type": "date",
34 "description": "updated time",
35 "jsonPath": ".status.lastUpdated"
36 }"#,
37 printcolumn = r#"{
38 "name": "version",
39 "type": "integer",
40 "description": "model version",
41 "jsonPath": ".metadata.generation"
42 }"#,
43 )
44)]
45#[serde(rename_all = "camelCase")]
46pub enum ModelSpec {
47 Dynamic {},
48 Fields(ModelFieldsSpec),
49 CustomResourceDefinitionRef(ModelCustomResourceDefinitionRefSpec),
50}
51
52impl Default for ModelSpec {
53 fn default() -> Self {
54 Self::Dynamic {}
55 }
56}
57
58#[cfg(feature = "k8s")]
59impl ModelCrd {
60 pub const FINALIZER_NAME: &'static str = "cdl.ulagbulag.io/finalizer-models";
61
62 pub fn get_fields_unchecked(&self) -> &ModelFieldsNativeSpec {
63 self.status
64 .as_ref()
65 .and_then(|status| status.fields.as_ref())
66 .expect("fields should not be empty")
67 }
68
69 pub fn into_fields_unchecked(self) -> ModelFieldsNativeSpec {
70 self.status
71 .and_then(|status| status.fields)
72 .expect("fields should not be empty")
73 }
74}
75
76#[derive(
77 Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
78)]
79#[serde(rename_all = "camelCase")]
80pub struct ModelStatus {
81 #[serde(default)]
82 pub state: ModelState,
83 pub fields: Option<ModelFieldsSpec<ModelFieldKindNativeSpec>>,
84 pub last_updated: DateTime<Utc>,
85}
86
87pub type ModelFieldsSpec<Kind = ModelFieldKindSpec> = Vec<ModelFieldSpec<Kind>>;
88pub type ModelFieldsNativeSpec = ModelFieldsSpec<ModelFieldKindNativeSpec>;
89
90pub type ModelFieldNativeSpec = ModelFieldSpec<ModelFieldKindNativeSpec>;
91pub type ModelFieldExtendedSpec = ModelFieldSpec<ModelFieldKindExtendedSpec>;
92
93#[derive(
94 Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
95)]
96#[serde(rename_all = "camelCase")]
97pub struct ModelFieldSpec<Kind = ModelFieldKindSpec> {
98 pub name: String,
99 #[serde(flatten)]
100 pub kind: Kind,
101 #[serde(flatten)]
102 pub attribute: ModelFieldAttributeSpec,
103}
104
105impl ModelFieldSpec {
106 pub fn try_into_native(self) -> Result<ModelFieldNativeSpec> {
107 match self.kind {
108 ModelFieldKindSpec::Native(kind) => Ok(ModelFieldSpec {
109 name: self.name,
110 kind,
111 attribute: self.attribute,
112 }),
113 kind => {
114 let name = &self.name;
115 let type_ = kind.to_type();
116 bail!(
117 "cannot infer field type {name:?}: expected Native types, but given {type_:?}"
118 )
119 }
120 }
121 }
122
123 pub fn try_into_extended(self) -> Result<ModelFieldExtendedSpec> {
124 match self.kind {
125 ModelFieldKindSpec::Extended(kind) => Ok(ModelFieldSpec {
126 name: self.name,
127 kind,
128 attribute: self.attribute,
129 }),
130 kind => {
131 let name = &self.name;
132 let type_ = kind.to_type();
133 bail!(
134 "cannot infer field type {name:?}: expected Extended types, but given {type_:?}"
135 )
136 }
137 }
138 }
139}
140
141#[derive(
142 Copy,
143 Clone,
144 Debug,
145 Default,
146 PartialEq,
147 Eq,
148 PartialOrd,
149 Ord,
150 Hash,
151 Serialize,
152 Deserialize,
153 JsonSchema,
154)]
155#[serde(rename_all = "camelCase")]
156pub struct ModelFieldAttributeSpec {
157 #[serde(default)]
158 pub optional: bool,
159}
160
161#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
162pub enum ModelFieldKindSpec {
163 Native(ModelFieldKindNativeSpec),
164 Extended(ModelFieldKindExtendedSpec),
165}
166
167impl Default for ModelFieldKindSpec {
168 fn default() -> Self {
169 Self::Native(Default::default())
170 }
171}
172
173mod _impl_jsonschema_for_model_field_kind_spec {
175 use super::*;
176
177 mod serialize {
178 #[allow(dead_code)]
179 #[derive(super::Serialize)]
180 #[serde(rename_all = "camelCase")]
181 enum ModelFieldKindSpec<'a> {
182 None {},
184 Boolean {
185 #[serde(default)]
186 default: &'a Option<bool>,
187 },
188 Integer {
189 #[serde(default)]
190 default: &'a Option<super::Integer>,
191 #[serde(default)]
192 minimum: &'a Option<super::Integer>,
193 #[serde(default)]
194 maximum: &'a Option<super::Integer>,
195 },
196 Number {
197 #[serde(default)]
198 default: &'a Option<super::Number>,
199 #[serde(default)]
200 minimum: &'a Option<super::Number>,
201 #[serde(default)]
202 maximum: &'a Option<super::Number>,
203 },
204 String {
205 #[serde(default)]
206 default: &'a Option<String>,
207 #[serde(default, flatten)]
208 kind: &'a super::ModelFieldKindStringSpec,
209 },
210 OneOfStrings {
211 #[serde(default)]
212 default: &'a Option<String>,
213 choices: &'a Vec<String>,
214 },
215 DateTime {
217 #[serde(default)]
218 default: &'a Option<super::ModelFieldDateTimeDefaultType>,
219 },
220 Ip {},
221 Uuid {},
222 StringArray {},
224 Object {
225 #[serde(default)]
226 children: &'a Vec<String>,
227 #[serde(default)]
228 kind: &'a super::ModelFieldKindObjectSpec,
229 },
230 ObjectArray {
231 #[serde(default)]
232 children: &'a Vec<String>,
233 },
234 Model {
236 name: &'a String,
237 },
238 }
239
240 impl super::Serialize for super::ModelFieldKindSpec {
241 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
242 where
243 S: ::serde::Serializer,
244 {
245 let spec = match self {
246 Self::Native(spec) => match spec {
247 super::ModelFieldKindNativeSpec::None {} => ModelFieldKindSpec::None {},
249 super::ModelFieldKindNativeSpec::Boolean { default } => {
250 ModelFieldKindSpec::Boolean { default }
251 }
252 super::ModelFieldKindNativeSpec::Integer {
253 default,
254 minimum,
255 maximum,
256 } => ModelFieldKindSpec::Integer {
257 default,
258 minimum,
259 maximum,
260 },
261 super::ModelFieldKindNativeSpec::Number {
262 default,
263 minimum,
264 maximum,
265 } => ModelFieldKindSpec::Number {
266 default,
267 minimum,
268 maximum,
269 },
270 super::ModelFieldKindNativeSpec::String { default, kind } => {
271 ModelFieldKindSpec::String { default, kind }
272 }
273 super::ModelFieldKindNativeSpec::OneOfStrings { default, choices } => {
274 ModelFieldKindSpec::OneOfStrings { default, choices }
275 }
276 super::ModelFieldKindNativeSpec::DateTime { default } => {
278 ModelFieldKindSpec::DateTime { default }
279 }
280 super::ModelFieldKindNativeSpec::Ip {} => ModelFieldKindSpec::Ip {},
281 super::ModelFieldKindNativeSpec::Uuid {} => ModelFieldKindSpec::Uuid {},
282 super::ModelFieldKindNativeSpec::StringArray {} => {
284 ModelFieldKindSpec::StringArray {}
285 }
286 super::ModelFieldKindNativeSpec::Object { children, kind } => {
287 ModelFieldKindSpec::Object { children, kind }
288 }
289 super::ModelFieldKindNativeSpec::ObjectArray { children } => {
290 ModelFieldKindSpec::ObjectArray { children }
291 }
292 },
293 Self::Extended(spec) => match spec {
294 super::ModelFieldKindExtendedSpec::Model { name } => {
296 ModelFieldKindSpec::Model { name }
297 }
298 },
299 };
300
301 spec.serialize(serializer)
302 }
303 }
304 }
305
306 mod deserialize {
307 #[allow(dead_code)]
308 #[derive(super::Deserialize, super::JsonSchema)]
309 #[serde(rename_all = "camelCase")]
310 enum ModelFieldKindSpec {
311 None {},
313 Boolean {
314 #[serde(default)]
315 default: Option<bool>,
316 },
317 Integer {
318 #[serde(default)]
319 default: Option<super::Integer>,
320 #[serde(default)]
321 minimum: Option<super::Integer>,
322 #[serde(default)]
323 maximum: Option<super::Integer>,
324 },
325 Number {
326 #[serde(default)]
327 default: Option<super::Number>,
328 #[serde(default)]
329 minimum: Option<super::Number>,
330 #[serde(default)]
331 maximum: Option<super::Number>,
332 },
333 String {
334 #[serde(default)]
335 default: Option<String>,
336 #[serde(default, flatten)]
337 kind: super::ModelFieldKindStringSpec,
338 },
339 OneOfStrings {
340 #[serde(default)]
341 default: Option<String>,
342 choices: Vec<String>,
343 },
344 DateTime {
346 #[serde(default)]
347 default: Option<super::ModelFieldDateTimeDefaultType>,
348 },
349 Ip {},
350 Uuid {},
351 Object {
353 #[serde(default)]
354 children: Vec<String>,
355 #[serde(default)]
356 kind: super::ModelFieldKindObjectSpec,
357 },
358 ObjectArray {
359 #[serde(default)]
360 children: Vec<String>,
361 },
362 Model {
364 name: String,
365 },
366 }
367
368 impl<'de> super::Deserialize<'de> for super::ModelFieldKindSpec {
369 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
370 where
371 D: ::serde::Deserializer<'de>,
372 {
373 <ModelFieldKindSpec as super::Deserialize<'de>>::deserialize(deserializer).map(
374 |spec| {
375 match spec {
376 ModelFieldKindSpec::None {} => {
378 Self::Native(super::ModelFieldKindNativeSpec::None {})
379 }
380 ModelFieldKindSpec::Boolean { default } => {
381 Self::Native(super::ModelFieldKindNativeSpec::Boolean { default })
382 }
383 ModelFieldKindSpec::Integer {
384 default,
385 minimum,
386 maximum,
387 } => Self::Native(super::ModelFieldKindNativeSpec::Integer {
388 default,
389 minimum,
390 maximum,
391 }),
392 ModelFieldKindSpec::Number {
393 default,
394 minimum,
395 maximum,
396 } => Self::Native(super::ModelFieldKindNativeSpec::Number {
397 default,
398 minimum,
399 maximum,
400 }),
401 ModelFieldKindSpec::String { default, kind } => {
402 Self::Native(super::ModelFieldKindNativeSpec::String {
403 default,
404 kind,
405 })
406 }
407 ModelFieldKindSpec::OneOfStrings { default, choices } => {
408 Self::Native(super::ModelFieldKindNativeSpec::OneOfStrings {
409 default,
410 choices,
411 })
412 }
413 ModelFieldKindSpec::DateTime { default } => {
415 Self::Native(super::ModelFieldKindNativeSpec::DateTime { default })
416 }
417 ModelFieldKindSpec::Ip {} => {
418 Self::Native(super::ModelFieldKindNativeSpec::Ip {})
419 }
420 ModelFieldKindSpec::Uuid {} => {
421 Self::Native(super::ModelFieldKindNativeSpec::Uuid {})
422 }
423 ModelFieldKindSpec::Object { children, kind } => {
425 Self::Native(super::ModelFieldKindNativeSpec::Object {
426 children,
427 kind,
428 })
429 }
430 ModelFieldKindSpec::ObjectArray { children } => {
431 Self::Native(super::ModelFieldKindNativeSpec::ObjectArray {
432 children,
433 })
434 }
435 ModelFieldKindSpec::Model { name } => {
437 Self::Extended(super::ModelFieldKindExtendedSpec::Model { name })
438 }
439 }
440 },
441 )
442 }
443 }
444
445 impl super::JsonSchema for super::ModelFieldKindSpec {
446 fn is_referenceable() -> bool {
447 <ModelFieldKindSpec as super::JsonSchema>::is_referenceable()
448 }
449
450 fn schema_name() -> String {
451 <ModelFieldKindSpec as super::JsonSchema>::schema_name()
452 }
453
454 fn json_schema(
455 gen: &mut ::schemars::gen::SchemaGenerator,
456 ) -> ::schemars::schema::Schema {
457 <ModelFieldKindSpec as super::JsonSchema>::json_schema(gen)
458 }
459
460 fn _schemars_private_non_optional_json_schema(
461 gen: &mut ::schemars::gen::SchemaGenerator,
462 ) -> ::schemars::schema::Schema {
463 <ModelFieldKindSpec as super::JsonSchema>::_schemars_private_non_optional_json_schema(gen)
464 }
465
466 fn _schemars_private_is_option() -> bool {
467 <ModelFieldKindSpec as super::JsonSchema>::_schemars_private_is_option()
468 }
469 }
470 }
471}
472
473impl ModelFieldKindSpec {
474 pub fn get_children(&self) -> Option<&Vec<String>> {
475 match self {
476 Self::Native(spec) => spec.get_children(),
477 Self::Extended(spec) => spec.get_children(),
478 }
479 }
480
481 pub fn get_children_mut(&mut self) -> Option<&mut Vec<String>> {
482 match self {
483 Self::Native(spec) => spec.get_children_mut(),
484 Self::Extended(spec) => spec.get_children_mut(),
485 }
486 }
487
488 pub const fn to_type(&self) -> ModelFieldKindType {
489 match self {
490 Self::Native(spec) => ModelFieldKindType::Native(spec.to_type()),
491 Self::Extended(spec) => ModelFieldKindType::Extended(spec.to_type()),
492 }
493 }
494}
495
496#[derive(
497 Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
498)]
499#[serde(rename_all = "camelCase")]
500pub enum ModelFieldKindNativeSpec {
501 None {},
503 Boolean {
504 #[serde(default)]
505 default: Option<bool>,
506 },
507 Integer {
508 #[serde(default)]
509 default: Option<Integer>,
510 #[serde(default)]
511 minimum: Option<Integer>,
512 #[serde(default)]
513 maximum: Option<Integer>,
514 },
515 Number {
516 #[serde(default)]
517 default: Option<Number>,
518 #[serde(default)]
519 minimum: Option<Number>,
520 #[serde(default)]
521 maximum: Option<Number>,
522 },
523 String {
524 #[serde(default)]
525 default: Option<String>,
526 #[serde(default, flatten)]
527 kind: ModelFieldKindStringSpec,
528 },
529 OneOfStrings {
530 #[serde(default)]
531 default: Option<String>,
532 choices: Vec<String>,
533 },
534 DateTime {
536 #[serde(default)]
537 default: Option<ModelFieldDateTimeDefaultType>,
538 },
539 Ip {},
540 Uuid {},
541 StringArray {},
543 Object {
544 #[serde(default)]
545 children: Vec<String>,
546 #[serde(default)]
547 kind: ModelFieldKindObjectSpec,
548 },
549 ObjectArray {
550 #[serde(default)]
551 children: Vec<String>,
552 },
553}
554
555impl Default for ModelFieldKindNativeSpec {
556 fn default() -> Self {
557 Self::None {}
558 }
559}
560
561impl ModelFieldKindNativeSpec {
562 pub fn get_children(&self) -> Option<&Vec<String>> {
563 match self {
564 Self::None {} => None,
566 Self::Boolean { .. } => None,
567 Self::Integer { .. } => None,
568 Self::Number { .. } => None,
569 Self::String { .. } => None,
570 Self::OneOfStrings { .. } => None,
571 Self::DateTime { .. } => None,
573 Self::Ip { .. } => None,
574 Self::Uuid { .. } => None,
575 Self::StringArray { .. } => None,
577 Self::Object { children, .. } | Self::ObjectArray { children, .. } => Some(children),
578 }
579 }
580
581 pub fn get_children_mut(&mut self) -> Option<&mut Vec<String>> {
582 match self {
583 Self::None {} => None,
585 Self::Boolean { .. } => None,
586 Self::Integer { .. } => None,
587 Self::Number { .. } => None,
588 Self::String { .. } => None,
589 Self::OneOfStrings { .. } => None,
590 Self::DateTime { .. } => None,
592 Self::Ip { .. } => None,
593 Self::Uuid { .. } => None,
594 Self::StringArray { .. } => None,
596 Self::Object { children, .. } | Self::ObjectArray { children, .. } => Some(children),
597 }
598 }
599
600 pub const fn is_array(&self) -> bool {
601 self.to_type().is_array()
602 }
603
604 pub const fn to_type(&self) -> ModelFieldKindNativeType {
605 match self {
606 Self::None {} => ModelFieldKindNativeType::None,
608 Self::Boolean { .. } => ModelFieldKindNativeType::Boolean,
609 Self::Integer { .. } => ModelFieldKindNativeType::Integer,
610 Self::Number { .. } => ModelFieldKindNativeType::Number,
611 Self::String { .. } => ModelFieldKindNativeType::String,
612 Self::OneOfStrings { .. } => ModelFieldKindNativeType::OneOfStrings,
613 Self::DateTime { .. } => ModelFieldKindNativeType::DateTime,
615 Self::Ip { .. } => ModelFieldKindNativeType::Ip,
616 Self::Uuid { .. } => ModelFieldKindNativeType::Uuid,
617 Self::StringArray { .. } => ModelFieldKindNativeType::StringArray,
619 Self::Object { .. } => ModelFieldKindNativeType::Object,
620 Self::ObjectArray { .. } => ModelFieldKindNativeType::ObjectArray,
621 }
622 }
623}
624
625#[derive(
626 Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
627)]
628#[serde(rename_all = "camelCase")]
629pub enum ModelFieldKindStringSpec {
630 Dynamic {},
631 Static {
632 length: u32,
633 },
634 Range {
635 #[serde(default)]
636 minimum: Option<u32>,
637 maximum: u32,
638 },
639}
640
641impl Default for ModelFieldKindStringSpec {
642 fn default() -> Self {
643 Self::Dynamic {}
644 }
645}
646
647#[derive(
648 Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
649)]
650#[serde(rename_all = "camelCase")]
651pub enum ModelFieldKindObjectSpec {
652 Dynamic {},
653 Enumerate { choices: Vec<String> },
654 Static {},
655}
656
657impl Default for ModelFieldKindObjectSpec {
658 fn default() -> Self {
659 Self::Static {}
660 }
661}
662
663#[derive(
664 Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
665)]
666#[serde(rename_all = "camelCase")]
667pub enum ModelFieldKindExtendedSpec {
668 Model { name: String },
670}
671
672impl ModelFieldKindExtendedSpec {
673 pub fn get_children(&self) -> Option<&Vec<String>> {
674 None
675 }
676
677 pub fn get_children_mut(&mut self) -> Option<&mut Vec<String>> {
678 None
679 }
680
681 pub const fn to_type(&self) -> ModelFieldKindExtendedType {
682 match self {
683 Self::Model { .. } => ModelFieldKindExtendedType::Model,
685 }
686 }
687}
688
689#[derive(
690 Copy,
691 Clone,
692 Debug,
693 Display,
694 PartialEq,
695 Eq,
696 PartialOrd,
697 Ord,
698 Hash,
699 Serialize,
700 Deserialize,
701 JsonSchema,
702)]
703#[serde(untagged)]
704pub enum ModelFieldKindType {
705 Native(ModelFieldKindNativeType),
706 Extended(ModelFieldKindExtendedType),
707}
708
709#[derive(
710 Copy,
711 Clone,
712 Debug,
713 Display,
714 EnumString,
715 PartialEq,
716 Eq,
717 PartialOrd,
718 Ord,
719 Hash,
720 Serialize,
721 Deserialize,
722 JsonSchema,
723)]
724pub enum ModelFieldKindNativeType {
725 None,
727 Boolean,
728 Integer,
729 Number,
730 String,
731 OneOfStrings,
732 DateTime,
734 Ip,
735 Uuid,
736 StringArray,
738 Object,
739 ObjectArray,
740}
741
742impl ModelFieldKindNativeType {
743 pub const fn is_array(&self) -> bool {
744 matches!(self, Self::StringArray | Self::ObjectArray)
746 }
747
748 pub const fn to_natural(&self) -> &'static str {
749 match self {
750 Self::None => "None",
752 Self::Boolean => "Boolean",
753 Self::Integer => "Integer",
754 Self::Number => "Number",
755 Self::String | Self::OneOfStrings => "String",
756 Self::DateTime => "DateTime",
758 Self::Ip => "Ip",
759 Self::Uuid => "Uuid",
760 Self::StringArray => "String[]",
762 Self::Object => "Object",
763 Self::ObjectArray => "Object[]",
764 }
765 }
766}
767
768#[derive(
769 Copy,
770 Clone,
771 Debug,
772 Display,
773 EnumString,
774 PartialEq,
775 Eq,
776 PartialOrd,
777 Ord,
778 Hash,
779 Serialize,
780 Deserialize,
781 JsonSchema,
782)]
783pub enum ModelFieldKindExtendedType {
784 Model,
786}
787
788#[derive(
789 Copy,
790 Clone,
791 Debug,
792 Display,
793 EnumString,
794 PartialEq,
795 Eq,
796 PartialOrd,
797 Ord,
798 Hash,
799 Serialize,
800 Deserialize,
801 JsonSchema,
802)]
803pub enum ModelFieldDateTimeDefaultType {
804 Now,
805}
806
807#[derive(
808 Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
809)]
810#[serde(rename_all = "camelCase")]
811pub struct ModelCustomResourceDefinitionRefSpec {
812 pub name: String,
813}
814
815impl ModelCustomResourceDefinitionRefSpec {
816 pub fn plural(&self) -> &str {
817 self.name.split('.').next().unwrap()
818 }
819}
820
821#[derive(
822 Copy,
823 Clone,
824 Debug,
825 Display,
826 Default,
827 EnumString,
828 PartialEq,
829 Eq,
830 PartialOrd,
831 Ord,
832 Hash,
833 Serialize,
834 Deserialize,
835 JsonSchema,
836)]
837pub enum ModelState {
838 #[default]
839 Pending,
840 Ready,
841 Deleting,
842}
843
844pub type Integer = i64;
845
846pub type Number = ::ordered_float::OrderedFloat<f64>;