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
12pub mod swagger20;
22
23mod templates;
24
25#[derive(Clone, Copy, Debug)]
27pub struct RunResult {
28 pub num_generated_structs: usize,
29 pub num_generated_type_aliases: usize,
30}
31
32#[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
67pub trait MapNamespace {
78 fn map_namespace<'a>(&self, path_parts: &[&'a str]) -> Option<Vec<&'a str>>;
79}
80
81pub trait RunState {
83 type Writer: std::io::Write;
85
86 fn make_writer(&mut self, parts: &[&str]) -> std::io::Result<Self::Writer>;
93
94 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#[derive(Clone, Copy, Debug)]
113pub enum GenerateSchema<'a> {
114 Yes {
115 feature: Option<&'a str>,
117 },
118
119 No,
120}
121
122pub 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 mut state: impl RunState,
146) -> Result<RunResult, Error> {
147 let definition = definitions.get(definition_path).ok_or_else(|| format!("definition for {definition_path} does not exist in spec"))?;
148
149 let local = map_namespace_local_to_string(map_namespace)?;
150
151 let mut run_result = RunResult {
152 num_generated_structs: 0,
153 num_generated_type_aliases: 0,
154 };
155
156 let path_parts: Vec<_> = definition_path.split('.').collect();
157 let namespace_parts: Vec<_> =
158 map_namespace.map_namespace(&path_parts).ok_or_else(|| format!("unexpected path {definition_path:?}"))?
159 .into_iter()
160 .collect();
161
162 let mut out = state.make_writer(&namespace_parts)?;
163
164 let type_name = path_parts.last().ok_or_else(|| format!("path for {definition_path} has no parts"))?;
165
166 let derives = get_derives(&definition.kind, definitions, map_namespace)?;
167
168 templates::type_header::generate(
169 &mut out,
170 definition_path,
171 definition.description.as_deref(),
172 derives,
173 vis,
174 )?;
175
176 match &definition.kind {
177 swagger20::SchemaKind::Properties(properties) => {
178 let (template_properties, resource_metadata, metadata_ty) = {
179 let mut result = Vec::with_capacity(properties.len());
180
181 let mut single_group_version_kind = match &definition.kubernetes_group_kind_versions[..] {
182 [group_version_kind] => Some((group_version_kind, false, false)),
183 _ => None,
184 };
185
186 let mut metadata_ty = None;
187
188 for (name, (schema, required)) in properties {
189 if name.0 == "apiVersion" {
190 if let Some((_, has_api_version, _)) = &mut single_group_version_kind {
191 *has_api_version = true;
192 continue;
193 }
194 }
195
196 if name.0 == "kind" {
197 if let Some((_, _, has_kind)) = &mut single_group_version_kind {
198 *has_kind = true;
199 continue;
200 }
201 }
202
203 let field_name = get_rust_ident(name);
204
205 let mut field_type_name = String::new();
206
207 let required = match required {
208 true => templates::PropertyRequired::Required {
209 is_default: is_default(&schema.kind, definitions, map_namespace)?,
210 },
211 false => templates::PropertyRequired::Optional,
212 };
213
214 if let templates::PropertyRequired::Optional = required {
215 field_type_name.push_str("Option<");
216 }
217
218 let type_name = get_rust_type(&schema.kind, map_namespace)?;
219
220 if name.0 == "metadata" {
221 metadata_ty = Some((type_name.clone(), required));
222 }
223
224 if let swagger20::SchemaKind::Ref(swagger20::RefPath { path, .. }) = &schema.kind {
226 match (&**definition_path, &**name, &**path) {
227 (
228 "io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1beta1.JSONSchemaProps",
229 "not",
230 "io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1beta1.JSONSchemaProps",
231 ) |
232 (
233 "io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.JSONSchemaProps",
234 "not",
235 "io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.JSONSchemaProps",
236 ) => {
237 field_type_name.push_str("std::boxed::Box<");
238 field_type_name.push_str(&type_name);
239 field_type_name.push('>');
240 },
241
242 _ => field_type_name.push_str(&type_name),
243 }
244 }
245 else {
246 field_type_name.push_str(&type_name);
247 }
248
249 if let templates::PropertyRequired::Optional = required {
250 field_type_name.push('>');
251 }
252
253 let is_flattened = matches!(&schema.kind, swagger20::SchemaKind::Ty(swagger20::Type::CustomResourceSubresources(_)));
254
255 result.push(templates::Property {
256 name,
257 comment: schema.description.as_deref(),
258 field_name,
259 field_type_name,
260 required,
261 is_flattened,
262 merge_type: &schema.merge_type,
263 });
264 }
265
266 let resource_metadata = match single_group_version_kind {
267 Some((single_group_version_kind, true, true)) =>
268 Some(if single_group_version_kind.group.is_empty() {
269 (
270 format!("{:?}", single_group_version_kind.version),
271 format!("{:?}", ""),
272 format!("{:?}", single_group_version_kind.kind),
273 format!("{:?}", single_group_version_kind.version),
274 definition.list_kind.as_ref().map(|kind| format!("{kind:?}")),
275 )
276 }
277 else {
278 (
279 format!("{:?}", format!("{}/{}", single_group_version_kind.group, single_group_version_kind.version)),
280 format!("{:?}", single_group_version_kind.group),
281 format!("{:?}", single_group_version_kind.kind),
282 format!("{:?}", single_group_version_kind.version),
283 definition.list_kind.as_ref().map(|kind| format!("{kind:?}")),
284 )
285 }),
286 Some((_, true, false)) => return Err(format!("{definition_path} has an apiVersion property but not a kind property").into()),
287 Some((_, false, true)) => return Err(format!("{definition_path} has a kind property but not an apiVersion property").into()),
288 Some((_, false, false)) | None => None,
289 };
290
291 (result, resource_metadata, metadata_ty)
292 };
293
294 templates::r#struct::generate(
295 &mut out,
296 vis,
297 type_name,
298 Default::default(),
299 &template_properties,
300 )?;
301
302 let mut namespace_or_cluster_scoped_url_path_segment_and_scope = vec![];
303 let mut subresource_url_path_segment_and_scope = vec![];
304
305 if !definition.kubernetes_group_kind_versions.is_empty() {
306 let mut kubernetes_group_kind_versions: Vec<_> = definition.kubernetes_group_kind_versions.iter().collect();
307 kubernetes_group_kind_versions.sort();
308
309 let mut operations_by_gkv: std::collections::BTreeMap<_, Vec<_>> = Default::default();
310 for operation in std::mem::take(operations) {
311 operations_by_gkv
312 .entry(operation.kubernetes_group_kind_version.clone())
313 .or_default()
314 .push(operation);
315 }
316
317 for kubernetes_group_kind_version in kubernetes_group_kind_versions {
318 if let Some(mut operations) = operations_by_gkv.remove(&Some(kubernetes_group_kind_version.clone())) {
319 operations.sort_by(|o1, o2| o1.id.cmp(&o2.id));
320
321 for operation in operations {
322 match operation.kubernetes_action {
324 Some(
325 swagger20::KubernetesAction::Delete |
326 swagger20::KubernetesAction::Get |
327 swagger20::KubernetesAction::Post |
328 swagger20::KubernetesAction::Put
329 ) => (),
330 _ => continue,
331 }
332 let mut components = operation.path.rsplit('/');
333 let components = (
334 components.next().expect("str::rsplit returns at least one component"),
335 components.next(),
336 components.next(),
337 components.next(),
338 );
339
340 let (url_path_segment_, scope_, url_path_segment_and_scope) = match components {
341 ("{name}", Some(url_path_segment), Some("{namespace}"), Some("namespaces")) =>
342 (
343 format!("{url_path_segment:?}"),
344 format!("{local}NamespaceResourceScope"),
345 &mut namespace_or_cluster_scoped_url_path_segment_and_scope,
346 ),
347
348 ("{name}", Some(url_path_segment), _, _) =>
349 (
350 format!("{url_path_segment:?}"),
351 format!("{local}ClusterResourceScope"),
352 &mut namespace_or_cluster_scoped_url_path_segment_and_scope,
353 ),
354
355 (url_path_segment, Some("{name}"), _, _) =>
356 (
357 format!("{url_path_segment:?}"),
358 format!("{local}SubResourceScope"),
359 &mut subresource_url_path_segment_and_scope,
360 ),
361
362 (url_path_segment, Some("{namespace}"), Some("namespaces"), _) =>
363 (
364 format!("{url_path_segment:?}"),
365 format!("{local}NamespaceResourceScope"),
366 &mut namespace_or_cluster_scoped_url_path_segment_and_scope,
367 ),
368
369 (url_path_segment, _, _, _) =>
370 (
371 format!("{url_path_segment:?}"),
372 format!("{local}ClusterResourceScope"),
373 &mut namespace_or_cluster_scoped_url_path_segment_and_scope,
374 ),
375 };
376
377 url_path_segment_and_scope.push((url_path_segment_, scope_));
378 }
379 }
380 }
381
382 *operations = operations_by_gkv.into_values().flatten().collect();
383 }
384
385 match &**definition_path {
386 "io.k8s.apimachinery.pkg.apis.meta.v1.APIGroup" |
387 "io.k8s.apimachinery.pkg.apis.meta.v1.APIGroupList" |
388 "io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" |
389 "io.k8s.apimachinery.pkg.apis.meta.v1.APIVersions" =>
390 namespace_or_cluster_scoped_url_path_segment_and_scope.push((r#""""#.to_owned(), format!("{local}ClusterResourceScope"))),
391 "io.k8s.apimachinery.pkg.apis.meta.v1.Status" =>
392 subresource_url_path_segment_and_scope.push((r#""status""#.to_owned(), format!("{local}SubResourceScope"))),
393 _ => (),
394 }
395
396 namespace_or_cluster_scoped_url_path_segment_and_scope.dedup();
397 subresource_url_path_segment_and_scope.dedup();
398
399 let template_resource_metadata = match (&resource_metadata, &metadata_ty) {
400 (
401 Some((api_version, group, kind, version, list_kind)),
402 Some((metadata_ty, templates::PropertyRequired::Required { is_default: _ })),
403 ) => Some(templates::ResourceMetadata {
404 api_version,
405 group,
406 kind,
407 version,
408 list_kind: list_kind.as_deref(),
409 metadata_ty: Some(metadata_ty),
410 url_path_segment_and_scope: match (&*namespace_or_cluster_scoped_url_path_segment_and_scope, &*subresource_url_path_segment_and_scope) {
411 ([(url_path_segment, scope)], _) |
412 ([], [(url_path_segment, scope)]) => (&**url_path_segment, &**scope),
413
414 ([], []) => return Err(format!(
415 "definition {definition_path} is a Resource but its URL path segment and scope could not be inferred").into()),
416 ([_, ..], _) => return Err(format!(
417 "definition {definition_path} is a Resource but was inferred to have multiple scopes {namespace_or_cluster_scoped_url_path_segment_and_scope:?}").into()),
418 ([], [_, ..]) => return Err(format!(
419 "definition {definition_path} is a Resource but was inferred to have multiple scopes {subresource_url_path_segment_and_scope:?}").into()),
420 },
421 }),
422
423 (Some(_), Some((_, templates::PropertyRequired::Optional | templates::PropertyRequired::OptionalDefault))) =>
424 return Err(format!("definition {definition_path} has optional metadata").into()),
425
426 (
427 Some((api_version, group, kind, version, list_kind)),
428 None,
429 ) => Some(templates::ResourceMetadata {
430 api_version,
431 group,
432 kind,
433 version,
434 list_kind: list_kind.as_deref(),
435 metadata_ty: None,
436 url_path_segment_and_scope: match (&*namespace_or_cluster_scoped_url_path_segment_and_scope, &*subresource_url_path_segment_and_scope) {
437 ([(url_path_segment, scope)], _) |
438 ([], [(url_path_segment, scope)]) => (&**url_path_segment, &**scope),
439
440 ([], []) => return Err(format!(
441 "definition {definition_path} is a Resource but its URL path segment and scope could not be inferred").into()),
442 ([_, _, ..], _) => return Err(format!(
443 "definition {definition_path} is a Resource but was inferred to have multiple scopes {namespace_or_cluster_scoped_url_path_segment_and_scope:?}").into()),
444 ([], [_, _, ..]) => return Err(format!(
445 "definition {definition_path} is a Resource but was inferred to have multiple scopes {subresource_url_path_segment_and_scope:?}").into()),
446 },
447 }),
448
449 (None, _) => None,
450 };
451
452 if let Some(template_resource_metadata) = &template_resource_metadata {
453 templates::impl_resource::generate(
454 &mut out,
455 type_name,
456 Default::default(),
457 map_namespace,
458 template_resource_metadata,
459 )?;
460
461 templates::impl_listable_resource::generate(
462 &mut out,
463 type_name,
464 Default::default(),
465 map_namespace,
466 template_resource_metadata,
467 )?;
468
469 templates::impl_metadata::generate(
470 &mut out,
471 type_name,
472 Default::default(),
473 map_namespace,
474 template_resource_metadata,
475 )?;
476 }
477
478 if definition.impl_deep_merge {
479 templates::struct_deep_merge::generate(
480 &mut out,
481 type_name,
482 Default::default(),
483 &template_properties,
484 map_namespace,
485 )?;
486 }
487
488 templates::impl_deserialize::generate(
489 &mut out,
490 type_name,
491 Default::default(),
492 &template_properties,
493 map_namespace,
494 template_resource_metadata.as_ref(),
495 )?;
496
497 templates::impl_serialize::generate(
498 &mut out,
499 type_name,
500 Default::default(),
501 &template_properties,
502 map_namespace,
503 template_resource_metadata.as_ref(),
504 )?;
505
506 run_result.num_generated_structs += 1;
507 },
508
509 swagger20::SchemaKind::Ref(_) => return Err(format!("{definition_path} is a Ref").into()),
510
511 swagger20::SchemaKind::Ty(swagger20::Type::IntOrString) => {
512 templates::int_or_string::generate(
513 &mut out,
514 type_name,
515 map_namespace,
516 )?;
517
518 run_result.num_generated_structs += 1;
519 },
520
521 swagger20::SchemaKind::Ty(swagger20::Type::JsonSchemaPropsOr(namespace, json_schema_props_or)) => {
522 let json_schema_props_or = match json_schema_props_or {
523 swagger20::JsonSchemaPropsOr::Array => templates::json_schema_props_or::Or::Array,
524 swagger20::JsonSchemaPropsOr::Bool => templates::json_schema_props_or::Or::Bool,
525 swagger20::JsonSchemaPropsOr::StringArray => templates::json_schema_props_or::Or::StringArray,
526 };
527
528 let json_schema_props_type_name =
529 get_fully_qualified_type_name(
530 &swagger20::RefPath {
531 path: format!("io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.{namespace}.JSONSchemaProps"),
532 can_be_default: None,
533 },
534 map_namespace);
535
536 templates::json_schema_props_or::generate(
537 &mut out,
538 type_name,
539 json_schema_props_or,
540 &json_schema_props_type_name,
541 map_namespace,
542 )?;
543
544 run_result.num_generated_structs += 1;
545 },
546
547 swagger20::SchemaKind::Ty(swagger20::Type::Patch) => {
548 templates::patch::generate(
549 &mut out,
550 type_name,
551 map_namespace,
552 )?;
553
554 run_result.num_generated_structs += 1;
555 },
556
557 swagger20::SchemaKind::Ty(swagger20::Type::WatchEvent(raw_extension_ref_path)) => {
558 let error_status_rust_type = get_rust_type(
559 &swagger20::SchemaKind::Ref(swagger20::RefPath {
560 path: "io.k8s.apimachinery.pkg.apis.meta.v1.Status".to_owned(),
561 can_be_default: None,
562 }),
563 map_namespace,
564 )?;
565
566 let error_other_rust_type = get_rust_type(
567 &swagger20::SchemaKind::Ref(raw_extension_ref_path.clone()),
568 map_namespace,
569 )?;
570
571 templates::watch_event::generate(
572 &mut out,
573 type_name,
574 &error_status_rust_type,
575 &error_other_rust_type,
576 map_namespace,
577 )?;
578
579 run_result.num_generated_structs += 1;
580 },
581
582 swagger20::SchemaKind::Ty(swagger20::Type::ListDef { metadata }) => {
583 let metadata_rust_type = get_rust_type(metadata, map_namespace)?;
584
585 let template_generics_where_part = format!("T: {local}ListableResource");
586 let template_generics = templates::Generics {
587 type_part: Some("T"),
588 where_part: Some(&template_generics_where_part),
589 };
590
591 let items_merge_type = swagger20::MergeType::List {
592 strategy: swagger20::KubernetesListType::Map,
593 keys: vec!["metadata().namespace".to_string(), "metadata().name".to_string()],
594 item_merge_type: Box::new(swagger20::MergeType::Default),
595 };
596
597 let template_properties = vec![
598 templates::Property {
599 name: "items",
600 comment: Some("List of objects."),
601 field_name: "items".into(),
602 field_type_name: "std::vec::Vec<T>".to_owned(),
603 required: templates::PropertyRequired::Required { is_default: true },
604 is_flattened: false,
605 merge_type: &items_merge_type,
606 },
607
608 templates::Property {
609 name: "metadata",
610 comment: Some("Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds"),
611 field_name: "metadata".into(),
612 field_type_name: (*metadata_rust_type).to_owned(),
613 required: templates::PropertyRequired::Required { is_default: true },
614 is_flattened: false,
615 merge_type: &swagger20::MergeType::Default,
616 },
617 ];
618
619 let template_resource_metadata = templates::ResourceMetadata {
620 api_version: "<T as crate::Resource>::API_VERSION",
621 group: "<T as crate::Resource>::GROUP",
622 kind: "<T as crate::ListableResource>::LIST_KIND",
623 version: "<T as crate::Resource>::VERSION",
624 list_kind: None,
625 metadata_ty: Some(&metadata_rust_type),
626 url_path_segment_and_scope: (r#""""#, "<T as crate::Resource>::Scope"),
627 };
628
629 templates::r#struct::generate(
630 &mut out,
631 vis,
632 type_name,
633 template_generics,
634 &template_properties,
635 )?;
636
637 templates::impl_resource::generate(
638 &mut out,
639 type_name,
640 template_generics,
641 map_namespace,
642 &template_resource_metadata,
643 )?;
644
645 templates::impl_listable_resource::generate(
646 &mut out,
647 type_name,
648 template_generics,
649 map_namespace,
650 &template_resource_metadata,
651 )?;
652
653 templates::impl_metadata::generate(
654 &mut out,
655 type_name,
656 template_generics,
657 map_namespace,
658 &template_resource_metadata,
659 )?;
660
661 if definition.impl_deep_merge {
662 let template_generics_where_part = format!("T: {local}DeepMerge + {local}Metadata<Ty = {local}apimachinery::pkg::apis::meta::v1::ObjectMeta> + {local}ListableResource");
663 let template_generics = templates::Generics {
664 where_part: Some(&template_generics_where_part),
665 ..template_generics
666 };
667
668 templates::struct_deep_merge::generate(
669 &mut out,
670 type_name,
671 template_generics,
672 &template_properties,
673 map_namespace,
674 )?;
675 }
676
677 {
678 let template_generics_where_part = format!("T: {local}serde::Deserialize<'de> + {local}ListableResource");
679 let template_generics = templates::Generics {
680 where_part: Some(&template_generics_where_part),
681 ..template_generics
682 };
683
684 templates::impl_deserialize::generate(
685 &mut out,
686 type_name,
687 template_generics,
688 &template_properties,
689 map_namespace,
690 Some(&template_resource_metadata),
691 )?;
692 }
693
694 {
695 let template_generics_where_part = format!("T: {local}serde::Serialize + {local}ListableResource");
696 let template_generics = templates::Generics {
697 where_part: Some(&template_generics_where_part),
698 ..template_generics
699 };
700
701 templates::impl_serialize::generate(
702 &mut out,
703 type_name,
704 template_generics,
705 &template_properties,
706 map_namespace,
707 Some(&template_resource_metadata),
708 )?;
709 }
710
711 run_result.num_generated_structs += 1;
712 },
713
714 swagger20::SchemaKind::Ty(swagger20::Type::ListRef { .. }) => return Err(format!("definition {definition_path} is a ListRef").into()),
715
716 swagger20::SchemaKind::Ty(_) => {
717 let inner_type_name = get_rust_type(&definition.kind, map_namespace)?;
718
719 let datetime_serialization_format = match (&**definition_path, &definition.kind) {
731 (
732 "io.k8s.apimachinery.pkg.apis.meta.v1.MicroTime",
733 swagger20::SchemaKind::Ty(swagger20::Type::String { format: Some(swagger20::StringFormat::DateTime) }),
734 ) => templates::DateTimeSerializationFormat::SixDecimalDigits,
735
736 (
737 "io.k8s.apimachinery.pkg.apis.meta.v1.Time",
738 swagger20::SchemaKind::Ty(swagger20::Type::String { format: Some(swagger20::StringFormat::DateTime) }),
739 ) => templates::DateTimeSerializationFormat::ZeroDecimalDigits,
740
741 _ => templates::DateTimeSerializationFormat::Default,
742 };
743
744 templates::newtype::generate(
745 &mut out,
746 vis,
747 type_name,
748 &inner_type_name,
749 datetime_serialization_format,
750 map_namespace,
751 )?;
752
753 run_result.num_generated_type_aliases += 1;
754 },
755 }
756
757 if let GenerateSchema::Yes { feature: schema_feature } = generate_schema {
758 match &definition.kind {
759 swagger20::SchemaKind::Properties(_) |
760 swagger20::SchemaKind::Ty(
761 swagger20::Type::Any |
762 swagger20::Type::Array { .. } |
763 swagger20::Type::Boolean |
764 swagger20::Type::Integer { .. } |
765 swagger20::Type::IntOrString |
766 swagger20::Type::Number { .. } |
767 swagger20::Type::Object { .. } |
768 swagger20::Type::String { .. } |
769 swagger20::Type::JsonSchemaPropsOr(_, _) |
770 swagger20::Type::Patch
771 ) => {
772 templates::impl_schema::generate(
773 &mut out,
774 type_name,
775 Default::default(),
776 definition_path,
777 definition,
778 schema_feature,
779 map_namespace,
780 )?;
781 }
782
783 swagger20::SchemaKind::Ty(swagger20::Type::WatchEvent(_)) => {
784 templates::impl_schema::generate(
785 &mut out,
786 type_name,
787 templates::Generics {
788 type_part: Some("T"),
789 where_part: None,
790 },
791 definition_path,
792 definition,
793 schema_feature,
794 map_namespace,
795 )?;
796 }
797
798 _ => (),
799 }
800 }
801
802 state.finish(out);
803
804 Ok(run_result)
805}
806
807fn map_namespace_local_to_string(map_namespace: &impl MapNamespace) -> Result<String, Error> {
808 let namespace_parts = map_namespace.map_namespace(&["io", "k8s"]).ok_or(r#"unexpected path "io.k8s""#)?;
809
810 let mut result = String::new();
811 for namespace_part in namespace_parts {
812 result.push_str(&get_rust_ident(namespace_part));
813 result.push_str("::");
814 }
815 Ok(result)
816}
817
818fn get_derives(
819 kind: &swagger20::SchemaKind,
820 definitions: &std::collections::BTreeMap<swagger20::DefinitionPath, swagger20::Schema>,
821 map_namespace: &impl MapNamespace,
822) -> Result<Option<templates::type_header::Derives>, Error> {
823 if matches!(kind, swagger20::SchemaKind::Ty(swagger20::Type::ListRef { .. })) {
824 return Ok(None);
826 }
827
828 let derive_clone =
829 evaluate_trait_bound(
830 kind,
831 true,
832 definitions,
833 map_namespace,
834 |_, _| Ok(true))?;
835
836 let derive_copy =
837 derive_clone &&
838 evaluate_trait_bound(
839 kind,
840 false,
841 definitions,
842 map_namespace,
843 |_, _| Ok(false))?;
844
845 #[allow(clippy::match_same_arms)]
846 let is_default = evaluate_trait_bound(kind, false, definitions, map_namespace, |kind, required| match kind {
847 _ if !required => Ok(true),
849
850 swagger20::SchemaKind::Ref(swagger20::RefPath { can_be_default: Some(can_be_default), .. }) => Ok(*can_be_default),
851
852 swagger20::SchemaKind::Ref(ref_path @ swagger20::RefPath { .. }) if ref_path.references_scope(map_namespace) => Ok(false),
853
854 swagger20::SchemaKind::Ref(ref_path @ swagger20::RefPath { .. })
856 if !ref_path.references_scope(map_namespace) && ref_path.path == "io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" => Ok(true),
857
858 swagger20::SchemaKind::Ref(ref_path @ swagger20::RefPath { .. }) if !ref_path.references_scope(map_namespace) => unreachable!(),
860
861 swagger20::SchemaKind::Ty(swagger20::Type::String { format: Some(swagger20::StringFormat::DateTime) }) => Ok(false),
863
864 swagger20::SchemaKind::Ty(
866 swagger20::Type::JsonSchemaPropsOr(_, _) |
867 swagger20::Type::Patch |
868 swagger20::Type::WatchEvent(_)
869 ) => Ok(false),
870
871 _ => Ok(true),
872 })?;
873 let derive_default =
874 is_default &&
875 !matches!(kind, swagger20::SchemaKind::Ty(swagger20::Type::IntOrString));
877
878 let derive_partial_eq =
879 evaluate_trait_bound(
880 kind,
881 true,
882 definitions,
883 map_namespace,
884 |_, _| Ok(true))?;
885
886 let derive_eq =
893 derive_partial_eq &&
894 matches!(kind, swagger20::SchemaKind::Ty(
895 swagger20::Type::IntOrString |
896 swagger20::Type::String { format: Some(swagger20::StringFormat::DateTime) }
897 ));
898
899 let derive_partial_ord =
900 derive_partial_eq &&
901 matches!(kind, swagger20::SchemaKind::Ty(swagger20::Type::String { format: Some(swagger20::StringFormat::DateTime) }));
902
903 let derive_ord = derive_partial_ord && derive_eq;
904
905 Ok(Some(templates::type_header::Derives {
906 clone: derive_clone,
907 copy: derive_copy,
908 default: derive_default,
909 eq: derive_eq,
910 ord: derive_ord,
911 partial_eq: derive_partial_eq,
912 partial_ord: derive_partial_ord,
913 }))
914}
915
916fn is_default(
917 kind: &swagger20::SchemaKind,
918 definitions: &std::collections::BTreeMap<swagger20::DefinitionPath, swagger20::Schema>,
919 map_namespace: &impl MapNamespace,
920) -> Result<bool, Error> {
921 #[allow(clippy::match_same_arms)]
922 evaluate_trait_bound(kind, false, definitions, map_namespace, |kind, required| match kind {
923 _ if !required => Ok(true),
925
926 swagger20::SchemaKind::Ref(swagger20::RefPath { can_be_default: Some(can_be_default), .. }) => Ok(*can_be_default),
927
928 swagger20::SchemaKind::Ref(ref_path @ swagger20::RefPath { .. }) if ref_path.references_scope(map_namespace) => Ok(false),
929
930 swagger20::SchemaKind::Ref(ref_path @ swagger20::RefPath { .. })
932 if !ref_path.references_scope(map_namespace) && ref_path.path == "io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" => Ok(true),
933
934 swagger20::SchemaKind::Ref(ref_path @ swagger20::RefPath { .. }) if !ref_path.references_scope(map_namespace) => unreachable!(),
936
937 swagger20::SchemaKind::Ty(swagger20::Type::String { format: Some(swagger20::StringFormat::DateTime) }) => Ok(false),
939
940 swagger20::SchemaKind::Ty(
942 swagger20::Type::JsonSchemaPropsOr(_, _) |
943 swagger20::Type::Patch |
944 swagger20::Type::WatchEvent(_)
945 ) => Ok(false),
946
947 _ => Ok(true),
948 })
949}
950
951fn evaluate_trait_bound(
952 kind: &swagger20::SchemaKind,
953 array_follows_elements: bool,
954 definitions: &std::collections::BTreeMap<swagger20::DefinitionPath, swagger20::Schema>,
955 map_namespace: &impl MapNamespace,
956 mut f: impl FnMut(&swagger20::SchemaKind, bool) -> Result<bool, Error>,
957) -> Result<bool, Error> {
958 fn evaluate_trait_bound_inner<'a>(
959 #[allow(clippy::ptr_arg)] kind: &std::borrow::Cow<'a, swagger20::SchemaKind>,
961 required: bool,
962 array_follows_elements: bool,
963 definitions: &std::collections::BTreeMap<swagger20::DefinitionPath, swagger20::Schema>,
964 map_namespace: &impl MapNamespace,
965 visited: &mut std::collections::BTreeSet<std::borrow::Cow<'a, swagger20::SchemaKind>>,
966 f: &mut impl FnMut(&swagger20::SchemaKind, bool) -> Result<bool, Error>,
967 ) -> Result<bool, Error> {
968 if !visited.insert(kind.clone()) {
969 return Ok(true);
971 }
972
973 match &**kind {
974 swagger20::SchemaKind::Properties(properties) => {
975 for (property_schema, property_required) in properties.values() {
976 let mut visited = visited.clone();
977 let field_bound =
978 evaluate_trait_bound_inner(
979 &std::borrow::Cow::Borrowed(&property_schema.kind),
980 required && *property_required,
981 array_follows_elements,
982 definitions,
983 map_namespace,
984 &mut visited,
985 f,
986 )?;
987 if !field_bound {
988 return Ok(false);
989 }
990 }
991
992 Ok(true)
993 },
994
995 swagger20::SchemaKind::Ref(ref_path @ swagger20::RefPath { .. }) if !ref_path.references_scope(map_namespace) => {
996 let trait_bound =
997 if let Some(target) = definitions.get(&*ref_path.path) {
998 let mut visited = visited.clone();
999 evaluate_trait_bound_inner(
1000 &std::borrow::Cow::Borrowed(&target.kind),
1001 required,
1002 array_follows_elements,
1003 definitions,
1004 map_namespace,
1005 &mut visited,
1006 f,
1007 )
1008 }
1009 else {
1010 f(kind, required)
1011 };
1012 trait_bound
1013 },
1014
1015 swagger20::SchemaKind::Ty(swagger20::Type::Array { items }) if array_follows_elements =>
1016 evaluate_trait_bound_inner(
1017 &std::borrow::Cow::Owned(items.kind.clone()),
1018 required,
1019 array_follows_elements,
1020 definitions,
1021 map_namespace,
1022 visited,
1023 f,
1024 ),
1025
1026 swagger20::SchemaKind::Ty(swagger20::Type::JsonSchemaPropsOr(namespace, _)) => {
1027 let json_schema_props_ref_path = swagger20::RefPath {
1028 path: format!("io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.{namespace}.JSONSchemaProps"),
1029 can_be_default: None,
1030 };
1031 let json_schema_props_bound =
1032 evaluate_trait_bound_inner(
1033 &std::borrow::Cow::Owned(swagger20::SchemaKind::Ref(json_schema_props_ref_path)),
1034 required,
1035 array_follows_elements,
1036 definitions,
1037 map_namespace,
1038 visited,
1039 f,
1040 )?;
1041 if !json_schema_props_bound {
1042 return Ok(false);
1043 }
1044
1045 f(kind, required)
1046 },
1047
1048 swagger20::SchemaKind::Ty(swagger20::Type::WatchEvent(raw_extension_ref_path)) => {
1049 let raw_extension_bound =
1050 evaluate_trait_bound_inner(
1051 &std::borrow::Cow::Owned(swagger20::SchemaKind::Ref(raw_extension_ref_path.clone())),
1052 required,
1053 array_follows_elements,
1054 definitions,
1055 map_namespace,
1056 visited,
1057 f,
1058 )?;
1059 if !raw_extension_bound {
1060 return Ok(false);
1061 }
1062
1063 f(kind, required)
1064 },
1065
1066 kind => f(kind, required),
1067 }
1068 }
1069
1070 let mut visited = Default::default();
1071 evaluate_trait_bound_inner(
1072 &std::borrow::Cow::Borrowed(kind),
1073 true,
1074 array_follows_elements,
1075 definitions,
1076 map_namespace,
1077 &mut visited,
1078 &mut f,
1079 )
1080}
1081
1082fn get_comment_text<'a>(s: &'a str, indent: &'a str) -> impl Iterator<Item = std::borrow::Cow<'static, str>> + 'a {
1083 s.lines().scan(true, move |previous_line_was_empty, line|
1084 if line.is_empty() {
1085 *previous_line_was_empty = true;
1086 Some("".into())
1087 }
1088 else {
1089 let line =
1090 line
1091 .replace('\\', r"\\")
1092 .replace('[', r"\[")
1093 .replace(']', r"\]")
1094 .replace('<', r"\<")
1095 .replace('>', r"\>")
1096 .replace('\t', " ")
1097 .replace("```", "");
1098
1099 let line =
1100 if *previous_line_was_empty && line.starts_with(" ") {
1101 format!(" {}", line.trim_start())
1103 }
1104 else {
1105 line
1106 };
1107 let line = line.trim_end();
1108
1109 *previous_line_was_empty = false;
1110
1111 Some(format!("{indent} {line}").into())
1112 })
1113}
1114
1115fn get_fully_qualified_type_name(
1116 ref_path: &swagger20::RefPath,
1117 map_namespace: &impl MapNamespace,
1118) -> String {
1119 let path_parts: Vec<_> = ref_path.path.split('.').collect();
1120 let namespace_parts = map_namespace.map_namespace(&path_parts[..(path_parts.len() - 1)]);
1121 if let Some(namespace_parts) = namespace_parts {
1122 let mut result = String::new();
1123 for namespace_part in namespace_parts {
1124 result.push_str(&get_rust_ident(namespace_part));
1125 result.push_str("::");
1126 }
1127 result.push_str(path_parts[path_parts.len() - 1]);
1128 result
1129 }
1130 else {
1131 let last_part = *path_parts.last().expect("str::split yields at least one item");
1132 last_part.to_owned()
1133 }
1134}
1135
1136pub fn get_rust_ident(name: &str) -> std::borrow::Cow<'static, str> {
1138 match name {
1140 "$ref" => return "ref_path".into(),
1141 "$schema" => return "schema".into(),
1142 "as" => return "as_".into(),
1143 "continue" => return "continue_".into(),
1144 "enum" => return "enum_".into(),
1145 "ref" => return "ref_".into(),
1146 "type" => return "type_".into(),
1147 _ => (),
1148 }
1149
1150 match name {
1154 "clusterIPs" => return "cluster_ips".into(),
1155 "externalIPs" => return "external_ips".into(),
1156 "hostIPs" => return "host_ips".into(),
1157 "nonResourceURLs" => return "non_resource_urls".into(),
1158 "podCIDRs" => return "pod_cidrs".into(),
1159 "podIPs" => return "pod_ips".into(),
1160 "serverAddressByClientCIDRs" => return "server_address_by_client_cidrs".into(),
1161 "targetWWNs" => return "target_wwns".into(),
1162 _ => (),
1163 }
1164
1165 let mut result = String::new();
1166
1167 let chars =
1168 name.chars()
1169 .zip(std::iter::once(None).chain(name.chars().map(|c| Some(c.is_uppercase()))))
1170 .zip(name.chars().skip(1).map(|c| Some(c.is_uppercase())).chain(std::iter::once(None)));
1171
1172 for ((c, previous), next) in chars {
1173 if c.is_uppercase() {
1174 match (previous, next) {
1175 (Some(false), _) |
1176 (Some(true), Some(false)) => result.push('_'),
1177 _ => (),
1178 }
1179
1180 result.extend(c.to_lowercase());
1181 }
1182 else {
1183 result.push(match c {
1184 '-' => '_',
1185 c => c,
1186 });
1187 }
1188 }
1189
1190 result.into()
1191}
1192
1193fn get_rust_type(
1194 schema_kind: &swagger20::SchemaKind,
1195 map_namespace: &impl MapNamespace,
1196) -> Result<std::borrow::Cow<'static, str>, Error> {
1197 let local = map_namespace_local_to_string(map_namespace)?;
1198
1199 match schema_kind {
1200 swagger20::SchemaKind::Properties(_) => Err("Nested anonymous types not supported".into()),
1201
1202 swagger20::SchemaKind::Ref(ref_path) =>
1203 Ok(get_fully_qualified_type_name(ref_path, map_namespace).into()),
1204
1205 swagger20::SchemaKind::Ty(swagger20::Type::Any) => Ok(format!("{local}serde_json::Value").into()),
1206
1207 swagger20::SchemaKind::Ty(swagger20::Type::Array { items }) =>
1208 Ok(format!("std::vec::Vec<{}>", get_rust_type(&items.kind, map_namespace)?).into()),
1209
1210 swagger20::SchemaKind::Ty(swagger20::Type::Boolean) => Ok("bool".into()),
1211
1212 swagger20::SchemaKind::Ty(swagger20::Type::Integer { format: swagger20::IntegerFormat::Int32 }) => Ok("i32".into()),
1213 swagger20::SchemaKind::Ty(swagger20::Type::Integer { format: swagger20::IntegerFormat::Int64 }) => Ok("i64".into()),
1214
1215 swagger20::SchemaKind::Ty(swagger20::Type::Number { format: swagger20::NumberFormat::Double }) => Ok("f64".into()),
1216
1217 swagger20::SchemaKind::Ty(swagger20::Type::Object { additional_properties }) =>
1218 Ok(format!("std::collections::BTreeMap<std::string::String, {}>", get_rust_type(&additional_properties.kind, map_namespace)?).into()),
1219
1220 swagger20::SchemaKind::Ty(swagger20::Type::String { format: Some(swagger20::StringFormat::Byte) }) =>
1221 Ok(format!("{local}ByteString").into()),
1222 swagger20::SchemaKind::Ty(swagger20::Type::String { format: Some(swagger20::StringFormat::DateTime) }) =>
1223 Ok(format!("{local}chrono::DateTime<{local}chrono::Utc>").into()),
1224 swagger20::SchemaKind::Ty(swagger20::Type::String { format: None }) => Ok("std::string::String".into()),
1225
1226 swagger20::SchemaKind::Ty(swagger20::Type::CustomResourceSubresources(namespace)) => {
1227 let namespace_parts =
1228 &["io", "k8s", "apiextensions_apiserver", "pkg", "apis", "apiextensions", namespace];
1229 let namespace_parts =
1230 map_namespace.map_namespace(namespace_parts)
1231 .ok_or_else(|| format!("unexpected path {:?}", namespace_parts.join(".")))?;
1232
1233 let mut result = String::new();
1234 for namespace_part in namespace_parts {
1235 result.push_str(&get_rust_ident(namespace_part));
1236 result.push_str("::");
1237 }
1238 result.push_str("CustomResourceSubresources");
1239 Ok(result.into())
1240 },
1241
1242 swagger20::SchemaKind::Ty(swagger20::Type::IntOrString) => Err("nothing should be trying to refer to IntOrString".into()),
1243
1244 swagger20::SchemaKind::Ty(swagger20::Type::JsonSchemaPropsOr(_, _)) => Err("JSON schema types not supported".into()),
1245 swagger20::SchemaKind::Ty(swagger20::Type::Patch) => Err("Patch type not supported".into()),
1246 swagger20::SchemaKind::Ty(swagger20::Type::WatchEvent(_)) => Err("WatchEvent type not supported".into()),
1247
1248 swagger20::SchemaKind::Ty(swagger20::Type::ListDef { .. }) => Err("ListDef type not supported".into()),
1249 swagger20::SchemaKind::Ty(swagger20::Type::ListRef { items }) =>
1250 Ok(format!("{local}List<{}>", get_rust_type(items, map_namespace)?).into()),
1251 }
1252}