Skip to main content

k8s_openapi_codegen_common/
lib.rs

1#![warn(rust_2018_idioms)]
2#![deny(clippy::all, clippy::pedantic)]
3#![allow(
4    clippy::default_trait_access,
5    clippy::missing_errors_doc,
6    clippy::missing_panics_doc,
7    clippy::must_use_candidate,
8    clippy::too_many_arguments,
9    clippy::too_many_lines,
10)]
11
12//! This crate contains common code for the [`k8s-openapi` code generator](https://github.com/Arnavion/k8s-openapi/tree/master/k8s-openapi-codegen)
13//! and the [`k8s-openapi-derive`](https://crates.io/crates/k8s-openapi-derive) custom derive crate.
14//!
15//! It can be used by code generators that want to generate crates like `k8s-openapi` and `k8s-openapi-derive` for Kubernetes-like software
16//! such as OpenShift.
17//!
18//! 1. Create a [`swagger20::Spec`] value, either by deserializing it from an OpenAPI spec JSON file or by creating it manually.
19//! 1. Invoke the [`run`] function for each definition in the spec.
20
21pub mod swagger20;
22
23mod templates;
24
25/// Statistics from a successful invocation of [`run`]
26#[derive(Clone, Copy, Debug)]
27pub struct RunResult {
28    pub num_generated_structs: usize,
29    pub num_generated_type_aliases: usize,
30}
31
32/// Error type reported by [`run`]
33#[derive(Debug)]
34pub struct Error(Box<dyn std::error::Error + Send + Sync>);
35
36impl std::fmt::Display for Error {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        self.0.fmt(f)
39    }
40}
41
42impl std::error::Error for Error {
43    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
44        self.0.source()
45    }
46}
47
48macro_rules! impl_from_for_error {
49    ($($ty:ty ,)*) => {
50        $(
51            impl From<$ty> for Error {
52                fn from(err: $ty) -> Self {
53                    Error(err.into())
54                }
55            }
56        )*
57    };
58}
59
60impl_from_for_error! {
61    &'_ str,
62    String,
63    std::fmt::Error,
64    std::io::Error,
65}
66
67/// A mechanism for converting (the components of) an openapi path to (the components of) a Rust namespace.
68///
69/// The k8s-openapi code generator uses this trait to emit the paths of all types relative to the `k8s_openapi` crate.
70/// For example, it maps the components of the openapi path `io.k8s.api.core.v1` to
71/// the components of the Rust namespace `crate::core::v1`. The `io.k8s.` prefix is stripped and the path starts with `crate::`.
72///
73/// Other code generators can have more complicated implementations. For example, an OpenShift code generator that has its own types but also wants to
74/// reuse types from the k8s-openapi crate would map `com.github.openshift.` to `crate::` and `io.k8s.` to `k8s_openapi::` instead.
75///
76/// The implementation should return `None` for paths that it does not recognize.
77pub trait MapNamespace {
78    fn map_namespace<'a>(&self, path_parts: &[&'a str]) -> Option<Vec<&'a str>>;
79}
80
81/// Used to create an impl of `std::io::Write` for each type that the type's generated code will be written to.
82pub trait RunState {
83    /// The impl of `std::io::Write` for each type that the type's generated code will be written to.
84    type Writer: std::io::Write;
85
86    /// Returns an impl of `std::io::Write` for each type that the type's generated code will be written to.
87    ///
88    /// # Parameters
89    ///
90    /// - `parts`: A list of strings making up the components of the path of the generated type. Code generators that are emitting crates
91    ///   can use this parameter to make module subdirectories for each component, and to emit `use` statements in the final module's `mod.rs`.
92    fn make_writer(&mut self, parts: &[&str]) -> std::io::Result<Self::Writer>;
93
94    /// This function is invoked when `k8s_openapi_codegen_common::run` is done with the writer and completes successfully.
95    /// The implementation can do any cleanup that it wants here.
96    fn finish(&mut self, writer: Self::Writer);
97}
98
99impl<T> RunState for &'_ mut T where T: RunState {
100    type Writer = <T as RunState>::Writer;
101
102    fn make_writer(&mut self, parts: &[&str]) -> std::io::Result<Self::Writer> {
103        (*self).make_writer(parts)
104    }
105
106    fn finish(&mut self, writer: Self::Writer) {
107        (*self).finish(writer);
108    }
109}
110
111/// Whether [`run`] should generate an impl of `schemars::JsonSchema` for the type or not.
112#[derive(Clone, Copy, Debug)]
113pub enum GenerateSchema<'a> {
114    Yes {
115        /// An optional feature that the impl of `schemars::JsonSchema` will be `cfg`-gated by.
116        feature: Option<&'a str>,
117    },
118
119    No,
120}
121
122/// Each invocation of this function generates a single type specified by the `definition_path` parameter.
123///
124/// # Parameters
125///
126/// - `definitions`: The definitions parsed from the OpenAPI spec that should be emitted as model types.
127///
128/// - `operations`: The list of operations parsed from the OpenAPI spec. The list is mutated to remove the operations
129///   that are determined to be associated with the type currently being generated.
130///
131/// - `definition_path`: The specific definition path out of the `definitions` collection that should be emitted.
132///
133/// - `map_namespace`: An instance of the [`MapNamespace`] trait that controls how OpenAPI namespaces of the definitions are mapped to rust namespaces.
134///
135/// - `vis`: The visibility modifier that should be emitted on the generated code.
136///
137/// - `state`: See the documentation of the [`RunState`] trait.
138pub fn run(
139    definitions: &std::collections::BTreeMap<swagger20::DefinitionPath, swagger20::Schema>,
140    operations: &mut Vec<swagger20::Operation>,
141    definition_path: &swagger20::DefinitionPath,
142    map_namespace: &impl MapNamespace,
143    vis: &str,
144    generate_schema: GenerateSchema<'_>,
145    generate_schema08: GenerateSchema<'_>,
146    mut state: impl RunState,
147) -> Result<RunResult, Error> {
148    let definition = definitions.get(definition_path).ok_or_else(|| format!("definition for {definition_path} does not exist in spec"))?;
149
150    let local = map_namespace_local_to_string(map_namespace)?;
151
152    let mut run_result = RunResult {
153        num_generated_structs: 0,
154        num_generated_type_aliases: 0,
155    };
156
157    let path_parts: Vec<_> = definition_path.split('.').collect();
158    let namespace_parts: Vec<_> =
159        map_namespace.map_namespace(&path_parts).ok_or_else(|| format!("unexpected path {definition_path:?}"))?
160        .into_iter()
161        .collect();
162
163    let mut out = state.make_writer(&namespace_parts)?;
164
165    let type_name = path_parts.last().ok_or_else(|| format!("path for {definition_path} has no parts"))?;
166
167    let derives = get_derives(&definition.kind, definitions, map_namespace)?;
168
169    templates::type_header::generate(
170        &mut out,
171        definition_path,
172        definition.description.as_deref(),
173        derives,
174        vis,
175    )?;
176
177    match &definition.kind {
178        swagger20::SchemaKind::Properties(properties) => {
179            let (template_properties, resource_metadata, metadata_ty) = {
180                let mut result = Vec::with_capacity(properties.len());
181
182                let mut single_group_version_kind = match &definition.kubernetes_group_kind_versions[..] {
183                    [group_version_kind] => Some((group_version_kind, false, false)),
184                    _ => None,
185                };
186
187                let mut metadata_ty = None;
188
189                for (name, (schema, required)) in properties {
190                    if name.0 == "apiVersion" {
191                        if let Some((_, has_api_version, _)) = &mut single_group_version_kind {
192                            *has_api_version = true;
193                            continue;
194                        }
195                    }
196
197                    if name.0 == "kind" {
198                        if let Some((_, _, has_kind)) = &mut single_group_version_kind {
199                            *has_kind = true;
200                            continue;
201                        }
202                    }
203
204                    let field_name = get_rust_ident(name);
205
206                    let mut field_type_name = String::new();
207
208                    let required = match required {
209                        true => templates::PropertyRequired::Required {
210                            is_default: is_default(&schema.kind, definitions, map_namespace)?,
211                        },
212                        false => templates::PropertyRequired::Optional,
213                    };
214
215                    if let templates::PropertyRequired::Optional = required {
216                        field_type_name.push_str("Option<");
217                    }
218
219                    let type_name = get_rust_type(&schema.kind, map_namespace)?;
220
221                    if name.0 == "metadata" {
222                        metadata_ty = Some((type_name.clone(), required));
223                    }
224
225                    // Fix cases of infinite recursion
226                    if let swagger20::SchemaKind::Ref(swagger20::RefPath { path, .. }) = &schema.kind {
227                        match (&**definition_path, &**name, &**path) {
228                            (
229                                "io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1beta1.JSONSchemaProps",
230                                "not",
231                                "io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1beta1.JSONSchemaProps",
232                            ) |
233                            (
234                                "io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.JSONSchemaProps",
235                                "not",
236                                "io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.JSONSchemaProps",
237                            ) => {
238                                field_type_name.push_str("std::boxed::Box<");
239                                field_type_name.push_str(&type_name);
240                                field_type_name.push('>');
241                            },
242
243                            _ => field_type_name.push_str(&type_name),
244                        }
245                    }
246                    else {
247                        field_type_name.push_str(&type_name);
248                    }
249
250                    if let templates::PropertyRequired::Optional = required {
251                        field_type_name.push('>');
252                    }
253
254                    let is_flattened = matches!(&schema.kind, swagger20::SchemaKind::Ty(swagger20::Type::CustomResourceSubresources(_)));
255
256                    result.push(templates::Property {
257                        name,
258                        comment: schema.description.as_deref(),
259                        field_name,
260                        field_type_name,
261                        required,
262                        is_flattened,
263                        merge_type: &schema.merge_type,
264                    });
265                }
266
267                let resource_metadata = match single_group_version_kind {
268                    Some((single_group_version_kind, true, true)) =>
269                        Some(if single_group_version_kind.group.is_empty() {
270                            (
271                                format!("{:?}", single_group_version_kind.version),
272                                format!("{:?}", ""),
273                                format!("{:?}", single_group_version_kind.kind),
274                                format!("{:?}", single_group_version_kind.version),
275                                definition.list_kind.as_ref().map(|kind| format!("{kind:?}")),
276                            )
277                        }
278                        else {
279                            (
280                                format!("{:?}", format!("{}/{}", single_group_version_kind.group, single_group_version_kind.version)),
281                                format!("{:?}", single_group_version_kind.group),
282                                format!("{:?}", single_group_version_kind.kind),
283                                format!("{:?}", single_group_version_kind.version),
284                                definition.list_kind.as_ref().map(|kind| format!("{kind:?}")),
285                            )
286                        }),
287                    Some((_, true, false)) => return Err(format!("{definition_path} has an apiVersion property but not a kind property").into()),
288                    Some((_, false, true)) => return Err(format!("{definition_path} has a kind property but not an apiVersion property").into()),
289                    Some((_, false, false)) | None => None,
290                };
291
292                (result, resource_metadata, metadata_ty)
293            };
294
295            templates::r#struct::generate(
296                &mut out,
297                vis,
298                type_name,
299                Default::default(),
300                &template_properties,
301            )?;
302
303            let mut namespace_or_cluster_scoped_url_path_segment_and_scope = vec![];
304            let mut subresource_url_path_segment_and_scope = vec![];
305
306            if !definition.kubernetes_group_kind_versions.is_empty() {
307                let mut kubernetes_group_kind_versions: Vec<_> = definition.kubernetes_group_kind_versions.iter().collect();
308                kubernetes_group_kind_versions.sort();
309
310                let mut operations_by_gkv: std::collections::BTreeMap<_, Vec<_>> = Default::default();
311                for operation in std::mem::take(operations) {
312                    operations_by_gkv
313                        .entry(operation.kubernetes_group_kind_version.clone())
314                        .or_default()
315                        .push(operation);
316                }
317
318                for kubernetes_group_kind_version in kubernetes_group_kind_versions {
319                    if let Some(mut operations) = operations_by_gkv.remove(&Some(kubernetes_group_kind_version.clone())) {
320                        operations.sort_by(|o1, o2| o1.id.cmp(&o2.id));
321
322                        for operation in operations {
323                            // If this is a CRUD operation, use it to determine the resource's URL path segment and scope.
324                            match operation.kubernetes_action {
325                                Some(
326                                    swagger20::KubernetesAction::Delete |
327                                    swagger20::KubernetesAction::Get |
328                                    swagger20::KubernetesAction::Post |
329                                    swagger20::KubernetesAction::Put
330                                ) => (),
331                                _ => continue,
332                            }
333                            let mut components = operation.path.rsplit('/');
334                            let components = (
335                                components.next().expect("str::rsplit returns at least one component"),
336                                components.next(),
337                                components.next(),
338                                components.next(),
339                            );
340
341                            let (url_path_segment_, scope_, url_path_segment_and_scope) = match components {
342                                ("{name}", Some(url_path_segment), Some("{namespace}"), Some("namespaces")) =>
343                                    (
344                                        format!("{url_path_segment:?}"),
345                                        format!("{local}NamespaceResourceScope"),
346                                        &mut namespace_or_cluster_scoped_url_path_segment_and_scope,
347                                    ),
348
349                                ("{name}", Some(url_path_segment), _, _) =>
350                                    (
351                                        format!("{url_path_segment:?}"),
352                                        format!("{local}ClusterResourceScope"),
353                                        &mut namespace_or_cluster_scoped_url_path_segment_and_scope,
354                                    ),
355
356                                (url_path_segment, Some("{name}"), _, _) =>
357                                    (
358                                        format!("{url_path_segment:?}"),
359                                        format!("{local}SubResourceScope"),
360                                        &mut subresource_url_path_segment_and_scope,
361                                    ),
362
363                                (url_path_segment, Some("{namespace}"), Some("namespaces"), _) =>
364                                    (
365                                        format!("{url_path_segment:?}"),
366                                        format!("{local}NamespaceResourceScope"),
367                                        &mut namespace_or_cluster_scoped_url_path_segment_and_scope,
368                                    ),
369
370                                (url_path_segment, _, _, _) =>
371                                    (
372                                        format!("{url_path_segment:?}"),
373                                        format!("{local}ClusterResourceScope"),
374                                        &mut namespace_or_cluster_scoped_url_path_segment_and_scope,
375                                    ),
376                            };
377
378                            url_path_segment_and_scope.push((url_path_segment_, scope_));
379                        }
380                    }
381                }
382
383                *operations = operations_by_gkv.into_values().flatten().collect();
384            }
385
386            match &**definition_path {
387                "io.k8s.apimachinery.pkg.apis.meta.v1.APIGroup" |
388                "io.k8s.apimachinery.pkg.apis.meta.v1.APIGroupList" |
389                "io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" |
390                "io.k8s.apimachinery.pkg.apis.meta.v1.APIVersions" =>
391                    namespace_or_cluster_scoped_url_path_segment_and_scope.push((r#""""#.to_owned(), format!("{local}ClusterResourceScope"))),
392                "io.k8s.apimachinery.pkg.apis.meta.v1.Status" =>
393                    subresource_url_path_segment_and_scope.push((r#""status""#.to_owned(), format!("{local}SubResourceScope"))),
394                _ => (),
395            }
396
397            namespace_or_cluster_scoped_url_path_segment_and_scope.dedup();
398            subresource_url_path_segment_and_scope.dedup();
399
400            let template_resource_metadata = match (&resource_metadata, &metadata_ty) {
401                (
402                    Some((api_version, group, kind, version, list_kind)),
403                    Some((metadata_ty, templates::PropertyRequired::Required { is_default: _ })),
404                ) => Some(templates::ResourceMetadata {
405                    api_version,
406                    group,
407                    kind,
408                    version,
409                    list_kind: list_kind.as_deref(),
410                    metadata_ty: Some(metadata_ty),
411                    url_path_segment_and_scope: match (&*namespace_or_cluster_scoped_url_path_segment_and_scope, &*subresource_url_path_segment_and_scope) {
412                        ([(url_path_segment, scope)], _) |
413                        ([], [(url_path_segment, scope)]) => (&**url_path_segment, &**scope),
414
415                        ([], []) => return Err(format!(
416                            "definition {definition_path} is a Resource but its URL path segment and scope could not be inferred").into()),
417                        ([_, ..], _) => return Err(format!(
418                            "definition {definition_path} is a Resource but was inferred to have multiple scopes {namespace_or_cluster_scoped_url_path_segment_and_scope:?}").into()),
419                        ([], [_, ..]) => return Err(format!(
420                            "definition {definition_path} is a Resource but was inferred to have multiple scopes {subresource_url_path_segment_and_scope:?}").into()),
421                    },
422                }),
423
424                (Some(_), Some((_, templates::PropertyRequired::Optional | templates::PropertyRequired::OptionalDefault))) =>
425                    return Err(format!("definition {definition_path} has optional metadata").into()),
426
427                (
428                    Some((api_version, group, kind, version, list_kind)),
429                    None,
430                ) => Some(templates::ResourceMetadata {
431                    api_version,
432                    group,
433                    kind,
434                    version,
435                    list_kind: list_kind.as_deref(),
436                    metadata_ty: None,
437                    url_path_segment_and_scope: match (&*namespace_or_cluster_scoped_url_path_segment_and_scope, &*subresource_url_path_segment_and_scope) {
438                        ([(url_path_segment, scope)], _) |
439                        ([], [(url_path_segment, scope)]) => (&**url_path_segment, &**scope),
440
441                        ([], []) => return Err(format!(
442                            "definition {definition_path} is a Resource but its URL path segment and scope could not be inferred").into()),
443                        ([_, _, ..], _) => return Err(format!(
444                            "definition {definition_path} is a Resource but was inferred to have multiple scopes {namespace_or_cluster_scoped_url_path_segment_and_scope:?}").into()),
445                        ([], [_, _, ..]) => return Err(format!(
446                            "definition {definition_path} is a Resource but was inferred to have multiple scopes {subresource_url_path_segment_and_scope:?}").into()),
447                    },
448                }),
449
450                (None, _) => None,
451            };
452
453            if let Some(template_resource_metadata) = &template_resource_metadata {
454                templates::impl_resource::generate(
455                    &mut out,
456                    type_name,
457                    Default::default(),
458                    map_namespace,
459                    template_resource_metadata,
460                )?;
461
462                templates::impl_listable_resource::generate(
463                    &mut out,
464                    type_name,
465                    Default::default(),
466                    map_namespace,
467                    template_resource_metadata,
468                )?;
469
470                templates::impl_metadata::generate(
471                    &mut out,
472                    type_name,
473                    Default::default(),
474                    map_namespace,
475                    template_resource_metadata,
476                )?;
477            }
478
479            if definition.impl_deep_merge {
480                templates::struct_deep_merge::generate(
481                    &mut out,
482                    type_name,
483                    Default::default(),
484                    &template_properties,
485                    map_namespace,
486                )?;
487            }
488
489            templates::impl_deserialize::generate(
490                &mut out,
491                type_name,
492                Default::default(),
493                &template_properties,
494                map_namespace,
495                template_resource_metadata.as_ref(),
496            )?;
497
498            templates::impl_serialize::generate(
499                &mut out,
500                type_name,
501                Default::default(),
502                &template_properties,
503                map_namespace,
504                template_resource_metadata.as_ref(),
505            )?;
506
507            run_result.num_generated_structs += 1;
508        },
509
510        swagger20::SchemaKind::Ref(_) => return Err(format!("{definition_path} is a Ref").into()),
511
512        swagger20::SchemaKind::Ty(swagger20::Type::IntOrString) => {
513            templates::int_or_string::generate(
514                &mut out,
515                type_name,
516                map_namespace,
517            )?;
518
519            run_result.num_generated_structs += 1;
520        },
521
522        swagger20::SchemaKind::Ty(swagger20::Type::JsonSchemaPropsOr(namespace, json_schema_props_or)) => {
523            let json_schema_props_or = match json_schema_props_or {
524                swagger20::JsonSchemaPropsOr::Array => templates::json_schema_props_or::Or::Array,
525                swagger20::JsonSchemaPropsOr::Bool => templates::json_schema_props_or::Or::Bool,
526                swagger20::JsonSchemaPropsOr::StringArray => templates::json_schema_props_or::Or::StringArray,
527            };
528
529            let json_schema_props_type_name =
530                get_fully_qualified_type_name(
531                    &swagger20::RefPath {
532                        path: format!("io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.{namespace}.JSONSchemaProps"),
533                        can_be_default: None,
534                    },
535                    map_namespace);
536
537            templates::json_schema_props_or::generate(
538                &mut out,
539                type_name,
540                json_schema_props_or,
541                &json_schema_props_type_name,
542                map_namespace,
543            )?;
544
545            run_result.num_generated_structs += 1;
546        },
547
548        swagger20::SchemaKind::Ty(swagger20::Type::Quantity) => {
549            templates::quantity::generate(
550                &mut out,
551                type_name,
552                map_namespace,
553            )?;
554
555            run_result.num_generated_structs += 1;
556        },
557
558        swagger20::SchemaKind::Ty(swagger20::Type::Patch) => {
559            templates::patch::generate(
560                &mut out,
561                type_name,
562                map_namespace,
563            )?;
564
565            run_result.num_generated_structs += 1;
566        },
567
568        swagger20::SchemaKind::Ty(swagger20::Type::WatchEvent(raw_extension_ref_path)) => {
569            let error_status_rust_type = get_rust_type(
570                &swagger20::SchemaKind::Ref(swagger20::RefPath {
571                    path: "io.k8s.apimachinery.pkg.apis.meta.v1.Status".to_owned(),
572                    can_be_default: None,
573                }),
574                map_namespace,
575            )?;
576
577            let error_other_rust_type = get_rust_type(
578                &swagger20::SchemaKind::Ref(raw_extension_ref_path.clone()),
579                map_namespace,
580            )?;
581
582            templates::watch_event::generate(
583                &mut out,
584                type_name,
585                &error_status_rust_type,
586                &error_other_rust_type,
587                map_namespace,
588            )?;
589
590            run_result.num_generated_structs += 1;
591        },
592
593        swagger20::SchemaKind::Ty(swagger20::Type::ListDef { metadata }) => {
594            let metadata_rust_type = get_rust_type(metadata, map_namespace)?;
595
596            let template_generics_where_part = format!("T: {local}ListableResource");
597            let template_generics = templates::Generics {
598                type_part: Some("T"),
599                where_part: Some(&template_generics_where_part),
600            };
601
602            let items_merge_type = swagger20::MergeType::List {
603                strategy: swagger20::KubernetesListType::Map,
604                keys: vec!["metadata().namespace".to_string(), "metadata().name".to_string()],
605                item_merge_type: Box::new(swagger20::MergeType::Default),
606            };
607
608            let template_properties = vec![
609                templates::Property {
610                    name: "items",
611                    comment: Some("List of objects."),
612                    field_name: "items".into(),
613                    field_type_name: "std::vec::Vec<T>".to_owned(),
614                    required: templates::PropertyRequired::Required { is_default: true },
615                    is_flattened: false,
616                    merge_type: &items_merge_type,
617                },
618
619                templates::Property {
620                    name: "metadata",
621                    comment: Some("Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds"),
622                    field_name: "metadata".into(),
623                    field_type_name: (*metadata_rust_type).to_owned(),
624                    required: templates::PropertyRequired::Required { is_default: true },
625                    is_flattened: false,
626                    merge_type: &swagger20::MergeType::Default,
627                },
628            ];
629
630            let template_resource_metadata = templates::ResourceMetadata {
631                api_version: "<T as crate::Resource>::API_VERSION",
632                group: "<T as crate::Resource>::GROUP",
633                kind: "<T as crate::ListableResource>::LIST_KIND",
634                version: "<T as crate::Resource>::VERSION",
635                list_kind: None,
636                metadata_ty: Some(&metadata_rust_type),
637                url_path_segment_and_scope: (r#""""#, "<T as crate::Resource>::Scope"),
638            };
639
640            templates::r#struct::generate(
641                &mut out,
642                vis,
643                type_name,
644                template_generics,
645                &template_properties,
646            )?;
647
648            templates::impl_resource::generate(
649                &mut out,
650                type_name,
651                template_generics,
652                map_namespace,
653                &template_resource_metadata,
654            )?;
655
656            templates::impl_listable_resource::generate(
657                &mut out,
658                type_name,
659                template_generics,
660                map_namespace,
661                &template_resource_metadata,
662            )?;
663
664            templates::impl_metadata::generate(
665                &mut out,
666                type_name,
667                template_generics,
668                map_namespace,
669                &template_resource_metadata,
670            )?;
671
672            if definition.impl_deep_merge {
673                let template_generics_where_part = format!("T: {local}DeepMerge + {local}Metadata<Ty = {local}apimachinery::pkg::apis::meta::v1::ObjectMeta> + {local}ListableResource");
674                let template_generics = templates::Generics {
675                    where_part: Some(&template_generics_where_part),
676                    ..template_generics
677                };
678
679                templates::struct_deep_merge::generate(
680                    &mut out,
681                    type_name,
682                    template_generics,
683                    &template_properties,
684                    map_namespace,
685                )?;
686            }
687
688            {
689                let template_generics_where_part = format!("T: {local}serde::Deserialize<'de> + {local}ListableResource");
690                let template_generics = templates::Generics {
691                    where_part: Some(&template_generics_where_part),
692                    ..template_generics
693                };
694
695                templates::impl_deserialize::generate(
696                    &mut out,
697                    type_name,
698                    template_generics,
699                    &template_properties,
700                    map_namespace,
701                    Some(&template_resource_metadata),
702                )?;
703            }
704
705            {
706                let template_generics_where_part = format!("T: {local}serde::Serialize + {local}ListableResource");
707                let template_generics = templates::Generics {
708                    where_part: Some(&template_generics_where_part),
709                    ..template_generics
710                };
711
712                templates::impl_serialize::generate(
713                    &mut out,
714                    type_name,
715                    template_generics,
716                    &template_properties,
717                    map_namespace,
718                    Some(&template_resource_metadata),
719                )?;
720            }
721
722            run_result.num_generated_structs += 1;
723        },
724
725        swagger20::SchemaKind::Ty(swagger20::Type::ListRef { .. }) => return Err(format!("definition {definition_path} is a ListRef").into()),
726
727        swagger20::SchemaKind::Ty(_) => {
728            let inner_type_name = get_rust_type(&definition.kind, map_namespace)?;
729
730            // Kubernetes requires MicroTime to be serialized with exactly six decimal digits, instead of the default serde serialization of `jiff::Timestamp`
731            // that uses a variable number up to nine.
732            //
733            // Furthermore, while Kubernetes does deserialize a Time from a string with one or more decimal digits,
734            // the format string it uses to *serialize* datetimes does not contain any decimal digits. So match that behavior just to be safe, and to have
735            // the same behavior as the golang client.
736            //
737            // Refs:
738            // - https://github.com/Arnavion/k8s-openapi/issues/63
739            // - https://github.com/deislabs/krustlet/issues/5
740            // - https://github.com/kubernetes/apimachinery/issues/88
741            let datetime_serialization_format = match (&**definition_path, &definition.kind) {
742                (
743                    "io.k8s.apimachinery.pkg.apis.meta.v1.MicroTime",
744                    swagger20::SchemaKind::Ty(swagger20::Type::String { format: Some(swagger20::StringFormat::DateTime) }),
745                ) => templates::DateTimeSerializationFormat::SixDecimalDigits,
746
747                (
748                    "io.k8s.apimachinery.pkg.apis.meta.v1.Time",
749                    swagger20::SchemaKind::Ty(swagger20::Type::String { format: Some(swagger20::StringFormat::DateTime) }),
750                ) => templates::DateTimeSerializationFormat::ZeroDecimalDigits,
751
752                _ => templates::DateTimeSerializationFormat::Default,
753            };
754
755            templates::newtype::generate(
756                &mut out,
757                vis,
758                type_name,
759                &inner_type_name,
760                datetime_serialization_format,
761                map_namespace,
762            )?;
763
764            run_result.num_generated_type_aliases += 1;
765        },
766    }
767
768    if let GenerateSchema::Yes { feature: schemars_feature } = generate_schema {
769        match &definition.kind {
770            swagger20::SchemaKind::Properties(_) |
771            swagger20::SchemaKind::Ty(
772                swagger20::Type::Any |
773                swagger20::Type::Array { .. } |
774                swagger20::Type::Boolean |
775                swagger20::Type::Integer { .. } |
776                swagger20::Type::Number { .. } |
777                swagger20::Type::Object { .. } |
778                swagger20::Type::String { .. } |
779                swagger20::Type::IntOrString |
780                swagger20::Type::JsonSchemaPropsOr(_, _) |
781                swagger20::Type::Quantity |
782                swagger20::Type::Patch
783            ) => {
784                templates::impl_schema::generate(
785                    &mut out,
786                    type_name,
787                    Default::default(),
788                    definition_path,
789                    definition,
790                    schemars_feature,
791                    map_namespace,
792                )?;
793            }
794
795            swagger20::SchemaKind::Ty(swagger20::Type::WatchEvent(_)) => {
796                templates::impl_schema::generate(
797                    &mut out,
798                    type_name,
799                    templates::Generics {
800                        type_part: Some("T"),
801                        where_part: None,
802                    },
803                    definition_path,
804                    definition,
805                    schemars_feature,
806                    map_namespace,
807                )?;
808            }
809
810            _ => (),
811        }
812    }
813
814    if let GenerateSchema::Yes { feature: schemars_feature } = generate_schema08 {
815        match &definition.kind {
816            swagger20::SchemaKind::Properties(_) |
817            swagger20::SchemaKind::Ty(
818                swagger20::Type::Any |
819                swagger20::Type::Array { .. } |
820                swagger20::Type::Boolean |
821                swagger20::Type::Integer { .. } |
822                swagger20::Type::Number { .. } |
823                swagger20::Type::Object { .. } |
824                swagger20::Type::String { .. } |
825                swagger20::Type::IntOrString |
826                swagger20::Type::JsonSchemaPropsOr(_, _) |
827                swagger20::Type::Quantity |
828                swagger20::Type::Patch
829            ) => {
830                templates::impl_schema08::generate(
831                    &mut out,
832                    type_name,
833                    Default::default(),
834                    definition_path,
835                    definition,
836                    schemars_feature,
837                    map_namespace,
838                )?;
839            }
840
841            swagger20::SchemaKind::Ty(swagger20::Type::WatchEvent(_)) => {
842                templates::impl_schema08::generate(
843                    &mut out,
844                    type_name,
845                    templates::Generics {
846                        type_part: Some("T"),
847                        where_part: None,
848                    },
849                    definition_path,
850                    definition,
851                    schemars_feature,
852                    map_namespace,
853                )?;
854            }
855
856            _ => (),
857        }
858    }
859
860    state.finish(out);
861
862    Ok(run_result)
863}
864
865fn map_namespace_local_to_string(map_namespace: &impl MapNamespace) -> Result<String, Error> {
866    let namespace_parts = map_namespace.map_namespace(&["io", "k8s"]).ok_or(r#"unexpected path "io.k8s""#)?;
867
868    let mut result = String::new();
869    for namespace_part in namespace_parts {
870        result.push_str(&get_rust_ident(namespace_part));
871        result.push_str("::");
872    }
873    Ok(result)
874}
875
876fn get_derives(
877    kind: &swagger20::SchemaKind,
878    definitions: &std::collections::BTreeMap<swagger20::DefinitionPath, swagger20::Schema>,
879    map_namespace: &impl MapNamespace,
880) -> Result<Option<templates::type_header::Derives>, Error> {
881    if matches!(kind, swagger20::SchemaKind::Ty(swagger20::Type::ListRef { .. })) {
882        // ListRef is emitted as a type alias.
883        return Ok(None);
884    }
885
886    let derive_clone =
887        evaluate_trait_bound(
888            kind,
889            true,
890            definitions,
891            map_namespace,
892            |_, _| Ok(true))?;
893
894    let derive_copy =
895        derive_clone &&
896        evaluate_trait_bound(
897            kind,
898            false,
899            definitions,
900            map_namespace,
901            |_, _| Ok(false))?;
902
903    #[allow(clippy::match_same_arms)]
904    let is_default = evaluate_trait_bound(kind, false, definitions, map_namespace, |kind, required| match kind {
905        // Option<T>::default is None regardless of T
906        _ if !required => Ok(true),
907
908        swagger20::SchemaKind::Ref(swagger20::RefPath { can_be_default: Some(can_be_default), .. }) => Ok(*can_be_default),
909
910        swagger20::SchemaKind::Ref(ref_path @ swagger20::RefPath { .. }) if ref_path.references_scope(map_namespace) => Ok(false),
911
912        // metadata field in resource type created by #[derive(CustomResourceDefinition)]
913        swagger20::SchemaKind::Ref(ref_path @ swagger20::RefPath { .. })
914            if !ref_path.references_scope(map_namespace) && ref_path.path == "io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" => Ok(true),
915
916        // Handled by evaluate_trait_bound
917        swagger20::SchemaKind::Ref(ref_path @ swagger20::RefPath { .. }) if !ref_path.references_scope(map_namespace) => unreachable!(),
918
919        // jiff::Timestamp defaults to `UNIX_EPOCH`, so best to not use the default value.
920        swagger20::SchemaKind::Ty(swagger20::Type::String { format: Some(swagger20::StringFormat::DateTime) }) => Ok(false),
921
922        // Enums without a default value
923        swagger20::SchemaKind::Ty(
924            swagger20::Type::JsonSchemaPropsOr(_, _) |
925            swagger20::Type::Patch |
926            swagger20::Type::WatchEvent(_)
927        ) => Ok(false),
928
929        _ => Ok(true),
930    })?;
931    let derive_default =
932        is_default &&
933        // IntOrString has a manual Default impl, so don't #[derive] it.
934        !matches!(kind, swagger20::SchemaKind::Ty(swagger20::Type::IntOrString));
935
936    let derive_partial_eq =
937        evaluate_trait_bound(
938            kind,
939            true,
940            definitions,
941            map_namespace,
942            |_, _| Ok(true))?;
943
944    // The choice of deriving Eq, Ord and PartialOrd is deliberately more conservative than the choice of deriving PartialEq,
945    // so as to not change dramatically between Kubernetes versions. For example, ObjectMeta is Ord in v1.15 but not in v1.16 because
946    // it indirectly gained a serde_json::Value field (`managed_fields.fields_v1.0`).
947    //
948    // Also, being conservative means the types generated by #[derive(k8s_openapi_derive::CustomResource)] don't have to require them either.
949
950    let derive_eq =
951        derive_partial_eq &&
952        matches!(kind, swagger20::SchemaKind::Ty(
953            swagger20::Type::IntOrString |
954            swagger20::Type::String { format: Some(swagger20::StringFormat::DateTime) }
955        ));
956
957    let derive_partial_ord =
958        derive_partial_eq &&
959        matches!(kind, swagger20::SchemaKind::Ty(swagger20::Type::String { format: Some(swagger20::StringFormat::DateTime) }));
960
961    let derive_ord = derive_partial_ord && derive_eq;
962
963    Ok(Some(templates::type_header::Derives {
964        clone: derive_clone,
965        copy: derive_copy,
966        default: derive_default,
967        eq: derive_eq,
968        ord: derive_ord,
969        partial_eq: derive_partial_eq,
970        partial_ord: derive_partial_ord,
971    }))
972}
973
974fn is_default(
975    kind: &swagger20::SchemaKind,
976    definitions: &std::collections::BTreeMap<swagger20::DefinitionPath, swagger20::Schema>,
977    map_namespace: &impl MapNamespace,
978) -> Result<bool, Error> {
979    #[allow(clippy::match_same_arms)]
980    evaluate_trait_bound(kind, false, definitions, map_namespace, |kind, required| match kind {
981        // Option<T>::default is None regardless of T
982        _ if !required => Ok(true),
983
984        swagger20::SchemaKind::Ref(swagger20::RefPath { can_be_default: Some(can_be_default), .. }) => Ok(*can_be_default),
985
986        swagger20::SchemaKind::Ref(ref_path @ swagger20::RefPath { .. }) if ref_path.references_scope(map_namespace) => Ok(false),
987
988        // metadata field in resource type created by #[derive(CustomResourceDefinition)]
989        swagger20::SchemaKind::Ref(ref_path @ swagger20::RefPath { .. })
990            if !ref_path.references_scope(map_namespace) && ref_path.path == "io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" => Ok(true),
991
992        // Handled by evaluate_trait_bound
993        swagger20::SchemaKind::Ref(ref_path @ swagger20::RefPath { .. }) if !ref_path.references_scope(map_namespace) => unreachable!(),
994
995        // jiff::Timestamp defaults to `UNIX_EPOCH`, so best to not use the default value.
996        swagger20::SchemaKind::Ty(swagger20::Type::String { format: Some(swagger20::StringFormat::DateTime) }) => Ok(false),
997
998        // Enums without a default value
999        swagger20::SchemaKind::Ty(
1000            swagger20::Type::JsonSchemaPropsOr(_, _) |
1001            swagger20::Type::Patch |
1002            swagger20::Type::WatchEvent(_)
1003        ) => Ok(false),
1004
1005        _ => Ok(true),
1006    })
1007}
1008
1009fn evaluate_trait_bound(
1010    kind: &swagger20::SchemaKind,
1011    array_follows_elements: bool,
1012    definitions: &std::collections::BTreeMap<swagger20::DefinitionPath, swagger20::Schema>,
1013    map_namespace: &impl MapNamespace,
1014    mut f: impl FnMut(&swagger20::SchemaKind, bool) -> Result<bool, Error>,
1015) -> Result<bool, Error> {
1016    fn evaluate_trait_bound_inner<'a>(
1017        #[allow(clippy::ptr_arg)] // False positive. Clippy wants this to be `&SchemaKind` but we use Cow-specific operations (`.clone()`).
1018        kind: &std::borrow::Cow<'a, swagger20::SchemaKind>,
1019        required: bool,
1020        array_follows_elements: bool,
1021        definitions: &std::collections::BTreeMap<swagger20::DefinitionPath, swagger20::Schema>,
1022        map_namespace: &impl MapNamespace,
1023        visited: &mut std::collections::BTreeSet<std::borrow::Cow<'a, swagger20::SchemaKind>>,
1024        f: &mut impl FnMut(&swagger20::SchemaKind, bool) -> Result<bool, Error>,
1025    ) -> Result<bool, Error> {
1026        if !visited.insert(kind.clone()) {
1027            // In case of recursive types, assume the bound holds.
1028            return Ok(true);
1029        }
1030
1031        match &**kind {
1032            swagger20::SchemaKind::Properties(properties) => {
1033                for (property_schema, property_required) in properties.values() {
1034                    let mut visited = visited.clone();
1035                    let field_bound =
1036                        evaluate_trait_bound_inner(
1037                            &std::borrow::Cow::Borrowed(&property_schema.kind),
1038                            required && *property_required,
1039                            array_follows_elements,
1040                            definitions,
1041                            map_namespace,
1042                            &mut visited,
1043                            f,
1044                        )?;
1045                    if !field_bound {
1046                        return Ok(false);
1047                    }
1048                }
1049
1050                Ok(true)
1051            },
1052
1053            swagger20::SchemaKind::Ref(ref_path @ swagger20::RefPath { .. }) if !ref_path.references_scope(map_namespace) => {
1054                let trait_bound =
1055                    if let Some(target) = definitions.get(&*ref_path.path) {
1056                        let mut visited = visited.clone();
1057                        evaluate_trait_bound_inner(
1058                            &std::borrow::Cow::Borrowed(&target.kind),
1059                            required,
1060                            array_follows_elements,
1061                            definitions,
1062                            map_namespace,
1063                            &mut visited,
1064                            f,
1065                        )
1066                    }
1067                    else {
1068                        f(kind, required)
1069                    };
1070                trait_bound
1071            },
1072
1073            swagger20::SchemaKind::Ty(swagger20::Type::Array { items }) if array_follows_elements =>
1074                evaluate_trait_bound_inner(
1075                    &std::borrow::Cow::Owned(items.kind.clone()),
1076                    required,
1077                    array_follows_elements,
1078                    definitions,
1079                    map_namespace,
1080                    visited,
1081                    f,
1082                ),
1083
1084            swagger20::SchemaKind::Ty(swagger20::Type::JsonSchemaPropsOr(namespace, _)) => {
1085                let json_schema_props_ref_path = swagger20::RefPath {
1086                    path: format!("io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.{namespace}.JSONSchemaProps"),
1087                    can_be_default: None,
1088                };
1089                let json_schema_props_bound =
1090                    evaluate_trait_bound_inner(
1091                        &std::borrow::Cow::Owned(swagger20::SchemaKind::Ref(json_schema_props_ref_path)),
1092                        required,
1093                        array_follows_elements,
1094                        definitions,
1095                        map_namespace,
1096                        visited,
1097                        f,
1098                    )?;
1099                if !json_schema_props_bound {
1100                    return Ok(false);
1101                }
1102
1103                f(kind, required)
1104            },
1105
1106            swagger20::SchemaKind::Ty(swagger20::Type::WatchEvent(raw_extension_ref_path)) => {
1107                let raw_extension_bound =
1108                    evaluate_trait_bound_inner(
1109                        &std::borrow::Cow::Owned(swagger20::SchemaKind::Ref(raw_extension_ref_path.clone())),
1110                        required,
1111                        array_follows_elements,
1112                        definitions,
1113                        map_namespace,
1114                        visited,
1115                        f,
1116                    )?;
1117                if !raw_extension_bound {
1118                    return Ok(false);
1119                }
1120
1121                f(kind, required)
1122            },
1123
1124            kind => f(kind, required),
1125        }
1126    }
1127
1128    let mut visited = Default::default();
1129    evaluate_trait_bound_inner(
1130        &std::borrow::Cow::Borrowed(kind),
1131        true,
1132        array_follows_elements,
1133        definitions,
1134        map_namespace,
1135        &mut visited,
1136        &mut f,
1137    )
1138}
1139
1140fn get_comment_text<'a>(s: &'a str, indent: &'a str) -> impl Iterator<Item = std::borrow::Cow<'static, str>> + 'a {
1141    s.lines().scan(true, move |previous_line_was_empty, line|
1142        if line.is_empty() {
1143            *previous_line_was_empty = true;
1144            Some("".into())
1145        }
1146        else {
1147            let line =
1148                line
1149                .replace('\\', r"\\")
1150                .replace('[', r"\[")
1151                .replace(']', r"\]")
1152                .replace('<', r"\<")
1153                .replace('>', r"\>")
1154                .replace('\t', "    ")
1155                .replace("```", "");
1156
1157            let line =
1158                if *previous_line_was_empty && line.starts_with("    ") {
1159                    // Collapse this line's spaces into two. Otherwise rustdoc will think this is the start of a code block containing a Rust test.
1160                    format!("  {}", line.trim_start())
1161                }
1162                else {
1163                    line
1164                };
1165            let line = line.trim_end();
1166
1167            *previous_line_was_empty = false;
1168
1169            Some(format!("{indent} {line}").into())
1170        })
1171}
1172
1173fn get_fully_qualified_type_name(
1174    ref_path: &swagger20::RefPath,
1175    map_namespace: &impl MapNamespace,
1176) -> String {
1177    let path_parts: Vec<_> = ref_path.path.split('.').collect();
1178    let namespace_parts = map_namespace.map_namespace(&path_parts[..(path_parts.len() - 1)]);
1179    if let Some(namespace_parts) = namespace_parts {
1180        let mut result = String::new();
1181        for namespace_part in namespace_parts {
1182            result.push_str(&get_rust_ident(namespace_part));
1183            result.push_str("::");
1184        }
1185        result.push_str(path_parts[path_parts.len() - 1]);
1186        result
1187    }
1188    else {
1189        let last_part = *path_parts.last().expect("str::split yields at least one item");
1190        last_part.to_owned()
1191    }
1192}
1193
1194/// Converts the given string into a string that can be used as a Rust ident.
1195pub fn get_rust_ident(name: &str) -> std::borrow::Cow<'static, str> {
1196    // Fix cases of invalid rust idents
1197    match name {
1198        "$ref" => return "ref_path".into(),
1199        "$schema" => return "schema".into(),
1200        "as" => return "as_".into(),
1201        "continue" => return "continue_".into(),
1202        "enum" => return "enum_".into(),
1203        "ref" => return "ref_".into(),
1204        "type" => return "type_".into(),
1205        _ => (),
1206    }
1207
1208    // Some cases of "ABc" should be converted to "abc" instead of "a_bc".
1209    // Eg "JSONSchemas" => "json_schemas", but "externalIPs" => "external_ips" instead of "external_i_ps".
1210    // Mostly happens with plurals of abbreviations.
1211    match name {
1212        "clusterIPs" => return "cluster_ips".into(),
1213        "externalIPs" => return "external_ips".into(),
1214        "hostIPs" => return "host_ips".into(),
1215        "nonResourceURLs" => return "non_resource_urls".into(),
1216        "podCIDRs" => return "pod_cidrs".into(),
1217        "podIPs" => return "pod_ips".into(),
1218        "serverAddressByClientCIDRs" => return "server_address_by_client_cidrs".into(),
1219        "targetWWNs" => return "target_wwns".into(),
1220        _ => (),
1221    }
1222
1223    let mut result = String::new();
1224
1225    let chars =
1226        name.chars()
1227        .zip(std::iter::once(None).chain(name.chars().map(|c| Some(c.is_uppercase()))))
1228        .zip(name.chars().skip(1).map(|c| Some(c.is_uppercase())).chain(std::iter::once(None)));
1229
1230    for ((c, previous), next) in chars {
1231        if c.is_uppercase() {
1232            match (previous, next) {
1233                (Some(false), _) |
1234                (Some(true), Some(false)) => result.push('_'),
1235                _ => (),
1236            }
1237
1238            result.extend(c.to_lowercase());
1239        }
1240        else {
1241            result.push(match c {
1242                '-' => '_',
1243                c => c,
1244            });
1245        }
1246    }
1247
1248    result.into()
1249}
1250
1251fn get_rust_type(
1252    schema_kind: &swagger20::SchemaKind,
1253    map_namespace: &impl MapNamespace,
1254) -> Result<std::borrow::Cow<'static, str>, Error> {
1255    let local = map_namespace_local_to_string(map_namespace)?;
1256
1257    match schema_kind {
1258        swagger20::SchemaKind::Properties(_) => Err("Nested anonymous types not supported".into()),
1259
1260        swagger20::SchemaKind::Ref(ref_path) =>
1261            Ok(get_fully_qualified_type_name(ref_path, map_namespace).into()),
1262
1263        swagger20::SchemaKind::Ty(swagger20::Type::Any) => Ok(format!("{local}serde_json::Value").into()),
1264
1265        swagger20::SchemaKind::Ty(swagger20::Type::Array { items }) =>
1266            Ok(format!("std::vec::Vec<{}>", get_rust_type(&items.kind, map_namespace)?).into()),
1267
1268        swagger20::SchemaKind::Ty(swagger20::Type::Boolean) => Ok("bool".into()),
1269
1270        swagger20::SchemaKind::Ty(swagger20::Type::Integer { format: swagger20::IntegerFormat::Int32 }) => Ok("i32".into()),
1271        swagger20::SchemaKind::Ty(swagger20::Type::Integer { format: swagger20::IntegerFormat::Int64 }) => Ok("i64".into()),
1272
1273        swagger20::SchemaKind::Ty(swagger20::Type::Number { format: swagger20::NumberFormat::Double }) => Ok("f64".into()),
1274
1275        swagger20::SchemaKind::Ty(swagger20::Type::Object { additional_properties }) =>
1276            Ok(format!("std::collections::BTreeMap<std::string::String, {}>", get_rust_type(&additional_properties.kind, map_namespace)?).into()),
1277
1278        swagger20::SchemaKind::Ty(swagger20::Type::String { format: Some(swagger20::StringFormat::Byte) }) =>
1279            Ok(format!("{local}ByteString").into()),
1280        swagger20::SchemaKind::Ty(swagger20::Type::String { format: Some(swagger20::StringFormat::DateTime) }) =>
1281            Ok(format!("{local}jiff::Timestamp").into()),
1282        swagger20::SchemaKind::Ty(swagger20::Type::String { format: None }) => Ok("std::string::String".into()),
1283
1284        swagger20::SchemaKind::Ty(swagger20::Type::CustomResourceSubresources(namespace)) => {
1285            let namespace_parts =
1286                &["io", "k8s", "apiextensions_apiserver", "pkg", "apis", "apiextensions", namespace];
1287            let namespace_parts =
1288                map_namespace.map_namespace(namespace_parts)
1289                .ok_or_else(|| format!("unexpected path {:?}", namespace_parts.join(".")))?;
1290
1291            let mut result = String::new();
1292            for namespace_part in namespace_parts {
1293                result.push_str(&get_rust_ident(namespace_part));
1294                result.push_str("::");
1295            }
1296            result.push_str("CustomResourceSubresources");
1297            Ok(result.into())
1298        },
1299
1300        swagger20::SchemaKind::Ty(swagger20::Type::IntOrString) => Err("nothing should be trying to refer to IntOrString".into()),
1301        swagger20::SchemaKind::Ty(swagger20::Type::JsonSchemaPropsOr(_, _)) => Err("JSON schema types not supported".into()),
1302        swagger20::SchemaKind::Ty(swagger20::Type::Quantity) => Err("nothing should be trying to refer to Quantity".into()),
1303
1304        swagger20::SchemaKind::Ty(swagger20::Type::Patch) => Err("Patch type not supported".into()),
1305        swagger20::SchemaKind::Ty(swagger20::Type::WatchEvent(_)) => Err("WatchEvent type not supported".into()),
1306
1307        swagger20::SchemaKind::Ty(swagger20::Type::ListDef { .. }) => Err("ListDef type not supported".into()),
1308        swagger20::SchemaKind::Ty(swagger20::Type::ListRef { items }) =>
1309            Ok(format!("{local}List<{}>", get_rust_type(items, map_namespace)?).into()),
1310    }
1311}