1use ploidy_core::{
2 codegen::IntoCode,
3 ir::{ContainerView, HasTypeId, SchemaTypeView, View},
4};
5use proc_macro2::TokenStream;
6use quote::{ToTokens, TokenStreamExt, quote};
7
8use super::{
9 doc_attrs, enum_::CodegenEnum, graph::CodegenGraph, inlines::CodegenInlines,
10 naming::CodegenIdentUsage, primitive::CodegenPrimitive, ref_::CodegenRef,
11 struct_::CodegenStruct, tagged::CodegenTagged, untagged::CodegenUntagged,
12};
13
14#[derive(Debug)]
16pub struct CodegenSchemaType<'a> {
17 graph: &'a CodegenGraph<'a>,
18 ty: &'a SchemaTypeView<'a, 'a>,
19}
20
21impl<'a> CodegenSchemaType<'a> {
22 pub fn new(graph: &'a CodegenGraph<'a>, ty: &'a SchemaTypeView<'a, 'a>) -> Self {
23 Self { graph, ty }
24 }
25}
26
27impl ToTokens for CodegenSchemaType<'_> {
28 fn to_tokens(&self, tokens: &mut TokenStream) {
29 let ty = match self.ty {
30 SchemaTypeView::Struct(_, view) => {
31 CodegenStruct::new(self.graph, view).into_token_stream()
32 }
33 SchemaTypeView::Enum(_, view) => CodegenEnum::new(self.graph, view).into_token_stream(),
34 SchemaTypeView::Tagged(_, view) => {
35 CodegenTagged::new(self.graph, view).into_token_stream()
36 }
37 SchemaTypeView::Untagged(_, view) => {
38 CodegenUntagged::new(self.graph, view).into_token_stream()
39 }
40 SchemaTypeView::Container(_, ContainerView::Array(inner)) => {
41 let doc_attrs = inner.description().map(doc_attrs);
42 let type_name = CodegenIdentUsage::Type(self.graph.ident(self.ty.id()));
43 let inner_ty = inner.ty();
44 let inner_ref = CodegenRef::new(self.graph, &inner_ty);
45 quote! {
46 #doc_attrs
47 pub type #type_name = ::std::vec::Vec<#inner_ref>;
48 }
49 }
50 SchemaTypeView::Container(_, ContainerView::Map(inner)) => {
51 let doc_attrs = inner.description().map(doc_attrs);
52 let type_name = CodegenIdentUsage::Type(self.graph.ident(self.ty.id()));
53 let inner_ty = inner.ty();
54 let inner_ref = CodegenRef::new(self.graph, &inner_ty);
55 quote! {
56 #doc_attrs
57 pub type #type_name = ::std::collections::BTreeMap<::std::string::String, #inner_ref>;
58 }
59 }
60 SchemaTypeView::Container(_, ContainerView::Optional(inner)) => {
61 let doc_attrs = inner.description().map(doc_attrs);
62 let type_name = CodegenIdentUsage::Type(self.graph.ident(self.ty.id()));
63 let inner_ty = inner.ty();
64 let inner_ref = CodegenRef::new(self.graph, &inner_ty);
65 quote! {
66 #doc_attrs
67 pub type #type_name = ::std::option::Option<#inner_ref>;
68 }
69 }
70 SchemaTypeView::Primitive(_, view) => {
71 let type_name = CodegenIdentUsage::Type(self.graph.ident(self.ty.id()));
72 let primitive = CodegenPrimitive::new(self.graph, view);
73 quote! {
74 pub type #type_name = #primitive;
75 }
76 }
77 SchemaTypeView::Any(_, _) => {
78 let type_name = CodegenIdentUsage::Type(self.graph.ident(self.ty.id()));
79 quote! {
80 pub type #type_name = ::ploidy_util::serde_json::Value;
81 }
82 }
83 };
84 let inlines = CodegenInlines::for_schema_inlines(self.graph, self.ty.inlines().collect());
85 tokens.append_all(quote! {
86 #ty
87 #inlines
88 });
89 }
90}
91
92impl IntoCode for CodegenSchemaType<'_> {
93 type Code = (String, TokenStream);
94
95 fn into_code(self) -> Self::Code {
96 let mod_name = CodegenIdentUsage::Module(self.graph.ident(self.ty.id()));
97 (
98 format!("src/types/{}.rs", mod_name.display()),
99 self.into_token_stream(),
100 )
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 use ploidy_core::{
109 arena::Arena,
110 ir::{RawGraph, SchemaTypeView, Spec},
111 parse::Document,
112 };
113 use pretty_assertions::assert_eq;
114 use syn::parse_quote;
115
116 use crate::CodegenGraph;
117
118 #[test]
119 fn test_schema_inline_types_order() {
120 let doc = Document::from_yaml(indoc::indoc! {"
123 openapi: 3.0.0
124 info:
125 title: Test API
126 version: 1.0.0
127 paths: {}
128 components:
129 schemas:
130 Container:
131 type: object
132 properties:
133 zebra:
134 type: object
135 properties:
136 name:
137 type: string
138 mango:
139 type: object
140 properties:
141 name:
142 type: string
143 apple:
144 type: object
145 properties:
146 name:
147 type: string
148 "})
149 .unwrap();
150
151 let arena = Arena::new();
152 let spec = Spec::from_doc(&arena, &doc).unwrap();
153 let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
154
155 let schema = graph.schema("Container").unwrap();
156 let SchemaTypeView::Struct(_, _) = &schema else {
157 panic!("expected struct `Container`; got `{schema:?}`");
158 };
159
160 let codegen = CodegenSchemaType::new(&graph, &schema);
161
162 let actual: syn::File = parse_quote!(#codegen);
163 let expected: syn::File = parse_quote! {
167 #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize, ::ploidy_util::pointer::JsonPointee, ::ploidy_util::pointer::JsonPointerTarget)]
168 #[serde(crate = "::ploidy_util::serde")]
169 #[ploidy(pointer(crate = "::ploidy_util::pointer"))]
170 pub struct Container {
171 #[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
172 pub zebra: ::ploidy_util::absent::AbsentOr<crate::types::container::types::Zebra>,
173 #[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
174 pub mango: ::ploidy_util::absent::AbsentOr<crate::types::container::types::Mango>,
175 #[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
176 pub apple: ::ploidy_util::absent::AbsentOr<crate::types::container::types::Apple>,
177 }
178 pub mod types {
179 #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize, ::ploidy_util::pointer::JsonPointee, ::ploidy_util::pointer::JsonPointerTarget)]
180 #[serde(crate = "::ploidy_util::serde")]
181 #[ploidy(pointer(crate = "::ploidy_util::pointer"))]
182 pub struct Apple {
183 #[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
184 pub name: ::ploidy_util::absent::AbsentOr<::std::string::String>,
185 }
186 #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize, ::ploidy_util::pointer::JsonPointee, ::ploidy_util::pointer::JsonPointerTarget)]
187 #[serde(crate = "::ploidy_util::serde")]
188 #[ploidy(pointer(crate = "::ploidy_util::pointer"))]
189 pub struct Mango {
190 #[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
191 pub name: ::ploidy_util::absent::AbsentOr<::std::string::String>,
192 }
193 #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize, ::ploidy_util::pointer::JsonPointee, ::ploidy_util::pointer::JsonPointerTarget)]
194 #[serde(crate = "::ploidy_util::serde")]
195 #[ploidy(pointer(crate = "::ploidy_util::pointer"))]
196 pub struct Zebra {
197 #[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
198 pub name: ::ploidy_util::absent::AbsentOr<::std::string::String>,
199 }
200 }
201 };
202 assert_eq!(actual, expected);
203 }
204
205 #[test]
206 fn test_container_schema_emits_type_alias_with_inline_types() {
207 let doc = Document::from_yaml(indoc::indoc! {"
210 openapi: 3.0.0
211 info:
212 title: Test API
213 version: 1.0.0
214 paths: {}
215 components:
216 schemas:
217 InvalidParameters:
218 type: array
219 items:
220 type: object
221 required:
222 - name
223 - reason
224 properties:
225 name:
226 type: string
227 reason:
228 type: string
229 "})
230 .unwrap();
231
232 let arena = Arena::new();
233 let spec = Spec::from_doc(&arena, &doc).unwrap();
234 let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
235
236 let schema = graph.schema("InvalidParameters").unwrap();
237 let SchemaTypeView::Container(_, _) = &schema else {
238 panic!("expected container `InvalidParameters`; got `{schema:?}`");
239 };
240
241 let codegen = CodegenSchemaType::new(&graph, &schema);
242
243 let actual: syn::File = parse_quote!(#codegen);
244 let expected: syn::File = parse_quote! {
245 pub type InvalidParameters = ::std::vec::Vec<crate::types::invalid_parameters::types::Item>;
246 pub mod types {
247 #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize, ::ploidy_util::pointer::JsonPointee, ::ploidy_util::pointer::JsonPointerTarget)]
248 #[serde(crate = "::ploidy_util::serde")]
249 #[ploidy(pointer(crate = "::ploidy_util::pointer"))]
250 pub struct Item {
251 pub name: ::std::string::String,
252 pub reason: ::std::string::String,
253 }
254 }
255 };
256 assert_eq!(actual, expected);
257 }
258
259 #[test]
260 fn test_container_schema_emits_type_alias_without_inline_types() {
261 let doc = Document::from_yaml(indoc::indoc! {"
263 openapi: 3.0.0
264 info:
265 title: Test API
266 version: 1.0.0
267 paths: {}
268 components:
269 schemas:
270 Tags:
271 type: array
272 items:
273 type: string
274 "})
275 .unwrap();
276
277 let arena = Arena::new();
278 let spec = Spec::from_doc(&arena, &doc).unwrap();
279 let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
280
281 let schema = graph.schema("Tags").unwrap();
282 let SchemaTypeView::Container(_, _) = &schema else {
283 panic!("expected container `Tags`; got `{schema:?}`");
284 };
285
286 let codegen = CodegenSchemaType::new(&graph, &schema);
287
288 let actual: syn::File = parse_quote!(#codegen);
289 let expected: syn::File = parse_quote! {
290 pub type Tags = ::std::vec::Vec<::std::string::String>;
291 };
292 assert_eq!(actual, expected);
293 }
294
295 #[test]
296 fn test_container_schema_map_emits_type_alias() {
297 let doc = Document::from_yaml(indoc::indoc! {"
298 openapi: 3.0.0
299 info:
300 title: Test API
301 version: 1.0.0
302 paths: {}
303 components:
304 schemas:
305 Metadata:
306 type: object
307 additionalProperties:
308 type: string
309 "})
310 .unwrap();
311
312 let arena = Arena::new();
313 let spec = Spec::from_doc(&arena, &doc).unwrap();
314 let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
315
316 let schema = graph.schema("Metadata").unwrap();
317 let SchemaTypeView::Container(_, _) = &schema else {
318 panic!("expected container `Metadata`; got `{schema:?}`");
319 };
320
321 let codegen = CodegenSchemaType::new(&graph, &schema);
322
323 let actual: syn::File = parse_quote!(#codegen);
324 let expected: syn::File = parse_quote! {
325 pub type Metadata = ::std::collections::BTreeMap<::std::string::String, ::std::string::String>;
326 };
327 assert_eq!(actual, expected);
328 }
329
330 #[test]
331 fn test_container_nullable_schema() {
332 let doc = Document::from_yaml(indoc::indoc! {"
333 openapi: 3.1.0
334 info:
335 title: Test API
336 version: 1.0.0
337 paths: {}
338 components:
339 schemas:
340 NullableString:
341 type: [string, 'null']
342 NullableArray:
343 type: [array, 'null']
344 items:
345 type: string
346 NullableMap:
347 type: [object, 'null']
348 additionalProperties:
349 type: string
350 NullableOneOf:
351 oneOf:
352 - type: object
353 properties:
354 value:
355 type: string
356 - type: 'null'
357 "})
358 .unwrap();
359
360 let arena = Arena::new();
361 let spec = Spec::from_doc(&arena, &doc).unwrap();
362 let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
363
364 let schema = graph.schema("NullableString").unwrap();
366 let SchemaTypeView::Container(_, _) = &schema else {
367 panic!("expected container `NullableString`; got `{schema:?}`");
368 };
369 let codegen = CodegenSchemaType::new(&graph, &schema);
370 let actual: syn::File = parse_quote!(#codegen);
371 let expected: syn::File = parse_quote! {
372 pub type NullableString = ::std::option::Option<::std::string::String>;
373 };
374 assert_eq!(actual, expected);
375
376 let schema = graph.schema("NullableArray").unwrap();
378 let SchemaTypeView::Container(_, _) = &schema else {
379 panic!("expected container `NullableArray`; got `{schema:?}`");
380 };
381 let codegen = CodegenSchemaType::new(&graph, &schema);
382 let actual: syn::File = parse_quote!(#codegen);
383 let expected: syn::File = parse_quote! {
384 pub type NullableArray = ::std::option::Option<::std::vec::Vec<::std::string::String>>;
385 };
386 assert_eq!(actual, expected);
387
388 let schema = graph.schema("NullableMap").unwrap();
391 let SchemaTypeView::Container(_, _) = &schema else {
392 panic!("expected container `NullableMap`; got `{schema:?}`");
393 };
394 let codegen = CodegenSchemaType::new(&graph, &schema);
395 let actual: syn::File = parse_quote!(#codegen);
396 let expected: syn::File = parse_quote! {
397 pub type NullableMap = ::std::option::Option<::std::collections::BTreeMap<::std::string::String, ::std::string::String>>;
398 };
399 assert_eq!(actual, expected);
400
401 let schema = graph.schema("NullableOneOf").unwrap();
404 let SchemaTypeView::Container(_, _) = &schema else {
405 panic!("expected container `NullableOneOf`; got `{schema:?}`");
406 };
407 let codegen = CodegenSchemaType::new(&graph, &schema);
408 let actual: syn::File = parse_quote!(#codegen);
409 let expected: syn::File = parse_quote! {
410 pub type NullableOneOf = ::std::option::Option<crate::types::nullable_one_of::types::NullableOneOf>;
411 pub mod types {
412 #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize, ::ploidy_util::pointer::JsonPointee, ::ploidy_util::pointer::JsonPointerTarget)]
413 #[serde(crate = "::ploidy_util::serde")]
414 #[ploidy(pointer(crate = "::ploidy_util::pointer"))]
415 pub struct NullableOneOf {
416 #[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
417 pub value: ::ploidy_util::absent::AbsentOr<::std::string::String>,
418 }
419 }
420 };
421 assert_eq!(actual, expected);
422 }
423
424 #[test]
425 fn test_nullable_schema_value_name_collision() {
426 let doc = Document::from_yaml(indoc::indoc! {"
427 openapi: 3.1.0
428 info:
429 title: Test API
430 version: 1.0.0
431 paths: {}
432 components:
433 schemas:
434 NullableThing:
435 oneOf:
436 - type: object
437 properties:
438 value:
439 type: object
440 properties:
441 id:
442 type: string
443 - type: 'null'
444 "})
445 .unwrap();
446
447 let arena = Arena::new();
448 let spec = Spec::from_doc(&arena, &doc).unwrap();
449 let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
450
451 let schema = graph.schema("NullableThing").unwrap();
452 let SchemaTypeView::Container(_, _) = &schema else {
453 panic!("expected container `NullableThing`; got `{schema:?}`");
454 };
455
456 let codegen = CodegenSchemaType::new(&graph, &schema);
457
458 let actual: syn::File = parse_quote!(#codegen);
459 let expected: syn::File = parse_quote! {
460 pub type NullableThing = ::std::option::Option<crate::types::nullable_thing::types::NullableThing>;
461 pub mod types {
462 #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize, ::ploidy_util::pointer::JsonPointee, ::ploidy_util::pointer::JsonPointerTarget)]
463 #[serde(crate = "::ploidy_util::serde")]
464 #[ploidy(pointer(crate = "::ploidy_util::pointer"))]
465 pub struct NullableThing {
466 #[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
467 pub value: ::ploidy_util::absent::AbsentOr<crate::types::nullable_thing::types::Value>,
468 }
469 #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize, ::ploidy_util::pointer::JsonPointee, ::ploidy_util::pointer::JsonPointerTarget)]
470 #[serde(crate = "::ploidy_util::serde")]
471 #[ploidy(pointer(crate = "::ploidy_util::pointer"))]
472 pub struct Value {
473 #[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
474 pub id: ::ploidy_util::absent::AbsentOr<::std::string::String>,
475 }
476 }
477 };
478 assert_eq!(actual, expected);
479 }
480
481 #[test]
482 fn test_untagged_inline_variants_use_parent_name() {
483 let doc = Document::from_yaml(indoc::indoc! {"
484 openapi: 3.0.0
485 info:
486 title: Test API
487 version: 1.0.0
488 paths: {}
489 components:
490 schemas:
491 Pet:
492 oneOf:
493 - type: object
494 properties:
495 bark:
496 type: string
497 - type: object
498 properties:
499 meow:
500 type: string
501 "})
502 .unwrap();
503
504 let arena = Arena::new();
505 let spec = Spec::from_doc(&arena, &doc).unwrap();
506 let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
507
508 let schema = graph.schema("Pet").unwrap();
509 let SchemaTypeView::Untagged(_, _) = &schema else {
510 panic!("expected untagged `Pet`; got `{schema:?}`");
511 };
512
513 let codegen = CodegenSchemaType::new(&graph, &schema);
514
515 let actual: syn::File = parse_quote!(#codegen);
516 let expected: syn::File = parse_quote! {
517 #[derive(Debug, Clone, PartialEq, Eq, Hash, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize, ::ploidy_util::pointer::JsonPointee, ::ploidy_util::pointer::JsonPointerTarget)]
518 #[serde(crate = "::ploidy_util::serde", untagged)]
519 #[ploidy(pointer(crate = "::ploidy_util::pointer", untagged))]
520 pub enum Pet {
521 Pet1(crate::types::pet::types::Pet1),
522 Pet2(crate::types::pet::types::Pet2)
523 }
524 pub mod types {
525 #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize, ::ploidy_util::pointer::JsonPointee, ::ploidy_util::pointer::JsonPointerTarget)]
526 #[serde(crate = "::ploidy_util::serde")]
527 #[ploidy(pointer(crate = "::ploidy_util::pointer"))]
528 pub struct Pet1 {
529 #[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
530 pub bark: ::ploidy_util::absent::AbsentOr<::std::string::String>,
531 }
532 #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize, ::ploidy_util::pointer::JsonPointee, ::ploidy_util::pointer::JsonPointerTarget)]
533 #[serde(crate = "::ploidy_util::serde")]
534 #[ploidy(pointer(crate = "::ploidy_util::pointer"))]
535 pub struct Pet2 {
536 #[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
537 pub meow: ::ploidy_util::absent::AbsentOr<::std::string::String>,
538 }
539 }
540 };
541 assert_eq!(actual, expected);
542 }
543
544 #[test]
545 fn test_any_of_inline_fields_use_parent_name() {
546 let doc = Document::from_yaml(indoc::indoc! {"
547 openapi: 3.0.0
548 info:
549 title: Test API
550 version: 1.0.0
551 paths: {}
552 components:
553 schemas:
554 Pet:
555 anyOf:
556 - type: object
557 properties:
558 bark:
559 type: string
560 - type: object
561 properties:
562 meow:
563 type: string
564 "})
565 .unwrap();
566
567 let arena = Arena::new();
568 let spec = Spec::from_doc(&arena, &doc).unwrap();
569 let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
570
571 let schema = graph.schema("Pet").unwrap();
572 let SchemaTypeView::Struct(_, _) = &schema else {
573 panic!("expected struct `Pet`; got `{schema:?}`");
574 };
575
576 let codegen = CodegenSchemaType::new(&graph, &schema);
577
578 let actual: syn::File = parse_quote!(#codegen);
579 let expected: syn::File = parse_quote! {
580 #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize, ::ploidy_util::pointer::JsonPointee, ::ploidy_util::pointer::JsonPointerTarget)]
581 #[serde(crate = "::ploidy_util::serde")]
582 #[ploidy(pointer(crate = "::ploidy_util::pointer"))]
583 pub struct Pet {
584 #[serde(flatten, default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
585 #[ploidy(pointer(flatten))]
586 pub pet_1: ::ploidy_util::absent::AbsentOr<crate::types::pet::types::Pet1>,
587 #[serde(flatten, default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
588 #[ploidy(pointer(flatten))]
589 pub pet_2: ::ploidy_util::absent::AbsentOr<crate::types::pet::types::Pet2>,
590 }
591 pub mod types {
592 #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize, ::ploidy_util::pointer::JsonPointee, ::ploidy_util::pointer::JsonPointerTarget)]
593 #[serde(crate = "::ploidy_util::serde")]
594 #[ploidy(pointer(crate = "::ploidy_util::pointer"))]
595 pub struct Pet1 {
596 #[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
597 pub bark: ::ploidy_util::absent::AbsentOr<::std::string::String>,
598 }
599 #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize, ::ploidy_util::pointer::JsonPointee, ::ploidy_util::pointer::JsonPointerTarget)]
600 #[serde(crate = "::ploidy_util::serde")]
601 #[ploidy(pointer(crate = "::ploidy_util::pointer"))]
602 pub struct Pet2 {
603 #[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
604 pub meow: ::ploidy_util::absent::AbsentOr<::std::string::String>,
605 }
606 }
607 };
608 assert_eq!(actual, expected);
609 }
610
611 #[test]
612 fn test_container_schema_preserves_description() {
613 let doc = Document::from_yaml(indoc::indoc! {"
614 openapi: 3.0.0
615 info:
616 title: Test API
617 version: 1.0.0
618 paths: {}
619 components:
620 schemas:
621 Tags:
622 description: A list of tags.
623 type: array
624 items:
625 type: string
626 "})
627 .unwrap();
628
629 let arena = Arena::new();
630 let spec = Spec::from_doc(&arena, &doc).unwrap();
631 let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
632
633 let schema = graph.schema("Tags").unwrap();
634 let SchemaTypeView::Container(_, _) = &schema else {
635 panic!("expected container `Tags`; got `{schema:?}`");
636 };
637
638 let codegen = CodegenSchemaType::new(&graph, &schema);
639
640 let actual: syn::File = parse_quote!(#codegen);
641 let expected: syn::File = parse_quote! {
642 #[doc = "A list of tags."]
643 pub type Tags = ::std::vec::Vec<::std::string::String>;
644 };
645 assert_eq!(actual, expected);
646 }
647
648 #[test]
649 fn test_case_colliding_fields_uniquify_inline_type_names() {
650 let doc = Document::from_yaml(indoc::indoc! {"
654 openapi: 3.0.0
655 info:
656 title: Test API
657 version: 1.0.0
658 paths: {}
659 components:
660 schemas:
661 Qux:
662 type: object
663 properties:
664 fooBar:
665 type: array
666 items:
667 type: object
668 properties:
669 zoom:
670 type: string
671 foo_bar:
672 type: array
673 items:
674 type: object
675 properties:
676 blagh:
677 type: string
678 "})
679 .unwrap();
680
681 let arena = Arena::new();
682 let spec = Spec::from_doc(&arena, &doc).unwrap();
683 let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
684
685 let schema = graph.schema("Qux").unwrap();
686 let SchemaTypeView::Struct(_, _) = &schema else {
687 panic!("expected struct `Qux`; got `{schema:?}`");
688 };
689
690 let codegen = CodegenSchemaType::new(&graph, &schema);
691
692 let actual: syn::File = parse_quote!(#codegen);
693 let expected: syn::File = parse_quote! {
694 #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize, ::ploidy_util::pointer::JsonPointee, ::ploidy_util::pointer::JsonPointerTarget)]
695 #[serde(crate = "::ploidy_util::serde")]
696 #[ploidy(pointer(crate = "::ploidy_util::pointer"))]
697 pub struct Qux {
698 #[serde(rename = "fooBar", default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
699 #[ploidy(pointer(rename = "fooBar"))]
700 pub foo_bar: ::ploidy_util::absent::AbsentOr<::std::vec::Vec<crate::types::qux::types::FooBarItem>>,
701 #[serde(rename = "foo_bar", default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
702 #[ploidy(pointer(rename = "foo_bar"))]
703 pub foo_bar_2: ::ploidy_util::absent::AbsentOr<::std::vec::Vec<crate::types::qux::types::FooBar2Item>>,
704 }
705 pub mod types {
706 #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize, ::ploidy_util::pointer::JsonPointee, ::ploidy_util::pointer::JsonPointerTarget)]
707 #[serde(crate = "::ploidy_util::serde")]
708 #[ploidy(pointer(crate = "::ploidy_util::pointer"))]
709 pub struct FooBar2Item {
710 #[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
711 pub blagh: ::ploidy_util::absent::AbsentOr<::std::string::String>,
712 }
713 #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize, ::ploidy_util::pointer::JsonPointee, ::ploidy_util::pointer::JsonPointerTarget)]
714 #[serde(crate = "::ploidy_util::serde")]
715 #[ploidy(pointer(crate = "::ploidy_util::pointer"))]
716 pub struct FooBarItem {
717 #[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
718 pub zoom: ::ploidy_util::absent::AbsentOr<::std::string::String>,
719 }
720 }
721 };
722 assert_eq!(actual, expected);
723 }
724
725 #[test]
726 fn test_colliding_inline_paths_uniquify_inline_type_names() {
727 let doc = Document::from_yaml(indoc::indoc! {"
728 openapi: 3.0.0
729 info:
730 title: Collision API
731 version: 1.0.0
732 paths: {}
733 components:
734 schemas:
735 Qux:
736 type: object
737 properties:
738 fooItem:
739 type: object
740 properties:
741 direct:
742 type: string
743 foo:
744 type: array
745 items:
746 type: object
747 properties:
748 nested:
749 type: string
750 "})
751 .unwrap();
752
753 let arena = Arena::new();
754 let spec = Spec::from_doc(&arena, &doc).unwrap();
755 let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
756
757 let schema = graph.schema("Qux").unwrap();
758 let SchemaTypeView::Struct(_, _) = &schema else {
759 panic!("expected struct `Qux`; got `{schema:?}`");
760 };
761
762 let codegen = CodegenSchemaType::new(&graph, &schema);
763
764 let actual: syn::File = parse_quote!(#codegen);
765 let expected: syn::File = parse_quote! {
766 #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize, ::ploidy_util::pointer::JsonPointee, ::ploidy_util::pointer::JsonPointerTarget)]
767 #[serde(crate = "::ploidy_util::serde")]
768 #[ploidy(pointer(crate = "::ploidy_util::pointer"))]
769 pub struct Qux {
770 #[serde(rename = "fooItem", default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
771 #[ploidy(pointer(rename = "fooItem"))]
772 pub foo_item: ::ploidy_util::absent::AbsentOr<crate::types::qux::types::FooItem>,
773 #[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
774 pub foo: ::ploidy_util::absent::AbsentOr<::std::vec::Vec<crate::types::qux::types::FooItem2>>,
775 }
776 pub mod types {
777 #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize, ::ploidy_util::pointer::JsonPointee, ::ploidy_util::pointer::JsonPointerTarget)]
778 #[serde(crate = "::ploidy_util::serde")]
779 #[ploidy(pointer(crate = "::ploidy_util::pointer"))]
780 pub struct FooItem {
781 #[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
782 pub direct: ::ploidy_util::absent::AbsentOr<::std::string::String>,
783 }
784 #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize, ::ploidy_util::pointer::JsonPointee, ::ploidy_util::pointer::JsonPointerTarget)]
785 #[serde(crate = "::ploidy_util::serde")]
786 #[ploidy(pointer(crate = "::ploidy_util::pointer"))]
787 pub struct FooItem2 {
788 #[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
789 pub nested: ::ploidy_util::absent::AbsentOr<::std::string::String>,
790 }
791 }
792 };
793 assert_eq!(actual, expected);
794 }
795
796 #[test]
797 fn test_tagged_common_inline_field_codegen() {
798 let doc = Document::from_yaml(indoc::indoc! {"
802 openapi: 3.0.0
803 info:
804 title: Test API
805 version: 1.0.0
806 paths: {}
807 components:
808 schemas:
809 Dog:
810 type: object
811 properties:
812 kind:
813 type: string
814 bark:
815 type: string
816 Pet:
817 oneOf:
818 - $ref: '#/components/schemas/Dog'
819 discriminator:
820 propertyName: kind
821 mapping:
822 dog: '#/components/schemas/Dog'
823 properties:
824 metadata:
825 type: object
826 properties:
827 source:
828 type: string
829 "})
830 .unwrap();
831
832 let arena = Arena::new();
833 let spec = Spec::from_doc(&arena, &doc).unwrap();
834 let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
835
836 let schema = graph.schema("Pet").unwrap();
837 let SchemaTypeView::Tagged(_, _) = &schema else {
838 panic!("expected tagged `Pet`; got `{schema:?}`");
839 };
840
841 let codegen = CodegenSchemaType::new(&graph, &schema);
842
843 let actual: syn::File = parse_quote!(#codegen);
844 let expected: syn::File = parse_quote! {
845 #[derive(Debug, Clone, PartialEq, Eq, Hash, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize, ::ploidy_util::pointer::JsonPointee, ::ploidy_util::pointer::JsonPointerTarget)]
846 #[serde(crate = "::ploidy_util::serde", tag = "kind")]
847 #[ploidy(pointer(crate = "::ploidy_util::pointer", tag = "kind"))]
848 pub enum Pet {
849 #[serde(rename = "dog")]
850 #[ploidy(pointer(rename = "dog"))]
851 Dog(crate::types::Dog),
852 }
853
854 impl ::std::convert::From<crate::types::Dog> for Pet {
855 fn from(value: crate::types::Dog) -> Self {
856 Self::Dog(value)
857 }
858 }
859
860 pub mod types {
861 #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize, ::ploidy_util::pointer::JsonPointee, ::ploidy_util::pointer::JsonPointerTarget)]
862 #[serde(crate = "::ploidy_util::serde")]
863 #[ploidy(pointer(crate = "::ploidy_util::pointer"))]
864 pub struct Metadata {
865 #[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
866 pub source: ::ploidy_util::absent::AbsentOr<::std::string::String>,
867 }
868 }
869 };
870 assert_eq!(actual, expected);
871 }
872}