use std::borrow::Cow;
use itertools::Itertools;
use ploidy_core::{
codegen::UniqueNames,
ir::{
ContainerView, InlineTypeView, SchemaTypeView, StructFieldName, StructFieldNameHint,
StructFieldView, StructView, TypeView,
},
};
use proc_macro2::TokenStream;
use quote::{ToTokens, TokenStreamExt, quote};
use super::{
derives::ExtraDerive,
doc_attrs,
ext::ViewExt,
naming::{CodegenIdentRef, CodegenIdentScope, CodegenIdentUsage, CodegenTypeName},
ref_::CodegenRef,
};
#[derive(Clone, Debug)]
pub struct CodegenStruct<'a> {
name: CodegenTypeName<'a>,
ty: &'a StructView<'a>,
}
impl<'a> CodegenStruct<'a> {
pub fn new(name: CodegenTypeName<'a>, ty: &'a StructView<'a>) -> Self {
Self { name, ty }
}
}
impl ToTokens for CodegenStruct<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let unique = UniqueNames::new();
let mut scope = {
if self.ty.fields().any(|f| {
matches!(
f.name(),
StructFieldName::Hint(StructFieldNameHint::AdditionalProperties)
)
}) {
CodegenIdentScope::with_reserved(&unique, &["additional_properties"])
} else {
CodegenIdentScope::new(&unique)
}
};
let fields = self
.ty
.fields()
.filter(|field| !field.tag())
.map(|field| {
let doc_attrs = field.description().map(doc_attrs);
let name = match field.name() {
StructFieldName::Name(n) => Cow::Owned(scope.uniquify(n)),
StructFieldName::Hint(hint) => CodegenIdentRef::from_field_name_hint(hint),
};
let field_name = CodegenIdentUsage::Field(&name);
let serde_attr = SerdeStructFieldAttr::new(field_name, &field);
let ty = CodegenField::new(&field);
quote! {
#doc_attrs
#serde_attr
pub #field_name: #ty,
}
})
.collect_vec();
let mut extra_derives = vec![];
let all_hashable = self.ty.hashable();
if all_hashable {
extra_derives.push(ExtraDerive::Eq);
extra_derives.push(ExtraDerive::Hash);
}
let all_optional = self.ty.fields().filter(|f| !f.tag()).all(|f| !f.required());
if all_optional || self.ty.defaultable() {
extra_derives.push(ExtraDerive::Default);
}
let type_name = &self.name;
let doc_attrs = self.ty.description().map(doc_attrs);
tokens.append_all(quote! {
#doc_attrs
#[derive(Debug, Clone, PartialEq, #(#extra_derives,)* ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct #type_name {
#(#fields)*
}
});
}
}
#[derive(Debug)]
struct CodegenField<'view, 'a> {
field: &'a StructFieldView<'view, 'a>,
}
impl<'view, 'a> CodegenField<'view, 'a> {
fn new(field: &'a StructFieldView<'view, 'a>) -> Self {
Self { field }
}
fn needs_box(&self) -> bool {
let ty = std::iter::successors(Some(self.field.ty()), |ty| match ty {
TypeView::Schema(SchemaTypeView::Container(_, ContainerView::Optional(inner))) => {
Some(inner.ty())
}
TypeView::Inline(InlineTypeView::Container(_, ContainerView::Optional(inner))) => {
Some(inner.ty())
}
_ => None,
})
.last() .unwrap();
match ty {
TypeView::Schema(SchemaTypeView::Container(_, container))
| TypeView::Inline(InlineTypeView::Container(_, container)) => {
!matches!(container, ContainerView::Array(_) | ContainerView::Map(_))
}
TypeView::Inline(InlineTypeView::Primitive(..) | InlineTypeView::Any(..))
| TypeView::Schema(SchemaTypeView::Primitive(..) | SchemaTypeView::Any(..)) => false,
_ => self.field.needs_indirection(),
}
}
}
impl ToTokens for CodegenField<'_, '_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let (ty, nullable) =
std::iter::successors(Some((self.field.ty(), false)), |(ty, _)| match ty {
TypeView::Schema(SchemaTypeView::Container(_, ContainerView::Optional(inner))) => {
Some((inner.ty(), true))
}
TypeView::Inline(InlineTypeView::Container(_, ContainerView::Optional(inner))) => {
Some((inner.ty(), true))
}
_ => None,
})
.last() .unwrap();
let inner_ref = CodegenRef::new(&ty);
let inner = if self.needs_box() {
quote! { ::std::boxed::Box<#inner_ref> }
} else {
quote! { #inner_ref }
};
tokens.append_all(match (nullable, self.field.required()) {
(_, false) => quote! { ::ploidy_util::absent::AbsentOr<#inner> },
(true, true) => quote! { ::std::option::Option<#inner> },
(false, true) => inner,
});
}
}
#[derive(Debug)]
struct SerdeStructFieldAttr<'view, 'a> {
field_name: CodegenIdentUsage<'a>,
field: &'a StructFieldView<'view, 'a>,
}
impl<'view, 'a> SerdeStructFieldAttr<'view, 'a> {
fn new(field_name: CodegenIdentUsage<'a>, field: &'a StructFieldView<'view, 'a>) -> Self {
Self { field_name, field }
}
}
impl ToTokens for SerdeStructFieldAttr<'_, '_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let mut attrs = vec![];
if self.field.flattened() {
attrs.push(quote! { flatten });
} else if let &StructFieldName::Name(name) = &self.field.name() {
if self.field_name.display().to_string() != name {
attrs.push(quote! { rename = #name });
}
}
if !self.field.required() {
attrs.push(quote! { default });
attrs.push(
quote! { skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent" },
);
}
if !attrs.is_empty() {
tokens.append_all(quote! { #[serde(#(#attrs),*)] });
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use ploidy_core::{
arena::Arena,
ir::{RawGraph, SchemaTypeView, Spec},
parse::Document,
};
use pretty_assertions::assert_eq;
use syn::parse_quote;
use crate::CodegenGraph;
#[test]
fn test_struct() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Pet:
type: object
properties:
name:
type: string
age:
type: integer
format: int32
required:
- name
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Pet");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Pet`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Pet {
pub name: ::std::string::String,
#[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
pub age: ::ploidy_util::absent::AbsentOr<i32>,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_excludes_tag_fields() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Animal:
type: object
properties:
type:
type: string
name:
type: string
required:
- type
- name
Pet:
oneOf:
- $ref: '#/components/schemas/Animal'
discriminator:
propertyName: type
mapping:
animal: '#/components/schemas/Animal'
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Animal");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Animal`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Animal {
pub name: ::std::string::String,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_required_nullable_field_uses_option() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Record:
type: object
properties:
id:
type: string
deleted_at:
type: string
format: date-time
nullable: true
required:
- id
- deleted_at
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Record");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Record`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Record {
pub id: ::std::string::String,
pub deleted_at: ::std::option::Option<::ploidy_util::chrono::DateTime<::ploidy_util::chrono::Utc>>,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_required_nullable_field_openapi_31_syntax() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.1.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Record:
type: object
properties:
id:
type: string
deleted_at:
type: [string, 'null']
format: date-time
required:
- id
- deleted_at
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Record");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Record`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Record {
pub id: ::std::string::String,
pub deleted_at: ::std::option::Option<::ploidy_util::chrono::DateTime<::ploidy_util::chrono::Utc>>,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_optional_nullable_field_uses_absent_or() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Record:
type: object
properties:
id:
type: string
deleted_at:
type: string
format: date-time
nullable: true
required:
- id
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Record");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Record`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Record {
pub id: ::std::string::String,
#[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
pub deleted_at: ::ploidy_util::absent::AbsentOr<::ploidy_util::chrono::DateTime<::ploidy_util::chrono::Utc>>,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_optional_field_referencing_nullable_schema_unwraps() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.1.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
NullableString:
type:
- string
- 'null'
Record:
type: object
properties:
nickname:
$ref: '#/components/schemas/NullableString'
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Record");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Record`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Record {
#[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
pub nickname: ::ploidy_util::absent::AbsentOr<::std::string::String>,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_derives_hash_eq_when_hashable() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
User:
type: object
properties:
id:
type: string
active:
type: boolean
required:
- id
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "User");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `User`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct User {
pub id: ::std::string::String,
#[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
pub active: ::ploidy_util::absent::AbsentOr<bool>,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_omits_hash_eq_with_floats() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Measurement:
type: object
properties:
value:
type: number
format: double
unit:
type: string
required:
- value
- unit
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Measurement");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Measurement`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Measurement {
pub value: f64,
pub unit: ::std::string::String,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_derives_default_when_all_optional() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Options:
type: object
properties:
verbose:
type: boolean
count:
type: integer
format: int32
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Options");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Options`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Options {
#[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
pub verbose: ::ploidy_util::absent::AbsentOr<bool>,
#[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
pub count: ::ploidy_util::absent::AbsentOr<i32>,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_derives_default_with_nested_optional_struct() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Inner:
type: object
properties:
value:
type: string
Outer:
type: object
properties:
inner:
$ref: '#/components/schemas/Inner'
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Outer");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Outer`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Outer {
#[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
pub inner: ::ploidy_util::absent::AbsentOr<crate::types::Inner>,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_omits_default_with_required_nested_required_field() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Inner:
type: object
properties:
id:
type: string
required:
- id
Outer:
type: object
required:
- inner
properties:
inner:
$ref: '#/components/schemas/Inner'
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Outer");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Outer`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Outer {
pub inner: crate::types::Inner,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_derives_default_with_optional_tagged_union() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Dog:
type: object
properties:
bark:
type: string
Cat:
type: object
properties:
meow:
type: string
Pet:
oneOf:
- $ref: '#/components/schemas/Dog'
- $ref: '#/components/schemas/Cat'
discriminator:
propertyName: type
mapping:
dog: '#/components/schemas/Dog'
cat: '#/components/schemas/Cat'
Owner:
type: object
properties:
pet:
$ref: '#/components/schemas/Pet'
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Owner");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Owner`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Owner {
#[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
pub pet: ::ploidy_util::absent::AbsentOr<crate::types::Pet>,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_derives_default_with_optional_untagged_union() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
StringOrInt:
oneOf:
- type: string
- type: integer
format: int32
Container:
type: object
properties:
value:
$ref: '#/components/schemas/StringOrInt'
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Container");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Container`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Container {
#[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
pub value: ::ploidy_util::absent::AbsentOr<crate::types::StringOrInt>,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_omits_default_with_required_tagged_union() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Dog:
type: object
properties:
bark:
type: string
Cat:
type: object
properties:
meow:
type: string
Pet:
oneOf:
- $ref: '#/components/schemas/Dog'
- $ref: '#/components/schemas/Cat'
discriminator:
propertyName: type
mapping:
dog: '#/components/schemas/Dog'
cat: '#/components/schemas/Cat'
Owner:
type: object
required:
- pet
properties:
pet:
$ref: '#/components/schemas/Pet'
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Owner");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Owner`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Owner {
pub pet: crate::types::Pet,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_derives_default_with_optional_field_to_struct_with_required() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Inner:
type: object
properties:
id:
type: string
required:
- id
Outer:
type: object
properties:
inner:
$ref: '#/components/schemas/Inner'
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Outer");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Outer`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Outer {
#[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
pub inner: ::ploidy_util::absent::AbsentOr<crate::types::Inner>,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_derives_default_with_required_any_field() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Container:
type: object
required:
- data
properties:
data: {}
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Container");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Container`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Container {
pub data: ::ploidy_util::serde_json::Value,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_derives_default_with_required_primitive_fields() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Defaults:
type: object
required:
- text
- count
- flag
properties:
text:
type: string
count:
type: integer
format: int32
flag:
type: boolean
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Defaults");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Defaults`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Defaults {
pub text: ::std::string::String,
pub count: i32,
pub flag: bool,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_omits_default_with_required_url_field() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Resource:
type: object
required:
- link
properties:
link:
type: string
format: uri
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Resource");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Resource`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Resource {
pub link: ::ploidy_util::url::Url,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_derives_default_with_optional_url_field() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Resource:
type: object
properties:
link:
type: string
format: uri
name:
type: string
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Resource");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Resource`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Resource {
#[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
pub link: ::ploidy_util::absent::AbsentOr<::ploidy_util::url::Url>,
#[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
pub name: ::ploidy_util::absent::AbsentOr<::std::string::String>,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_derives_default_with_required_container_schema_field() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Tags:
type: array
items:
type: string
Container:
type: object
required:
- tags
properties:
tags:
$ref: '#/components/schemas/Tags'
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Container");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Container`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Container {
pub tags: crate::types::Tags,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_derives_default_when_inheriting_from_tagged_union() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Dog:
type: object
properties:
bark:
type: string
Cat:
type: object
properties:
meow:
type: string
Animal:
oneOf:
- $ref: '#/components/schemas/Dog'
- $ref: '#/components/schemas/Cat'
discriminator:
propertyName: type
mapping:
dog: '#/components/schemas/Dog'
cat: '#/components/schemas/Cat'
properties:
type:
type: string
required:
- type
Corgi:
allOf:
- $ref: '#/components/schemas/Animal'
- type: object
properties:
name:
type: string
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Corgi");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Corgi`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Corgi {
pub r#type: ::std::string::String,
#[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
pub name: ::ploidy_util::absent::AbsentOr<::std::string::String>,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_excludes_default_when_inheriting_non_defaultable_from_tagged_union() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
TypeA:
type: object
properties:
value:
type: string
TypeB:
type: object
properties:
count:
type: integer
Base:
oneOf:
- $ref: '#/components/schemas/TypeA'
- $ref: '#/components/schemas/TypeB'
discriminator:
propertyName: kind
mapping:
a: '#/components/schemas/TypeA'
b: '#/components/schemas/TypeB'
properties:
kind:
type: string
source:
type: string
format: uri
required:
- kind
- source
Child:
allOf:
- $ref: '#/components/schemas/Base'
- type: object
properties:
name:
type: string
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Child");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Child`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Child {
pub kind: ::std::string::String,
pub source: ::ploidy_util::url::Url,
#[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
pub name: ::ploidy_util::absent::AbsentOr<::std::string::String>,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_boxes_recursive_required_field() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Node:
type: object
properties:
value:
type: string
next:
$ref: '#/components/schemas/Node'
required:
- value
- next
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Node");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Node`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Node {
pub value: ::std::string::String,
pub next: ::std::boxed::Box<crate::types::Node>,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_boxes_recursive_optional_field() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Node:
type: object
properties:
value:
type: string
next:
$ref: '#/components/schemas/Node'
required:
- value
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Node");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Node`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Node {
pub value: ::std::string::String,
#[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
pub next: ::ploidy_util::absent::AbsentOr<::std::boxed::Box<crate::types::Node>>,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_does_not_box_recursive_array_field() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Node:
type: object
properties:
value:
type: string
children:
type: array
items:
$ref: '#/components/schemas/Node'
required:
- value
- children
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Node");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Node`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Node {
pub value: ::std::string::String,
pub children: ::std::vec::Vec<crate::types::Node>,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_does_not_box_optional_recursive_array_field() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Node:
type: object
properties:
value:
type: string
children:
type: array
items:
$ref: '#/components/schemas/Node'
required:
- value
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Node");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Node`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Node {
pub value: ::std::string::String,
#[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
pub children: ::ploidy_util::absent::AbsentOr<::std::vec::Vec<crate::types::Node>>,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_linearizes_inline_all_of_parent_fields() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Person:
allOf:
- type: object
properties:
name:
type: string
required:
- name
- type: object
properties:
age:
type: integer
format: int32
required:
- age
properties:
email:
type: string
required:
- email
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Person");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Person`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Person {
pub name: ::std::string::String,
pub age: i32,
pub email: ::std::string::String,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_with_additional_properties() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Config:
type: object
properties:
name:
type: string
required:
- name
additionalProperties:
type: string
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Config");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Config`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Config {
pub name: ::std::string::String,
#[serde(flatten)]
pub additional_properties: ::std::collections::BTreeMap<::std::string::String, ::std::string::String>,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_inlined_struct_includes_tag() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Dog:
type: object
properties:
kind:
type: string
bark:
type: string
required:
- kind
- bark
Pet:
oneOf:
- $ref: '#/components/schemas/Dog'
discriminator:
propertyName: kind
mapping:
dog: '#/components/schemas/Dog'
Owner:
type: object
properties:
dog:
$ref: '#/components/schemas/Dog'
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let mut raw = RawGraph::new(&arena, &spec);
raw.inline_tagged_variants();
let graph = CodegenGraph::new(raw.cook());
let schema = graph.schemas().find(|s| s.name() == "Dog");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Dog`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Dog {
pub kind: ::std::string::String,
pub bark: ::std::string::String,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_required_enum_field() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Status:
type: string
enum:
- active
- inactive
Pet:
type: object
required:
- name
- status
properties:
name:
type: string
status:
$ref: '#/components/schemas/Status'
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Pet");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Pet`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Pet {
pub name: ::std::string::String,
pub status: crate::types::Status,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_optional_enum_field_uses_absent() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Status:
type: string
enum:
- active
- inactive
Pet:
type: object
required:
- name
properties:
name:
type: string
status:
$ref: '#/components/schemas/Status'
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Pet");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Pet`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Pet {
pub name: ::std::string::String,
#[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent")]
pub status: ::ploidy_util::absent::AbsentOr<crate::types::Status>,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_required_inline_enum_field() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Pet:
type: object
required:
- name
- status
properties:
name:
type: string
status:
type: string
enum:
- active
- inactive
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Pet");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Pet`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Pet {
pub name: ::std::string::String,
pub status: crate::types::pet::types::Status,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_required_nullable_enum_field_uses_option() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Status:
type: string
enum:
- active
- inactive
nullable: true
Pet:
type: object
required:
- name
- status
properties:
name:
type: string
status:
$ref: '#/components/schemas/Status'
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Pet");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Pet`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Pet {
pub name: ::std::string::String,
pub status: ::std::option::Option<crate::types::Status>,
}
};
assert_eq!(actual, expected);
}
#[test]
fn test_struct_required_unrepresentable_enum_field_no_skip() {
let doc = Document::from_yaml(indoc::indoc! {"
openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths: {}
components:
schemas:
Priority:
type: integer
enum:
- 1
- 2
- 3
Pet:
type: object
required:
- name
- priority
properties:
name:
type: string
priority:
$ref: '#/components/schemas/Priority'
"})
.unwrap();
let arena = Arena::new();
let spec = Spec::from_doc(&arena, &doc).unwrap();
let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
let schema = graph.schemas().find(|s| s.name() == "Pet");
let Some(schema @ SchemaTypeView::Struct(_, struct_view)) = &schema else {
panic!("expected struct `Pet`; got `{schema:?}`");
};
let name = CodegenTypeName::Schema(schema);
let codegen = CodegenStruct::new(name, struct_view);
let actual: syn::ItemStruct = parse_quote!(#codegen);
let expected: syn::ItemStruct = parse_quote! {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
#[serde(crate = "::ploidy_util::serde")]
pub struct Pet {
pub name: ::std::string::String,
pub priority: crate::types::Priority,
}
};
assert_eq!(actual, expected);
}
}