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 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 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 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 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 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 _ 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 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 swagger20::SchemaKind::Ref(ref_path @ swagger20::RefPath { .. }) if !ref_path.references_scope(map_namespace) => unreachable!(),
918
919 swagger20::SchemaKind::Ty(swagger20::Type::String { format: Some(swagger20::StringFormat::DateTime) }) => Ok(false),
921
922 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 !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 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 _ 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 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 swagger20::SchemaKind::Ref(ref_path @ swagger20::RefPath { .. }) if !ref_path.references_scope(map_namespace) => unreachable!(),
994
995 swagger20::SchemaKind::Ty(swagger20::Type::String { format: Some(swagger20::StringFormat::DateTime) }) => Ok(false),
997
998 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)] 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 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 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
1194pub fn get_rust_ident(name: &str) -> std::borrow::Cow<'static, str> {
1196 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 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}