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::Quantity) => {
548 templates::quantity::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::Patch) => {
558 templates::patch::generate(
559 &mut out,
560 type_name,
561 map_namespace,
562 )?;
563
564 run_result.num_generated_structs += 1;
565 },
566
567 swagger20::SchemaKind::Ty(swagger20::Type::WatchEvent(raw_extension_ref_path)) => {
568 let error_status_rust_type = get_rust_type(
569 &swagger20::SchemaKind::Ref(swagger20::RefPath {
570 path: "io.k8s.apimachinery.pkg.apis.meta.v1.Status".to_owned(),
571 can_be_default: None,
572 }),
573 map_namespace,
574 )?;
575
576 let error_other_rust_type = get_rust_type(
577 &swagger20::SchemaKind::Ref(raw_extension_ref_path.clone()),
578 map_namespace,
579 )?;
580
581 templates::watch_event::generate(
582 &mut out,
583 type_name,
584 &error_status_rust_type,
585 &error_other_rust_type,
586 map_namespace,
587 )?;
588
589 run_result.num_generated_structs += 1;
590 },
591
592 swagger20::SchemaKind::Ty(swagger20::Type::ListDef { metadata }) => {
593 let metadata_rust_type = get_rust_type(metadata, map_namespace)?;
594
595 let template_generics_where_part = format!("T: {local}ListableResource");
596 let template_generics = templates::Generics {
597 type_part: Some("T"),
598 where_part: Some(&template_generics_where_part),
599 };
600
601 let items_merge_type = swagger20::MergeType::List {
602 strategy: swagger20::KubernetesListType::Map,
603 keys: vec!["metadata().namespace".to_string(), "metadata().name".to_string()],
604 item_merge_type: Box::new(swagger20::MergeType::Default),
605 };
606
607 let template_properties = vec![
608 templates::Property {
609 name: "items",
610 comment: Some("List of objects."),
611 field_name: "items".into(),
612 field_type_name: "std::vec::Vec<T>".to_owned(),
613 required: templates::PropertyRequired::Required { is_default: true },
614 is_flattened: false,
615 merge_type: &items_merge_type,
616 },
617
618 templates::Property {
619 name: "metadata",
620 comment: Some("Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds"),
621 field_name: "metadata".into(),
622 field_type_name: (*metadata_rust_type).to_owned(),
623 required: templates::PropertyRequired::Required { is_default: true },
624 is_flattened: false,
625 merge_type: &swagger20::MergeType::Default,
626 },
627 ];
628
629 let template_resource_metadata = templates::ResourceMetadata {
630 api_version: "<T as crate::Resource>::API_VERSION",
631 group: "<T as crate::Resource>::GROUP",
632 kind: "<T as crate::ListableResource>::LIST_KIND",
633 version: "<T as crate::Resource>::VERSION",
634 list_kind: None,
635 metadata_ty: Some(&metadata_rust_type),
636 url_path_segment_and_scope: (r#""""#, "<T as crate::Resource>::Scope"),
637 };
638
639 templates::r#struct::generate(
640 &mut out,
641 vis,
642 type_name,
643 template_generics,
644 &template_properties,
645 )?;
646
647 templates::impl_resource::generate(
648 &mut out,
649 type_name,
650 template_generics,
651 map_namespace,
652 &template_resource_metadata,
653 )?;
654
655 templates::impl_listable_resource::generate(
656 &mut out,
657 type_name,
658 template_generics,
659 map_namespace,
660 &template_resource_metadata,
661 )?;
662
663 templates::impl_metadata::generate(
664 &mut out,
665 type_name,
666 template_generics,
667 map_namespace,
668 &template_resource_metadata,
669 )?;
670
671 if definition.impl_deep_merge {
672 let template_generics_where_part = format!("T: {local}DeepMerge + {local}Metadata<Ty = {local}apimachinery::pkg::apis::meta::v1::ObjectMeta> + {local}ListableResource");
673 let template_generics = templates::Generics {
674 where_part: Some(&template_generics_where_part),
675 ..template_generics
676 };
677
678 templates::struct_deep_merge::generate(
679 &mut out,
680 type_name,
681 template_generics,
682 &template_properties,
683 map_namespace,
684 )?;
685 }
686
687 {
688 let template_generics_where_part = format!("T: {local}serde::Deserialize<'de> + {local}ListableResource");
689 let template_generics = templates::Generics {
690 where_part: Some(&template_generics_where_part),
691 ..template_generics
692 };
693
694 templates::impl_deserialize::generate(
695 &mut out,
696 type_name,
697 template_generics,
698 &template_properties,
699 map_namespace,
700 Some(&template_resource_metadata),
701 )?;
702 }
703
704 {
705 let template_generics_where_part = format!("T: {local}serde::Serialize + {local}ListableResource");
706 let template_generics = templates::Generics {
707 where_part: Some(&template_generics_where_part),
708 ..template_generics
709 };
710
711 templates::impl_serialize::generate(
712 &mut out,
713 type_name,
714 template_generics,
715 &template_properties,
716 map_namespace,
717 Some(&template_resource_metadata),
718 )?;
719 }
720
721 run_result.num_generated_structs += 1;
722 },
723
724 swagger20::SchemaKind::Ty(swagger20::Type::ListRef { .. }) => return Err(format!("definition {definition_path} is a ListRef").into()),
725
726 swagger20::SchemaKind::Ty(_) => {
727 let inner_type_name = get_rust_type(&definition.kind, map_namespace)?;
728
729 let datetime_serialization_format = match (&**definition_path, &definition.kind) {
741 (
742 "io.k8s.apimachinery.pkg.apis.meta.v1.MicroTime",
743 swagger20::SchemaKind::Ty(swagger20::Type::String { format: Some(swagger20::StringFormat::DateTime) }),
744 ) => templates::DateTimeSerializationFormat::SixDecimalDigits,
745
746 (
747 "io.k8s.apimachinery.pkg.apis.meta.v1.Time",
748 swagger20::SchemaKind::Ty(swagger20::Type::String { format: Some(swagger20::StringFormat::DateTime) }),
749 ) => templates::DateTimeSerializationFormat::ZeroDecimalDigits,
750
751 _ => templates::DateTimeSerializationFormat::Default,
752 };
753
754 templates::newtype::generate(
755 &mut out,
756 vis,
757 type_name,
758 &inner_type_name,
759 datetime_serialization_format,
760 map_namespace,
761 )?;
762
763 run_result.num_generated_type_aliases += 1;
764 },
765 }
766
767 if let GenerateSchema::Yes { feature: schema_feature } = generate_schema {
768 match &definition.kind {
769 swagger20::SchemaKind::Properties(_) |
770 swagger20::SchemaKind::Ty(
771 swagger20::Type::Any |
772 swagger20::Type::Array { .. } |
773 swagger20::Type::Boolean |
774 swagger20::Type::Integer { .. } |
775 swagger20::Type::Number { .. } |
776 swagger20::Type::Object { .. } |
777 swagger20::Type::String { .. } |
778 swagger20::Type::IntOrString |
779 swagger20::Type::JsonSchemaPropsOr(_, _) |
780 swagger20::Type::Quantity |
781 swagger20::Type::Patch
782 ) => {
783 templates::impl_schema::generate(
784 &mut out,
785 type_name,
786 Default::default(),
787 definition_path,
788 definition,
789 schema_feature,
790 map_namespace,
791 )?;
792 }
793
794 swagger20::SchemaKind::Ty(swagger20::Type::WatchEvent(_)) => {
795 templates::impl_schema::generate(
796 &mut out,
797 type_name,
798 templates::Generics {
799 type_part: Some("T"),
800 where_part: None,
801 },
802 definition_path,
803 definition,
804 schema_feature,
805 map_namespace,
806 )?;
807 }
808
809 _ => (),
810 }
811 }
812
813 state.finish(out);
814
815 Ok(run_result)
816}
817
818fn map_namespace_local_to_string(map_namespace: &impl MapNamespace) -> Result<String, Error> {
819 let namespace_parts = map_namespace.map_namespace(&["io", "k8s"]).ok_or(r#"unexpected path "io.k8s""#)?;
820
821 let mut result = String::new();
822 for namespace_part in namespace_parts {
823 result.push_str(&get_rust_ident(namespace_part));
824 result.push_str("::");
825 }
826 Ok(result)
827}
828
829fn get_derives(
830 kind: &swagger20::SchemaKind,
831 definitions: &std::collections::BTreeMap<swagger20::DefinitionPath, swagger20::Schema>,
832 map_namespace: &impl MapNamespace,
833) -> Result<Option<templates::type_header::Derives>, Error> {
834 if matches!(kind, swagger20::SchemaKind::Ty(swagger20::Type::ListRef { .. })) {
835 return Ok(None);
837 }
838
839 let derive_clone =
840 evaluate_trait_bound(
841 kind,
842 true,
843 definitions,
844 map_namespace,
845 |_, _| Ok(true))?;
846
847 let derive_copy =
848 derive_clone &&
849 evaluate_trait_bound(
850 kind,
851 false,
852 definitions,
853 map_namespace,
854 |_, _| Ok(false))?;
855
856 #[allow(clippy::match_same_arms)]
857 let is_default = evaluate_trait_bound(kind, false, definitions, map_namespace, |kind, required| match kind {
858 _ if !required => Ok(true),
860
861 swagger20::SchemaKind::Ref(swagger20::RefPath { can_be_default: Some(can_be_default), .. }) => Ok(*can_be_default),
862
863 swagger20::SchemaKind::Ref(ref_path @ swagger20::RefPath { .. }) if ref_path.references_scope(map_namespace) => Ok(false),
864
865 swagger20::SchemaKind::Ref(ref_path @ swagger20::RefPath { .. })
867 if !ref_path.references_scope(map_namespace) && ref_path.path == "io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" => Ok(true),
868
869 swagger20::SchemaKind::Ref(ref_path @ swagger20::RefPath { .. }) if !ref_path.references_scope(map_namespace) => unreachable!(),
871
872 swagger20::SchemaKind::Ty(swagger20::Type::String { format: Some(swagger20::StringFormat::DateTime) }) => Ok(false),
874
875 swagger20::SchemaKind::Ty(
877 swagger20::Type::JsonSchemaPropsOr(_, _) |
878 swagger20::Type::Patch |
879 swagger20::Type::WatchEvent(_)
880 ) => Ok(false),
881
882 _ => Ok(true),
883 })?;
884 let derive_default =
885 is_default &&
886 !matches!(kind, swagger20::SchemaKind::Ty(swagger20::Type::IntOrString));
888
889 let derive_partial_eq =
890 evaluate_trait_bound(
891 kind,
892 true,
893 definitions,
894 map_namespace,
895 |_, _| Ok(true))?;
896
897 let derive_eq =
904 derive_partial_eq &&
905 matches!(kind, swagger20::SchemaKind::Ty(
906 swagger20::Type::IntOrString |
907 swagger20::Type::String { format: Some(swagger20::StringFormat::DateTime) }
908 ));
909
910 let derive_partial_ord =
911 derive_partial_eq &&
912 matches!(kind, swagger20::SchemaKind::Ty(swagger20::Type::String { format: Some(swagger20::StringFormat::DateTime) }));
913
914 let derive_ord = derive_partial_ord && derive_eq;
915
916 Ok(Some(templates::type_header::Derives {
917 clone: derive_clone,
918 copy: derive_copy,
919 default: derive_default,
920 eq: derive_eq,
921 ord: derive_ord,
922 partial_eq: derive_partial_eq,
923 partial_ord: derive_partial_ord,
924 }))
925}
926
927fn is_default(
928 kind: &swagger20::SchemaKind,
929 definitions: &std::collections::BTreeMap<swagger20::DefinitionPath, swagger20::Schema>,
930 map_namespace: &impl MapNamespace,
931) -> Result<bool, Error> {
932 #[allow(clippy::match_same_arms)]
933 evaluate_trait_bound(kind, false, definitions, map_namespace, |kind, required| match kind {
934 _ if !required => Ok(true),
936
937 swagger20::SchemaKind::Ref(swagger20::RefPath { can_be_default: Some(can_be_default), .. }) => Ok(*can_be_default),
938
939 swagger20::SchemaKind::Ref(ref_path @ swagger20::RefPath { .. }) if ref_path.references_scope(map_namespace) => Ok(false),
940
941 swagger20::SchemaKind::Ref(ref_path @ swagger20::RefPath { .. })
943 if !ref_path.references_scope(map_namespace) && ref_path.path == "io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" => Ok(true),
944
945 swagger20::SchemaKind::Ref(ref_path @ swagger20::RefPath { .. }) if !ref_path.references_scope(map_namespace) => unreachable!(),
947
948 swagger20::SchemaKind::Ty(swagger20::Type::String { format: Some(swagger20::StringFormat::DateTime) }) => Ok(false),
950
951 swagger20::SchemaKind::Ty(
953 swagger20::Type::JsonSchemaPropsOr(_, _) |
954 swagger20::Type::Patch |
955 swagger20::Type::WatchEvent(_)
956 ) => Ok(false),
957
958 _ => Ok(true),
959 })
960}
961
962fn evaluate_trait_bound(
963 kind: &swagger20::SchemaKind,
964 array_follows_elements: bool,
965 definitions: &std::collections::BTreeMap<swagger20::DefinitionPath, swagger20::Schema>,
966 map_namespace: &impl MapNamespace,
967 mut f: impl FnMut(&swagger20::SchemaKind, bool) -> Result<bool, Error>,
968) -> Result<bool, Error> {
969 fn evaluate_trait_bound_inner<'a>(
970 #[allow(clippy::ptr_arg)] kind: &std::borrow::Cow<'a, swagger20::SchemaKind>,
972 required: bool,
973 array_follows_elements: bool,
974 definitions: &std::collections::BTreeMap<swagger20::DefinitionPath, swagger20::Schema>,
975 map_namespace: &impl MapNamespace,
976 visited: &mut std::collections::BTreeSet<std::borrow::Cow<'a, swagger20::SchemaKind>>,
977 f: &mut impl FnMut(&swagger20::SchemaKind, bool) -> Result<bool, Error>,
978 ) -> Result<bool, Error> {
979 if !visited.insert(kind.clone()) {
980 return Ok(true);
982 }
983
984 match &**kind {
985 swagger20::SchemaKind::Properties(properties) => {
986 for (property_schema, property_required) in properties.values() {
987 let mut visited = visited.clone();
988 let field_bound =
989 evaluate_trait_bound_inner(
990 &std::borrow::Cow::Borrowed(&property_schema.kind),
991 required && *property_required,
992 array_follows_elements,
993 definitions,
994 map_namespace,
995 &mut visited,
996 f,
997 )?;
998 if !field_bound {
999 return Ok(false);
1000 }
1001 }
1002
1003 Ok(true)
1004 },
1005
1006 swagger20::SchemaKind::Ref(ref_path @ swagger20::RefPath { .. }) if !ref_path.references_scope(map_namespace) => {
1007 let trait_bound =
1008 if let Some(target) = definitions.get(&*ref_path.path) {
1009 let mut visited = visited.clone();
1010 evaluate_trait_bound_inner(
1011 &std::borrow::Cow::Borrowed(&target.kind),
1012 required,
1013 array_follows_elements,
1014 definitions,
1015 map_namespace,
1016 &mut visited,
1017 f,
1018 )
1019 }
1020 else {
1021 f(kind, required)
1022 };
1023 trait_bound
1024 },
1025
1026 swagger20::SchemaKind::Ty(swagger20::Type::Array { items }) if array_follows_elements =>
1027 evaluate_trait_bound_inner(
1028 &std::borrow::Cow::Owned(items.kind.clone()),
1029 required,
1030 array_follows_elements,
1031 definitions,
1032 map_namespace,
1033 visited,
1034 f,
1035 ),
1036
1037 swagger20::SchemaKind::Ty(swagger20::Type::JsonSchemaPropsOr(namespace, _)) => {
1038 let json_schema_props_ref_path = swagger20::RefPath {
1039 path: format!("io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.{namespace}.JSONSchemaProps"),
1040 can_be_default: None,
1041 };
1042 let json_schema_props_bound =
1043 evaluate_trait_bound_inner(
1044 &std::borrow::Cow::Owned(swagger20::SchemaKind::Ref(json_schema_props_ref_path)),
1045 required,
1046 array_follows_elements,
1047 definitions,
1048 map_namespace,
1049 visited,
1050 f,
1051 )?;
1052 if !json_schema_props_bound {
1053 return Ok(false);
1054 }
1055
1056 f(kind, required)
1057 },
1058
1059 swagger20::SchemaKind::Ty(swagger20::Type::WatchEvent(raw_extension_ref_path)) => {
1060 let raw_extension_bound =
1061 evaluate_trait_bound_inner(
1062 &std::borrow::Cow::Owned(swagger20::SchemaKind::Ref(raw_extension_ref_path.clone())),
1063 required,
1064 array_follows_elements,
1065 definitions,
1066 map_namespace,
1067 visited,
1068 f,
1069 )?;
1070 if !raw_extension_bound {
1071 return Ok(false);
1072 }
1073
1074 f(kind, required)
1075 },
1076
1077 kind => f(kind, required),
1078 }
1079 }
1080
1081 let mut visited = Default::default();
1082 evaluate_trait_bound_inner(
1083 &std::borrow::Cow::Borrowed(kind),
1084 true,
1085 array_follows_elements,
1086 definitions,
1087 map_namespace,
1088 &mut visited,
1089 &mut f,
1090 )
1091}
1092
1093fn get_comment_text<'a>(s: &'a str, indent: &'a str) -> impl Iterator<Item = std::borrow::Cow<'static, str>> + 'a {
1094 s.lines().scan(true, move |previous_line_was_empty, line|
1095 if line.is_empty() {
1096 *previous_line_was_empty = true;
1097 Some("".into())
1098 }
1099 else {
1100 let line =
1101 line
1102 .replace('\\', r"\\")
1103 .replace('[', r"\[")
1104 .replace(']', r"\]")
1105 .replace('<', r"\<")
1106 .replace('>', r"\>")
1107 .replace('\t', " ")
1108 .replace("```", "");
1109
1110 let line =
1111 if *previous_line_was_empty && line.starts_with(" ") {
1112 format!(" {}", line.trim_start())
1114 }
1115 else {
1116 line
1117 };
1118 let line = line.trim_end();
1119
1120 *previous_line_was_empty = false;
1121
1122 Some(format!("{indent} {line}").into())
1123 })
1124}
1125
1126fn get_fully_qualified_type_name(
1127 ref_path: &swagger20::RefPath,
1128 map_namespace: &impl MapNamespace,
1129) -> String {
1130 let path_parts: Vec<_> = ref_path.path.split('.').collect();
1131 let namespace_parts = map_namespace.map_namespace(&path_parts[..(path_parts.len() - 1)]);
1132 if let Some(namespace_parts) = namespace_parts {
1133 let mut result = String::new();
1134 for namespace_part in namespace_parts {
1135 result.push_str(&get_rust_ident(namespace_part));
1136 result.push_str("::");
1137 }
1138 result.push_str(path_parts[path_parts.len() - 1]);
1139 result
1140 }
1141 else {
1142 let last_part = *path_parts.last().expect("str::split yields at least one item");
1143 last_part.to_owned()
1144 }
1145}
1146
1147pub fn get_rust_ident(name: &str) -> std::borrow::Cow<'static, str> {
1149 match name {
1151 "$ref" => return "ref_path".into(),
1152 "$schema" => return "schema".into(),
1153 "as" => return "as_".into(),
1154 "continue" => return "continue_".into(),
1155 "enum" => return "enum_".into(),
1156 "ref" => return "ref_".into(),
1157 "type" => return "type_".into(),
1158 _ => (),
1159 }
1160
1161 match name {
1165 "clusterIPs" => return "cluster_ips".into(),
1166 "externalIPs" => return "external_ips".into(),
1167 "hostIPs" => return "host_ips".into(),
1168 "nonResourceURLs" => return "non_resource_urls".into(),
1169 "podCIDRs" => return "pod_cidrs".into(),
1170 "podIPs" => return "pod_ips".into(),
1171 "serverAddressByClientCIDRs" => return "server_address_by_client_cidrs".into(),
1172 "targetWWNs" => return "target_wwns".into(),
1173 _ => (),
1174 }
1175
1176 let mut result = String::new();
1177
1178 let chars =
1179 name.chars()
1180 .zip(std::iter::once(None).chain(name.chars().map(|c| Some(c.is_uppercase()))))
1181 .zip(name.chars().skip(1).map(|c| Some(c.is_uppercase())).chain(std::iter::once(None)));
1182
1183 for ((c, previous), next) in chars {
1184 if c.is_uppercase() {
1185 match (previous, next) {
1186 (Some(false), _) |
1187 (Some(true), Some(false)) => result.push('_'),
1188 _ => (),
1189 }
1190
1191 result.extend(c.to_lowercase());
1192 }
1193 else {
1194 result.push(match c {
1195 '-' => '_',
1196 c => c,
1197 });
1198 }
1199 }
1200
1201 result.into()
1202}
1203
1204fn get_rust_type(
1205 schema_kind: &swagger20::SchemaKind,
1206 map_namespace: &impl MapNamespace,
1207) -> Result<std::borrow::Cow<'static, str>, Error> {
1208 let local = map_namespace_local_to_string(map_namespace)?;
1209
1210 match schema_kind {
1211 swagger20::SchemaKind::Properties(_) => Err("Nested anonymous types not supported".into()),
1212
1213 swagger20::SchemaKind::Ref(ref_path) =>
1214 Ok(get_fully_qualified_type_name(ref_path, map_namespace).into()),
1215
1216 swagger20::SchemaKind::Ty(swagger20::Type::Any) => Ok(format!("{local}serde_json::Value").into()),
1217
1218 swagger20::SchemaKind::Ty(swagger20::Type::Array { items }) =>
1219 Ok(format!("std::vec::Vec<{}>", get_rust_type(&items.kind, map_namespace)?).into()),
1220
1221 swagger20::SchemaKind::Ty(swagger20::Type::Boolean) => Ok("bool".into()),
1222
1223 swagger20::SchemaKind::Ty(swagger20::Type::Integer { format: swagger20::IntegerFormat::Int32 }) => Ok("i32".into()),
1224 swagger20::SchemaKind::Ty(swagger20::Type::Integer { format: swagger20::IntegerFormat::Int64 }) => Ok("i64".into()),
1225
1226 swagger20::SchemaKind::Ty(swagger20::Type::Number { format: swagger20::NumberFormat::Double }) => Ok("f64".into()),
1227
1228 swagger20::SchemaKind::Ty(swagger20::Type::Object { additional_properties }) =>
1229 Ok(format!("std::collections::BTreeMap<std::string::String, {}>", get_rust_type(&additional_properties.kind, map_namespace)?).into()),
1230
1231 swagger20::SchemaKind::Ty(swagger20::Type::String { format: Some(swagger20::StringFormat::Byte) }) =>
1232 Ok(format!("{local}ByteString").into()),
1233 swagger20::SchemaKind::Ty(swagger20::Type::String { format: Some(swagger20::StringFormat::DateTime) }) =>
1234 Ok(format!("{local}chrono::DateTime<{local}chrono::Utc>").into()),
1235 swagger20::SchemaKind::Ty(swagger20::Type::String { format: None }) => Ok("std::string::String".into()),
1236
1237 swagger20::SchemaKind::Ty(swagger20::Type::CustomResourceSubresources(namespace)) => {
1238 let namespace_parts =
1239 &["io", "k8s", "apiextensions_apiserver", "pkg", "apis", "apiextensions", namespace];
1240 let namespace_parts =
1241 map_namespace.map_namespace(namespace_parts)
1242 .ok_or_else(|| format!("unexpected path {:?}", namespace_parts.join(".")))?;
1243
1244 let mut result = String::new();
1245 for namespace_part in namespace_parts {
1246 result.push_str(&get_rust_ident(namespace_part));
1247 result.push_str("::");
1248 }
1249 result.push_str("CustomResourceSubresources");
1250 Ok(result.into())
1251 },
1252
1253 swagger20::SchemaKind::Ty(swagger20::Type::IntOrString) => Err("nothing should be trying to refer to IntOrString".into()),
1254 swagger20::SchemaKind::Ty(swagger20::Type::JsonSchemaPropsOr(_, _)) => Err("JSON schema types not supported".into()),
1255 swagger20::SchemaKind::Ty(swagger20::Type::Quantity) => Err("nothing should be trying to refer to Quantity".into()),
1256
1257 swagger20::SchemaKind::Ty(swagger20::Type::Patch) => Err("Patch type not supported".into()),
1258 swagger20::SchemaKind::Ty(swagger20::Type::WatchEvent(_)) => Err("WatchEvent type not supported".into()),
1259
1260 swagger20::SchemaKind::Ty(swagger20::Type::ListDef { .. }) => Err("ListDef type not supported".into()),
1261 swagger20::SchemaKind::Ty(swagger20::Type::ListRef { items }) =>
1262 Ok(format!("{local}List<{}>", get_rust_type(items, map_namespace)?).into()),
1263 }
1264}