k8s_openapi_codegen_common/swagger20/
definitions.rs

1/// A definition path.
2#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
3#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
4pub struct DefinitionPath(pub String);
5
6impl std::borrow::Borrow<str> for DefinitionPath {
7    fn borrow(&self) -> &str {
8        &self.0
9    }
10}
11
12impl std::ops::Deref for DefinitionPath {
13    type Target = str;
14
15    fn deref(&self) -> &Self::Target {
16        &self.0
17    }
18}
19
20impl std::fmt::Display for DefinitionPath {
21    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22        self.0.fmt(f)
23    }
24}
25
26/// An integer format. This corresponds to the `"format"` property of an `"integer"` schema type.
27#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
28pub enum IntegerFormat {
29    Int32,
30    Int64,
31}
32
33/// The value of an `x-kubernetes-list-type` annotation on a property.
34#[derive(Clone, Copy, Debug, Eq, PartialEq, Default, PartialOrd, Ord)]
35#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
36pub enum KubernetesListType {
37    #[default]
38    #[cfg_attr(feature = "serde", serde(rename = "atomic"))]
39    Atomic,
40
41    #[cfg_attr(feature = "serde", serde(rename = "map"))]
42    Map,
43
44    #[cfg_attr(feature = "serde", serde(rename = "set"))]
45    Set,
46}
47
48/// The value of an `x-kubernetes-map-type` annotation on a property.
49#[derive(Clone, Copy, Debug, Eq, PartialEq, Default, PartialOrd, Ord)]
50#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
51pub enum KubernetesMapType {
52    #[cfg_attr(feature = "serde", serde(rename = "atomic"))]
53    Atomic,
54
55    #[default]
56    #[cfg_attr(feature = "serde", serde(rename = "granular"))]
57    Granular,
58}
59
60#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
61pub enum MergeType {
62    Default,
63
64    List {
65        strategy: KubernetesListType,
66        keys: Vec<String>,
67        item_merge_type: Box<MergeType>,
68    },
69
70    Map {
71        strategy: KubernetesMapType,
72        value_merge_type: Box<MergeType>,
73    },
74}
75
76/// A number format. This corresponds to the `"format"` property of a `"number"` schema type.
77#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
78pub enum NumberFormat {
79    Double,
80}
81
82/// The name of a property of a schema type with a `"properties"` map.
83#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
84#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
85pub struct PropertyName(pub String);
86
87impl std::borrow::Borrow<str> for PropertyName {
88    fn borrow(&self) -> &str {
89        &self.0
90    }
91}
92
93impl std::ops::Deref for PropertyName {
94    type Target = str;
95
96    fn deref(&self) -> &Self::Target {
97        &self.0
98    }
99}
100
101impl std::fmt::Display for PropertyName {
102    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103        self.0.fmt(f)
104    }
105}
106
107/// The path specified by a `"$ref"` property.
108#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
109pub struct RefPath {
110    pub path: String,
111    pub can_be_default: Option<bool>,
112}
113
114#[cfg(feature = "serde")]
115impl<'de> serde::Deserialize<'de> for RefPath {
116    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> {
117        let path: String = serde::Deserialize::deserialize(deserializer)?;
118        let mut parts = path.split('/');
119
120        if parts.next() != Some("#") {
121            return Err(serde::de::Error::invalid_value(serde::de::Unexpected::Str(&path), &"path like `#/definitions/$definitionName`"));
122        }
123
124        if parts.next() != Some("definitions") {
125            return Err(serde::de::Error::invalid_value(serde::de::Unexpected::Str(&path), &"path like `#/definitions/$definitionName`"));
126        }
127
128        let ref_path = parts.next().ok_or_else(|| serde::de::Error::invalid_value(serde::de::Unexpected::Str(&path), &"path like `#/definitions/$definitionName`"))?;
129
130        if parts.next().is_some() {
131            return Err(serde::de::Error::invalid_value(serde::de::Unexpected::Str(&path), &"path like `#/definitions/$definitionName`"));
132        }
133
134        Ok(RefPath {
135            path: ref_path.to_string(),
136            can_be_default: None,
137        })
138    }
139}
140
141impl RefPath {
142    pub(crate) fn references_scope(&self, map_namespace: &impl crate::MapNamespace) -> bool {
143        let path_parts: Vec<_> = self.path.split('.').collect();
144        map_namespace.map_namespace(&path_parts[..(path_parts.len() - 1)]).is_none()
145    }
146}
147
148/// The schema of a definition or operation parameter.
149#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
150pub struct Schema {
151    pub description: Option<String>,
152    pub kind: SchemaKind,
153    pub kubernetes_group_kind_versions: Vec<super::KubernetesGroupKindVersion>,
154
155    pub merge_type: MergeType,
156
157    /// Used to store the definition path of the corresponding list type, if any.
158    pub list_kind: Option<String>,
159
160    /// Used to enable or disable the auto-generated impl of `k8s_openapi::DeepMerge` on the generated type.
161    pub impl_deep_merge: bool,
162}
163
164#[cfg(feature = "serde")]
165#[allow(clippy::use_self)]
166impl<'de> serde::Deserialize<'de> for Schema {
167    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> {
168        #[derive(Debug, serde::Deserialize)]
169        struct InnerSchema {
170            #[serde(rename = "additionalProperties")]
171            additional_properties: Option<Box<Schema>>,
172
173            description: Option<String>,
174
175            format: Option<String>,
176
177            items: Option<Box<Schema>>,
178
179            #[serde(default, rename = "x-kubernetes-group-version-kind")]
180            kubernetes_group_kind_versions: Vec<super::KubernetesGroupKindVersion>,
181
182            #[serde(default, rename = "x-kubernetes-list-map-keys")]
183            kubernetes_list_map_keys: Vec<String>,
184
185            #[serde(default, rename = "x-kubernetes-list-type")]
186            kubernetes_list_type: KubernetesListType,
187
188            #[serde(default, rename = "x-kubernetes-map-type")]
189            kubernetes_map_type: KubernetesMapType,
190
191            #[serde(default, rename = "x-kubernetes-patch-merge-key")]
192            kubernetes_patch_merge_key: Option<String>,
193
194            /// Comma-separated list of strategy tags.
195            /// Ref: <https://github.com/kubernetes/community/blob/master/contributors/devel/sig-api-machinery/strategic-merge-patch.md>
196            #[serde(default, rename = "x-kubernetes-patch-strategy")]
197            kubernetes_patch_strategy: String,
198
199            properties: Option<std::collections::BTreeMap<PropertyName, Schema>>,
200
201            #[serde(rename = "$ref")]
202            ref_path: Option<RefPath>,
203
204            #[serde(default)]
205            required: Vec<PropertyName>,
206
207            #[serde(rename = "type")]
208            ty: Option<String>,
209        }
210
211        let mut value: InnerSchema = serde::Deserialize::deserialize(deserializer)?;
212
213        let kind =
214            if let Some(ref_path) = value.ref_path {
215                SchemaKind::Ref(ref_path)
216            }
217            else if let Some(properties) = value.properties.take() {
218                if value.ty.as_deref() != Some("object") {
219                    return Err(serde::de::Error::custom(format!("schema has properties but not type=object {value:?}")));
220                }
221
222                let required: std::collections::BTreeSet<_> = value.required.into_iter().collect();
223                SchemaKind::Properties(properties.into_iter().map(|(name, schema)| {
224                    let required = required.contains(&name);
225                    (name, (schema, required))
226                }).collect())
227            }
228            else if let Some(ty) = value.ty {
229                SchemaKind::Ty(Type::parse::<D>(
230                    &ty,
231                    value.additional_properties,
232                    value.format.as_deref(),
233                    value.items,
234                )?)
235            }
236            else {
237                SchemaKind::Ty(Type::Any)
238            };
239
240        if let Some(key) = value.kubernetes_patch_merge_key {
241            value.kubernetes_list_map_keys = vec![key];
242        }
243        if value.kubernetes_patch_strategy.split(',').any(|x| x == "merge") {
244            value.kubernetes_list_type =
245                if value.kubernetes_list_map_keys.is_empty() {
246                    KubernetesListType::Set
247                }
248                else {
249                    KubernetesListType::Map
250                };
251        }
252
253        let merge_type = match &kind {
254            SchemaKind::Ty(Type::Array { items }) => MergeType::List {
255                strategy: value.kubernetes_list_type,
256                keys: value.kubernetes_list_map_keys,
257                item_merge_type: Box::new(items.merge_type.clone()),
258            },
259
260            SchemaKind::Ty(Type::Object { additional_properties }) => MergeType::Map {
261                strategy: value.kubernetes_map_type,
262                value_merge_type: Box::new(additional_properties.merge_type.clone()),
263            },
264
265            _ => MergeType::Default,
266        };
267
268        Ok(Schema {
269            description: value.description,
270            kind,
271            kubernetes_group_kind_versions: value.kubernetes_group_kind_versions,
272            list_kind: None,
273            merge_type,
274            impl_deep_merge: true,
275        })
276    }
277}
278
279/// The kind of a [`Schema`]
280#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
281pub enum SchemaKind {
282    Properties(std::collections::BTreeMap<PropertyName, (Schema, bool)>),
283    Ref(RefPath),
284    Ty(Type),
285}
286
287/// A string format. This corresponds to the `"format"` property of an `"string"` schema type.
288#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
289pub enum StringFormat {
290    Byte,
291    DateTime,
292}
293
294/// A type definition.
295#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
296pub enum Type {
297    Any,
298    Array { items: Box<Schema> },
299    Boolean,
300    Integer { format: IntegerFormat },
301    Number { format: NumberFormat },
302    Object { additional_properties: Box<Schema> },
303    String { format: Option<StringFormat> },
304
305    // Special type for the `subresources` field of custom resources.
306    CustomResourceSubresources(String),
307
308    // Special types that need alterative codegen
309    IntOrString,
310    JsonSchemaPropsOr(&'static str, JsonSchemaPropsOr),
311
312    Patch,
313    WatchEvent(RefPath),
314
315    // Special type for lists
316    ListDef { metadata: Box<SchemaKind> }, // The definition of the List type
317    ListRef { items: Box<SchemaKind> }, // A reference to a specialization of the List type for a particular resource type, eg List<Pod> for PodList
318}
319
320#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
321pub enum JsonSchemaPropsOr {
322    Array,
323    Bool,
324    StringArray,
325}
326
327impl Type {
328    #[cfg(feature = "serde")]
329    pub(crate) fn parse<'de, D>(
330        ty: &str,
331        additional_properties: Option<Box<Schema>>,
332        format: Option<&str>,
333        items: Option<Box<Schema>>,
334    ) -> Result<Self, D::Error> where D: serde::Deserializer<'de> {
335        match ty {
336            "array" => Ok(Type::Array {
337                items: items.ok_or_else(|| serde::de::Error::missing_field("items"))?,
338            }),
339
340            "boolean" => Ok(Type::Boolean),
341
342            "integer" => {
343                let format = match format {
344                    Some("int32") => IntegerFormat::Int32,
345                    Some("int64") | None => IntegerFormat::Int64,
346                    Some(format) => return Err(serde::de::Error::invalid_value(serde::de::Unexpected::Str(format), &"one of int32, int64")),
347                };
348                Ok(Type::Integer { format })
349            },
350
351            "number" => {
352                let format = format.ok_or_else(|| serde::de::Error::missing_field("format"))?;
353                let format = match format {
354                    "double" => NumberFormat::Double,
355                    format => return Err(serde::de::Error::invalid_value(serde::de::Unexpected::Str(format), &"one of double")),
356                };
357                Ok(Type::Number { format })
358            },
359
360            "object" => match additional_properties {
361                Some(additional_properties) => Ok(Type::Object { additional_properties }),
362                None => Ok(Type::Any),
363            },
364
365            "string" => {
366                let format = match format {
367                    Some("byte") => Some(StringFormat::Byte),
368                    Some("date-time") => Some(StringFormat::DateTime),
369                    Some("int-or-string") => return Ok(Type::IntOrString),
370                    Some(format) => return Err(serde::de::Error::invalid_value(serde::de::Unexpected::Str(format), &"one of byte, date-time, int-or-string")),
371                    None => None,
372                };
373                Ok(Type::String { format })
374            },
375
376            s => Err(serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &"one of array, boolean, integer, number, object, string")),
377        }
378    }
379}