use proc_macro2::TokenStream;
use quote::quote;
use syn::{DataEnum, Fields, Ident, Type};
use crate::container_attrs::ContainerAttributes;
use crate::generators::struct_schema::apply_rename_all;
use crate::parsers::field_parser::parse_field_attributes;
use crate::parsers::variant_parser::parse_variant_attributes;
use crate::type_utils::{
get_array_inner_type, get_box_inner_type, get_map_types, get_option_inner_type,
get_schema_type_from_rust_type, get_tuple_element_types, get_type_name, is_array_type,
is_box_type, is_json_value_type, is_map_type, is_option_type, is_tuple_type,
};
pub fn generate_enum_schema(
name: &Ident,
data_enum: &DataEnum,
container_attrs: &ContainerAttributes,
) -> TokenStream {
let all_simple = data_enum.variants.iter().all(|v| v.fields.is_empty());
let has_tag = container_attrs.serde_tag.is_some();
if all_simple && !has_tag {
generate_simple_enum_schema(name, data_enum, container_attrs)
} else {
generate_complex_enum_schema(name, data_enum, container_attrs)
}
}
fn generate_simple_enum_schema(
name: &Ident,
data_enum: &DataEnum,
container_attrs: &ContainerAttributes,
) -> TokenStream {
let variant_values: Vec<_> = data_enum
.variants
.iter()
.map(|v| {
let attrs = parse_variant_attributes(v);
let original_name = v.ident.to_string();
if let Some(ref rename) = attrs.serde_rename {
rename.clone()
} else if let Some(ref rename_all) = container_attrs.serde_rename_all {
apply_rename_all(&original_name, rename_all)
} else {
original_name
}
})
.collect();
let mut container_setters = Vec::new();
if let Some(desc) = &container_attrs.description {
container_setters.push(quote! {
schema_obj["description"] = ::serde_json::Value::String(#desc.to_string());
});
}
if let Some(title) = &container_attrs.title {
container_setters.push(quote! {
schema_obj["title"] = ::serde_json::Value::String(#title.to_string());
});
}
if !container_attrs.examples.is_empty() {
let examples_values = &container_attrs.examples;
container_setters.push(quote! {
let examples_array = vec![
#(#examples_values),*
];
schema_obj["examples"] = ::serde_json::Value::Array(examples_array);
});
}
let container_setter = if !container_setters.is_empty() {
quote! {
#(#container_setters)*
}
} else {
quote! {}
};
quote! {
impl ::rstructor::schema::SchemaType for #name {
fn schema() -> ::rstructor::schema::Schema {
let enum_values = vec![
#(::serde_json::Value::String(#variant_values.to_string())),*
];
let mut schema_obj = ::serde_json::json!({
"type": "string",
"enum": enum_values,
"title": stringify!(#name)
});
#container_setter
::rstructor::schema::Schema::new(schema_obj)
}
fn schema_name() -> Option<String> {
Some(stringify!(#name).to_string())
}
}
}
}
fn generate_complex_enum_schema(
name: &Ident,
data_enum: &DataEnum,
container_attrs: &ContainerAttributes,
) -> TokenStream {
if container_attrs.serde_untagged {
return generate_untagged_enum_schema(name, data_enum, container_attrs);
} else if let Some(tag) = &container_attrs.serde_tag {
if let Some(content) = &container_attrs.serde_content {
return generate_adjacently_tagged_enum_schema(
name,
data_enum,
container_attrs,
tag,
content,
);
} else {
return generate_internally_tagged_enum_schema(name, data_enum, container_attrs, tag);
}
}
generate_externally_tagged_enum_schema(name, data_enum, container_attrs)
}
fn generate_externally_tagged_enum_schema(
name: &Ident,
data_enum: &DataEnum,
container_attrs: &ContainerAttributes,
) -> TokenStream {
let mut variant_schemas = Vec::new();
for variant in &data_enum.variants {
let attrs = parse_variant_attributes(variant);
let original_variant_name = variant.ident.to_string();
let variant_name = if let Some(ref rename) = attrs.serde_rename {
rename.clone()
} else if let Some(ref rename_all) = container_attrs.serde_rename_all {
apply_rename_all(&original_variant_name, rename_all)
} else {
original_variant_name.clone()
};
let description = attrs
.description
.unwrap_or_else(|| format!("Variant {}", variant_name));
match &variant.fields {
Fields::Unit => {
let variant_name_str = variant_name.clone();
let description_str = description.clone();
variant_schemas.push(quote! {
::serde_json::json!({
"type": "string",
"enum": [#variant_name_str],
"description": #description_str
})
});
}
Fields::Unnamed(fields) => {
let has_single_field = fields.unnamed.len() == 1;
if has_single_field {
let field = fields.unnamed.first().unwrap();
let field_schema = generate_field_schema(&field.ty, &None);
let variant_name_str = variant_name.clone();
let description_str = description.clone();
variant_schemas.push(quote! {
{
let field_schema_value = #field_schema;
let mut properties_map = ::serde_json::Map::new();
properties_map.insert(#variant_name_str.to_string(), field_schema_value);
let mut required_array = Vec::new();
required_array.push(::serde_json::Value::String(#variant_name_str.to_string()));
let mut schema_obj = ::serde_json::Map::new();
schema_obj.insert("type".to_string(), ::serde_json::Value::String("object".to_string()));
schema_obj.insert("properties".to_string(), ::serde_json::Value::Object(properties_map));
schema_obj.insert("required".to_string(), ::serde_json::Value::Array(required_array));
schema_obj.insert("description".to_string(), ::serde_json::Value::String(#description_str.to_string()));
schema_obj.insert("additionalProperties".to_string(), ::serde_json::Value::Bool(false));
::serde_json::Value::Object(schema_obj)
}
});
} else {
let mut field_schemas = Vec::new();
for field in fields.unnamed.iter() {
let field_schema = generate_field_schema(&field.ty, &None);
field_schemas.push(field_schema);
}
let variant_name_str = variant_name.clone();
let description_str = description.clone();
let field_count = fields.unnamed.len();
variant_schemas.push(quote! {
{
let field_schema_values: Vec<::serde_json::Value> = vec![
#(#field_schemas),*
];
let mut items_array = ::serde_json::Map::new();
items_array.insert("type".to_string(), ::serde_json::Value::String("array".to_string()));
items_array.insert("items".to_string(), ::serde_json::Value::Array(field_schema_values));
let field_count_u64 = #field_count as u64;
items_array.insert("minItems".to_string(), ::serde_json::Value::Number(::serde_json::Number::from(field_count_u64)));
items_array.insert("maxItems".to_string(), ::serde_json::Value::Number(::serde_json::Number::from(field_count_u64)));
let mut variant_properties = ::serde_json::Map::new();
variant_properties.insert(#variant_name_str.to_string(), ::serde_json::Value::Object(items_array));
let mut required_array = Vec::new();
required_array.push(::serde_json::Value::String(#variant_name_str.to_string()));
let mut schema_obj = ::serde_json::Map::new();
schema_obj.insert("type".to_string(), ::serde_json::Value::String("object".to_string()));
schema_obj.insert("properties".to_string(), ::serde_json::Value::Object(variant_properties));
schema_obj.insert("required".to_string(), ::serde_json::Value::Array(required_array));
schema_obj.insert("description".to_string(), ::serde_json::Value::String(#description_str.to_string()));
schema_obj.insert("additionalProperties".to_string(), ::serde_json::Value::Bool(false));
::serde_json::Value::Object(schema_obj)
}
});
}
}
Fields::Named(fields) => {
let mut prop_setters = Vec::new();
let mut required_fields = Vec::new();
for field in &fields.named {
if let Some(field_ident) = &field.ident {
let original_field_name = field_ident.to_string();
let field_attrs = parse_field_attributes(field);
let field_name_str = if let Some(ref rename) = field_attrs.serde_rename {
rename.clone()
} else {
original_field_name.clone()
};
let field_desc = field_attrs
.description
.unwrap_or_else(|| format!("Field {}", field_name_str));
let is_optional = is_option_type(&field.ty);
let field_schema = generate_field_schema(&field.ty, &Some(field_desc));
let field_name_str_owned = field_name_str.clone();
prop_setters.push(quote! {
{
let field_schema_value = #field_schema;
properties_map.insert(#field_name_str_owned.to_string(), field_schema_value);
}
});
if !is_optional {
required_fields.push(quote! {
::serde_json::Value::String(#field_name_str.to_string())
});
}
}
}
let variant_name_str = variant_name.clone();
let description_str = description.clone();
let required_array_code = if !required_fields.is_empty() {
quote! {
let mut required_vec = Vec::new();
#(required_vec.push(#required_fields);)*
variant_properties.insert("required".to_string(), ::serde_json::Value::Array(required_vec));
}
} else {
quote! {}
};
variant_schemas.push(quote! {
{
let mut properties_map = ::serde_json::Map::new();
#(#prop_setters)*
let mut variant_properties = ::serde_json::Map::new();
variant_properties.insert("type".to_string(), ::serde_json::Value::String("object".to_string()));
variant_properties.insert("properties".to_string(), ::serde_json::Value::Object(properties_map));
#required_array_code
variant_properties.insert("additionalProperties".to_string(), ::serde_json::Value::Bool(false));
let mut outer_properties = ::serde_json::Map::new();
outer_properties.insert(#variant_name_str.to_string(), ::serde_json::Value::Object(variant_properties));
let mut schema_obj = ::serde_json::Map::new();
schema_obj.insert("type".to_string(), ::serde_json::Value::String("object".to_string()));
schema_obj.insert("properties".to_string(), ::serde_json::Value::Object(outer_properties));
let mut required_array = Vec::new();
required_array.push(::serde_json::Value::String(#variant_name_str.to_string()));
schema_obj.insert("required".to_string(), ::serde_json::Value::Array(required_array));
schema_obj.insert("description".to_string(), ::serde_json::Value::String(#description_str.to_string()));
schema_obj.insert("additionalProperties".to_string(), ::serde_json::Value::Bool(false));
::serde_json::Value::Object(schema_obj)
}
});
}
}
}
let mut container_setters = Vec::new();
if let Some(desc) = &container_attrs.description {
container_setters.push(quote! {
schema_obj["description"] = ::serde_json::Value::String(#desc.to_string());
});
}
if let Some(title) = &container_attrs.title {
container_setters.push(quote! {
schema_obj["title"] = ::serde_json::Value::String(#title.to_string());
});
}
if !container_attrs.examples.is_empty() {
let examples_values = &container_attrs.examples;
container_setters.push(quote! {
let examples_array = vec![
#(#examples_values),*
];
schema_obj["examples"] = ::serde_json::Value::Array(examples_array);
});
}
let container_setter = if !container_setters.is_empty() {
quote! {
#(#container_setters)*
}
} else {
quote! {}
};
quote! {
impl ::rstructor::schema::SchemaType for #name {
fn schema() -> ::rstructor::schema::Schema {
let variant_schemas = vec![
#(#variant_schemas),*
];
let mut schema_obj = ::serde_json::json!({
"anyOf": variant_schemas,
"title": stringify!(#name)
});
#container_setter
::rstructor::schema::Schema::new(schema_obj)
}
fn schema_name() -> Option<String> {
Some(stringify!(#name).to_string())
}
}
}
}
fn generate_field_schema(field_type: &Type, description: &Option<String>) -> TokenStream {
let schema_type = get_schema_type_from_rust_type(field_type);
let is_optional = is_option_type(field_type);
let actual_type = if is_optional {
get_option_inner_type(field_type)
} else {
field_type
};
let desc_prop = if let Some(desc) = description {
quote! { "description": #desc, }
} else {
quote! {}
};
if is_json_value_type(actual_type) {
return quote! {
::serde_json::json!({
#desc_prop
})
};
}
if is_map_type(actual_type)
&& let Some((_key_ty, val_ty)) = get_map_types(actual_type)
{
let val_schema_type = get_schema_type_from_rust_type(val_ty);
if val_schema_type == "object" {
return quote! {
{
let mut schema = ::serde_json::json!({
"type": "object",
#desc_prop
});
let value_schema = <#val_ty as ::rstructor::schema::SchemaType>::schema();
if let ::serde_json::Value::Object(map) = &mut schema {
map.insert("additionalProperties".to_string(), value_schema.to_json());
}
schema
}
};
} else {
return quote! {
::serde_json::json!({
"type": "object",
#desc_prop
"additionalProperties": {
"type": #val_schema_type
}
})
};
}
}
if is_box_type(actual_type)
&& let Some(inner_ty) = get_box_inner_type(actual_type)
{
return generate_field_schema(inner_ty, description);
}
let type_name = get_type_name(actual_type);
let is_datetime_type = matches!(
type_name.as_deref(),
Some("DateTime") | Some("NaiveDateTime")
);
let is_date_only_type = matches!(type_name.as_deref(), Some("NaiveDate") | Some("Date"));
let is_uuid_type = matches!(type_name.as_deref(), Some("Uuid"));
if is_datetime_type {
let date_desc = description
.clone()
.unwrap_or_else(|| "ISO-8601 formatted date and time".to_string());
return quote! {
::serde_json::json!({
"type": "string",
"format": "date-time",
"description": #date_desc
})
};
}
if is_date_only_type {
let date_desc = description
.clone()
.unwrap_or_else(|| "ISO-8601 formatted date (YYYY-MM-DD)".to_string());
return quote! {
::serde_json::json!({
"type": "string",
"format": "date",
"description": #date_desc
})
};
}
if is_uuid_type {
let uuid_desc = description
.clone()
.unwrap_or_else(|| "UUID identifier string".to_string());
return quote! {
::serde_json::json!({
"type": "string",
"format": "uuid",
"description": #uuid_desc
})
};
}
if is_tuple_type(actual_type)
&& let Some(element_types) = get_tuple_element_types(actual_type)
{
let element_count = element_types.len();
let element_schemas: Vec<TokenStream> = element_types
.iter()
.map(|elem_ty| {
let elem_schema_type = get_schema_type_from_rust_type(elem_ty);
if elem_schema_type == "object" {
quote! {
<#elem_ty as ::rstructor::schema::SchemaType>::schema().to_json()
}
} else {
quote! {
::serde_json::json!({"type": #elem_schema_type})
}
}
})
.collect();
return quote! {
{
let prefix_items = vec![
#(#element_schemas),*
];
::serde_json::json!({
"type": "array",
#desc_prop
"prefixItems": prefix_items,
"minItems": #element_count,
"maxItems": #element_count
})
}
};
}
if is_array_type(actual_type) {
if let Some(inner_type) = get_array_inner_type(actual_type) {
let inner_schema_type = get_schema_type_from_rust_type(inner_type);
let inner_type_name = get_type_name(inner_type);
let is_inner_datetime = matches!(
inner_type_name.as_deref(),
Some("DateTime") | Some("NaiveDateTime")
);
let is_inner_date_only =
matches!(inner_type_name.as_deref(), Some("NaiveDate") | Some("Date"));
let is_inner_uuid = matches!(inner_type_name.as_deref(), Some("Uuid"));
if is_inner_datetime {
return quote! {
::serde_json::json!({
"type": "array",
#desc_prop
"items": {
"type": "string",
"format": "date-time"
}
})
};
}
if is_inner_date_only {
return quote! {
::serde_json::json!({
"type": "array",
#desc_prop
"items": {
"type": "string",
"format": "date"
}
})
};
}
if is_inner_uuid {
return quote! {
::serde_json::json!({
"type": "array",
#desc_prop
"items": {
"type": "string",
"format": "uuid"
}
})
};
}
if inner_schema_type == "object" {
return quote! {
{
let items_schema = <#inner_type as ::rstructor::schema::SchemaType>::schema().to_json();
::serde_json::json!({
"type": "array",
#desc_prop
"items": items_schema
})
}
};
} else {
return quote! {
::serde_json::json!({
"type": "array",
#desc_prop
"items": {
"type": #inner_schema_type
}
})
};
}
} else {
return quote! {
::serde_json::json!({
"type": "array",
#desc_prop
"items": {
"type": "string"
}
})
};
}
}
if schema_type == "object"
&& let Type::Path(type_path) = actual_type
&& type_path.path.segments.last().is_some()
{
if let Some(desc) = description {
let desc_str = desc.clone();
return quote! {
{
let mut obj = <#type_path as ::rstructor::schema::SchemaType>::schema().to_json().clone();
if let ::serde_json::Value::Object(map) = &mut obj {
map.insert("description".to_string(), ::serde_json::Value::String(#desc_str.to_string()));
}
obj
}
};
} else {
return quote! {
<#type_path as ::rstructor::schema::SchemaType>::schema().to_json()
};
}
}
quote! {
::serde_json::json!({
"type": #schema_type,
#desc_prop
})
}
}
fn generate_internally_tagged_enum_schema(
name: &Ident,
data_enum: &DataEnum,
container_attrs: &ContainerAttributes,
tag_name: &str,
) -> TokenStream {
let mut variant_schemas = Vec::new();
for variant in &data_enum.variants {
let attrs = parse_variant_attributes(variant);
let original_variant_name = variant.ident.to_string();
let variant_name = if let Some(ref rename) = attrs.serde_rename {
rename.clone()
} else if let Some(ref rename_all) = container_attrs.serde_rename_all {
apply_rename_all(&original_variant_name, rename_all)
} else {
original_variant_name.clone()
};
let description = attrs
.description
.unwrap_or_else(|| format!("Variant {}", variant_name));
match &variant.fields {
Fields::Unit => {
let variant_name_str = variant_name.clone();
let description_str = description.clone();
let tag_name_str = tag_name.to_string();
variant_schemas.push(quote! {
{
let mut schema_obj = ::serde_json::Map::new();
schema_obj.insert("type".to_string(), ::serde_json::Value::String("object".to_string()));
let mut properties = ::serde_json::Map::new();
properties.insert(#tag_name_str.to_string(), ::serde_json::json!({
"type": "string",
"enum": [#variant_name_str]
}));
schema_obj.insert("properties".to_string(), ::serde_json::Value::Object(properties));
let required = vec![::serde_json::Value::String(#tag_name_str.to_string())];
schema_obj.insert("required".to_string(), ::serde_json::Value::Array(required));
schema_obj.insert("description".to_string(), ::serde_json::Value::String(#description_str.to_string()));
schema_obj.insert("additionalProperties".to_string(), ::serde_json::Value::Bool(false));
::serde_json::Value::Object(schema_obj)
}
});
}
Fields::Named(fields) => {
let mut prop_setters = Vec::new();
let mut required_fields = vec![quote! {
::serde_json::Value::String(#tag_name.to_string())
}];
for field in &fields.named {
if let Some(field_ident) = &field.ident {
let original_field_name = field_ident.to_string();
let field_attrs = parse_field_attributes(field);
let field_name_str = if let Some(ref rename) = field_attrs.serde_rename {
rename.clone()
} else {
original_field_name.clone()
};
let field_desc = field_attrs
.description
.unwrap_or_else(|| format!("Field {}", field_name_str));
let is_optional = is_option_type(&field.ty);
let field_schema = generate_field_schema(&field.ty, &Some(field_desc));
let field_name_str_owned = field_name_str.clone();
prop_setters.push(quote! {
{
let field_schema_value = #field_schema;
properties.insert(#field_name_str_owned.to_string(), field_schema_value);
}
});
if !is_optional {
required_fields.push(quote! {
::serde_json::Value::String(#field_name_str.to_string())
});
}
}
}
let variant_name_str = variant_name.clone();
let description_str = description.clone();
let tag_name_str = tag_name.to_string();
variant_schemas.push(quote! {
{
let mut schema_obj = ::serde_json::Map::new();
schema_obj.insert("type".to_string(), ::serde_json::Value::String("object".to_string()));
let mut properties = ::serde_json::Map::new();
properties.insert(#tag_name_str.to_string(), ::serde_json::json!({
"type": "string",
"enum": [#variant_name_str]
}));
#(#prop_setters)*
schema_obj.insert("properties".to_string(), ::serde_json::Value::Object(properties));
let required = vec![#(#required_fields),*];
schema_obj.insert("required".to_string(), ::serde_json::Value::Array(required));
schema_obj.insert("description".to_string(), ::serde_json::Value::String(#description_str.to_string()));
schema_obj.insert("additionalProperties".to_string(), ::serde_json::Value::Bool(false));
::serde_json::Value::Object(schema_obj)
}
});
}
Fields::Unnamed(fields) => {
let variant_name_str = variant_name.clone();
let description_str = description.clone();
let tag_name_str = tag_name.to_string();
if fields.unnamed.len() == 1 {
let field = fields.unnamed.first().unwrap();
let inner_ty = &field.ty;
let schema_ty = if is_box_type(inner_ty) {
get_box_inner_type(inner_ty).unwrap_or(inner_ty)
} else {
inner_ty
};
variant_schemas.push(quote! {
{
let inner_schema = <#schema_ty as ::rstructor::schema::SchemaType>::schema().to_json();
let mut properties = ::serde_json::Map::new();
properties.insert(#tag_name_str.to_string(), ::serde_json::json!({
"type": "string",
"enum": [#variant_name_str]
}));
let mut required = vec![::serde_json::Value::String(#tag_name_str.to_string())];
let mut defs = ::serde_json::Map::new();
if let Some(inner_obj) = inner_schema.as_object() {
let resolved_obj = if inner_obj.get("properties").is_some() {
Some(inner_obj)
} else {
inner_obj
.get("$ref")
.and_then(|r| r.as_str())
.and_then(|r| r.strip_prefix("#/$defs/"))
.and_then(|def_name| inner_obj.get("$defs").and_then(|defs| defs.get(def_name)))
.and_then(|def_schema| def_schema.as_object())
};
if let Some(::serde_json::Value::Object(inner_defs)) = inner_obj.get("$defs") {
for (k, v) in inner_defs {
defs.insert(k.clone(), v.clone());
}
}
if let Some(resolved_obj) = resolved_obj {
if let Some(::serde_json::Value::Object(inner_props)) = resolved_obj.get("properties") {
for (k, v) in inner_props {
properties.insert(k.clone(), v.clone());
}
}
if let Some(::serde_json::Value::Array(inner_req)) = resolved_obj.get("required") {
for r in inner_req {
required.push(r.clone());
}
}
}
}
let mut schema_obj = ::serde_json::Map::new();
schema_obj.insert("type".to_string(), ::serde_json::Value::String("object".to_string()));
schema_obj.insert("properties".to_string(), ::serde_json::Value::Object(properties));
schema_obj.insert("required".to_string(), ::serde_json::Value::Array(required));
schema_obj.insert("description".to_string(), ::serde_json::Value::String(#description_str.to_string()));
schema_obj.insert("additionalProperties".to_string(), ::serde_json::Value::Bool(false));
if !defs.is_empty() {
schema_obj.insert("$defs".to_string(), ::serde_json::Value::Object(defs));
}
::serde_json::Value::Object(schema_obj)
}
});
} else {
variant_schemas.push(quote! {
{
let mut schema_obj = ::serde_json::Map::new();
schema_obj.insert("type".to_string(), ::serde_json::Value::String("object".to_string()));
let mut properties = ::serde_json::Map::new();
properties.insert(#tag_name_str.to_string(), ::serde_json::json!({
"type": "string",
"enum": [#variant_name_str]
}));
schema_obj.insert("properties".to_string(), ::serde_json::Value::Object(properties));
let required = vec![::serde_json::Value::String(#tag_name_str.to_string())];
schema_obj.insert("required".to_string(), ::serde_json::Value::Array(required));
schema_obj.insert("description".to_string(), ::serde_json::Value::String(#description_str.to_string()));
schema_obj.insert("additionalProperties".to_string(), ::serde_json::Value::Bool(false));
::serde_json::Value::Object(schema_obj)
}
});
}
}
}
}
let container_setter = generate_container_setters(container_attrs);
quote! {
impl ::rstructor::schema::SchemaType for #name {
fn schema() -> ::rstructor::schema::Schema {
let variant_schemas = vec![
#(#variant_schemas),*
];
let mut schema_obj = ::serde_json::json!({
"anyOf": variant_schemas,
"title": stringify!(#name)
});
#container_setter
::rstructor::schema::Schema::new(schema_obj)
}
fn schema_name() -> Option<String> {
Some(stringify!(#name).to_string())
}
}
}
}
fn generate_adjacently_tagged_enum_schema(
name: &Ident,
data_enum: &DataEnum,
container_attrs: &ContainerAttributes,
tag_name: &str,
content_name: &str,
) -> TokenStream {
let mut variant_schemas = Vec::new();
for variant in &data_enum.variants {
let attrs = parse_variant_attributes(variant);
let original_variant_name = variant.ident.to_string();
let variant_name = if let Some(ref rename) = attrs.serde_rename {
rename.clone()
} else if let Some(ref rename_all) = container_attrs.serde_rename_all {
apply_rename_all(&original_variant_name, rename_all)
} else {
original_variant_name.clone()
};
let description = attrs
.description
.unwrap_or_else(|| format!("Variant {}", variant_name));
match &variant.fields {
Fields::Unit => {
let variant_name_str = variant_name.clone();
let description_str = description.clone();
let tag_name_str = tag_name.to_string();
variant_schemas.push(quote! {
{
let mut schema_obj = ::serde_json::Map::new();
schema_obj.insert("type".to_string(), ::serde_json::Value::String("object".to_string()));
let mut properties = ::serde_json::Map::new();
properties.insert(#tag_name_str.to_string(), ::serde_json::json!({
"type": "string",
"enum": [#variant_name_str]
}));
schema_obj.insert("properties".to_string(), ::serde_json::Value::Object(properties));
let required = vec![::serde_json::Value::String(#tag_name_str.to_string())];
schema_obj.insert("required".to_string(), ::serde_json::Value::Array(required));
schema_obj.insert("description".to_string(), ::serde_json::Value::String(#description_str.to_string()));
schema_obj.insert("additionalProperties".to_string(), ::serde_json::Value::Bool(false));
::serde_json::Value::Object(schema_obj)
}
});
}
Fields::Unnamed(fields) => {
let variant_name_str = variant_name.clone();
let description_str = description.clone();
let tag_name_str = tag_name.to_string();
let content_name_str = content_name.to_string();
if fields.unnamed.len() == 1 {
let field = fields.unnamed.first().unwrap();
let field_schema = generate_field_schema(&field.ty, &None);
let explicit_description = format!(
"{} - MUST be an object with '{}' (set to '{}') and '{}' (the value) at the top level",
description_str, tag_name_str, variant_name_str, content_name_str
);
variant_schemas.push(quote! {
{
let mut schema_obj = ::serde_json::Map::new();
schema_obj.insert("type".to_string(), ::serde_json::Value::String("object".to_string()));
let mut properties = ::serde_json::Map::new();
properties.insert(#tag_name_str.to_string(), ::serde_json::json!({
"type": "string",
"enum": [#variant_name_str]
}));
properties.insert(#content_name_str.to_string(), #field_schema);
schema_obj.insert("properties".to_string(), ::serde_json::Value::Object(properties));
let required = vec![
::serde_json::Value::String(#tag_name_str.to_string()),
::serde_json::Value::String(#content_name_str.to_string())
];
schema_obj.insert("required".to_string(), ::serde_json::Value::Array(required));
schema_obj.insert("description".to_string(), ::serde_json::Value::String(#explicit_description.to_string()));
schema_obj.insert("additionalProperties".to_string(), ::serde_json::Value::Bool(false));
::serde_json::Value::Object(schema_obj)
}
});
} else {
let mut field_schemas = Vec::new();
for field in fields.unnamed.iter() {
let field_schema = generate_field_schema(&field.ty, &None);
field_schemas.push(field_schema);
}
let field_count = fields.unnamed.len();
let explicit_description = format!(
"{} - MUST be an object with '{}' (set to '{}') and '{}' (array with {} elements) at the top level",
description_str,
tag_name_str,
variant_name_str,
content_name_str,
field_count
);
variant_schemas.push(quote! {
{
let mut schema_obj = ::serde_json::Map::new();
schema_obj.insert("type".to_string(), ::serde_json::Value::String("object".to_string()));
let mut properties = ::serde_json::Map::new();
properties.insert(#tag_name_str.to_string(), ::serde_json::json!({
"type": "string",
"enum": [#variant_name_str]
}));
let field_schema_values: Vec<::serde_json::Value> = vec![
#(#field_schemas),*
];
let mut content_schema = ::serde_json::Map::new();
content_schema.insert("type".to_string(), ::serde_json::Value::String("array".to_string()));
content_schema.insert("items".to_string(), ::serde_json::Value::Array(field_schema_values));
let field_count_u64 = #field_count as u64;
content_schema.insert("minItems".to_string(), ::serde_json::Value::Number(::serde_json::Number::from(field_count_u64)));
content_schema.insert("maxItems".to_string(), ::serde_json::Value::Number(::serde_json::Number::from(field_count_u64)));
properties.insert(#content_name_str.to_string(), ::serde_json::Value::Object(content_schema));
schema_obj.insert("properties".to_string(), ::serde_json::Value::Object(properties));
let required = vec![
::serde_json::Value::String(#tag_name_str.to_string()),
::serde_json::Value::String(#content_name_str.to_string())
];
schema_obj.insert("required".to_string(), ::serde_json::Value::Array(required));
schema_obj.insert("description".to_string(), ::serde_json::Value::String(#explicit_description.to_string()));
schema_obj.insert("additionalProperties".to_string(), ::serde_json::Value::Bool(false));
::serde_json::Value::Object(schema_obj)
}
});
}
}
Fields::Named(fields) => {
let mut prop_setters = Vec::new();
let mut required_content_fields = Vec::new();
let mut field_names = Vec::new();
for field in &fields.named {
if let Some(field_ident) = &field.ident {
let original_field_name = field_ident.to_string();
let field_attrs = parse_field_attributes(field);
let field_name_str = if let Some(ref rename) = field_attrs.serde_rename {
rename.clone()
} else {
original_field_name.clone()
};
field_names.push(field_name_str.clone());
let field_desc = field_attrs
.description
.unwrap_or_else(|| format!("Field {}", field_name_str));
let is_optional = is_option_type(&field.ty);
let field_schema = generate_field_schema(&field.ty, &Some(field_desc));
let field_name_str_owned = field_name_str.clone();
prop_setters.push(quote! {
{
let field_schema_value = #field_schema;
content_properties.insert(#field_name_str_owned.to_string(), field_schema_value);
}
});
if !is_optional {
required_content_fields.push(quote! {
::serde_json::Value::String(#field_name_str.to_string())
});
}
}
}
let variant_name_str = variant_name.clone();
let description_str = description.clone();
let tag_name_str = tag_name.to_string();
let content_name_str = content_name.to_string();
let field_names_list = field_names.join(", ");
let required_content_code = if !required_content_fields.is_empty() {
quote! {
let required_content = vec![#(#required_content_fields),*];
content_schema.insert("required".to_string(), ::serde_json::Value::Array(required_content));
}
} else {
quote! {}
};
let explicit_description = if !field_names.is_empty() {
format!(
"{} - MUST be an object with '{}' (set to '{}') and '{}' (object with fields: {}) at the top level",
description_str,
tag_name_str,
variant_name_str,
content_name_str,
field_names_list
)
} else {
description_str.clone()
};
variant_schemas.push(quote! {
{
let mut schema_obj = ::serde_json::Map::new();
schema_obj.insert("type".to_string(), ::serde_json::Value::String("object".to_string()));
let mut content_properties = ::serde_json::Map::new();
#(#prop_setters)*
let mut content_schema = ::serde_json::Map::new();
content_schema.insert("type".to_string(), ::serde_json::Value::String("object".to_string()));
content_schema.insert("properties".to_string(), ::serde_json::Value::Object(content_properties));
#required_content_code
content_schema.insert("additionalProperties".to_string(), ::serde_json::Value::Bool(false));
let content_field_desc = format!("REQUIRED nested object containing fields: {}", #field_names_list);
content_schema.insert("description".to_string(), ::serde_json::Value::String(content_field_desc));
let mut properties = ::serde_json::Map::new();
properties.insert(#tag_name_str.to_string(), ::serde_json::json!({
"type": "string",
"enum": [#variant_name_str],
"description": format!("Must be the string '{}'", #variant_name_str)
}));
properties.insert(#content_name_str.to_string(), ::serde_json::Value::Object(content_schema));
schema_obj.insert("properties".to_string(), ::serde_json::Value::Object(properties));
let required = vec![
::serde_json::Value::String(#tag_name_str.to_string()),
::serde_json::Value::String(#content_name_str.to_string())
];
schema_obj.insert("required".to_string(), ::serde_json::Value::Array(required));
schema_obj.insert("description".to_string(), ::serde_json::Value::String(#explicit_description.to_string()));
schema_obj.insert("additionalProperties".to_string(), ::serde_json::Value::Bool(false));
::serde_json::Value::Object(schema_obj)
}
});
}
}
}
let container_setter = generate_container_setters(container_attrs);
quote! {
impl ::rstructor::schema::SchemaType for #name {
fn schema() -> ::rstructor::schema::Schema {
let variant_schemas = vec![
#(#variant_schemas),*
];
let mut schema_obj = ::serde_json::json!({
"anyOf": variant_schemas,
"title": stringify!(#name)
});
#container_setter
::rstructor::schema::Schema::new(schema_obj)
}
fn schema_name() -> Option<String> {
Some(stringify!(#name).to_string())
}
}
}
}
fn generate_untagged_enum_schema(
name: &Ident,
data_enum: &DataEnum,
container_attrs: &ContainerAttributes,
) -> TokenStream {
let mut variant_schemas = Vec::new();
for variant in &data_enum.variants {
let attrs = parse_variant_attributes(variant);
let variant_name = variant.ident.to_string();
let description = attrs
.description
.unwrap_or_else(|| format!("Variant {}", variant_name));
match &variant.fields {
Fields::Unit => {
let description_str = description.clone();
variant_schemas.push(quote! {
::serde_json::json!({
"type": "null",
"description": #description_str
})
});
}
Fields::Unnamed(fields) => {
if fields.unnamed.len() == 1 {
let field = fields.unnamed.first().unwrap();
let field_schema = generate_field_schema(&field.ty, &Some(description.clone()));
variant_schemas.push(quote! { #field_schema });
} else {
let mut field_schemas = Vec::new();
for field in fields.unnamed.iter() {
let field_schema = generate_field_schema(&field.ty, &None);
field_schemas.push(field_schema);
}
let field_count = fields.unnamed.len();
let description_str = description.clone();
variant_schemas.push(quote! {
{
let field_schema_values: Vec<::serde_json::Value> = vec![
#(#field_schemas),*
];
let mut schema_obj = ::serde_json::Map::new();
schema_obj.insert("type".to_string(), ::serde_json::Value::String("array".to_string()));
schema_obj.insert("items".to_string(), ::serde_json::Value::Array(field_schema_values));
let field_count_u64 = #field_count as u64;
schema_obj.insert("minItems".to_string(), ::serde_json::Value::Number(::serde_json::Number::from(field_count_u64)));
schema_obj.insert("maxItems".to_string(), ::serde_json::Value::Number(::serde_json::Number::from(field_count_u64)));
schema_obj.insert("description".to_string(), ::serde_json::Value::String(#description_str.to_string()));
::serde_json::Value::Object(schema_obj)
}
});
}
}
Fields::Named(fields) => {
let mut prop_setters = Vec::new();
let mut required_fields = Vec::new();
for field in &fields.named {
if let Some(field_ident) = &field.ident {
let original_field_name = field_ident.to_string();
let field_attrs = parse_field_attributes(field);
let field_name_str = if let Some(ref rename) = field_attrs.serde_rename {
rename.clone()
} else {
original_field_name.clone()
};
let field_desc = field_attrs
.description
.unwrap_or_else(|| format!("Field {}", field_name_str));
let is_optional = is_option_type(&field.ty);
let field_schema = generate_field_schema(&field.ty, &Some(field_desc));
let field_name_str_owned = field_name_str.clone();
prop_setters.push(quote! {
{
let field_schema_value = #field_schema;
properties.insert(#field_name_str_owned.to_string(), field_schema_value);
}
});
if !is_optional {
required_fields.push(quote! {
::serde_json::Value::String(#field_name_str.to_string())
});
}
}
}
let description_str = description.clone();
let required_code = if !required_fields.is_empty() {
quote! {
let required = vec![#(#required_fields),*];
schema_obj.insert("required".to_string(), ::serde_json::Value::Array(required));
}
} else {
quote! {}
};
variant_schemas.push(quote! {
{
let mut schema_obj = ::serde_json::Map::new();
schema_obj.insert("type".to_string(), ::serde_json::Value::String("object".to_string()));
let mut properties = ::serde_json::Map::new();
#(#prop_setters)*
schema_obj.insert("properties".to_string(), ::serde_json::Value::Object(properties));
#required_code
schema_obj.insert("description".to_string(), ::serde_json::Value::String(#description_str.to_string()));
schema_obj.insert("additionalProperties".to_string(), ::serde_json::Value::Bool(false));
::serde_json::Value::Object(schema_obj)
}
});
}
}
}
let container_setter = generate_container_setters(container_attrs);
quote! {
impl ::rstructor::schema::SchemaType for #name {
fn schema() -> ::rstructor::schema::Schema {
let variant_schemas = vec![
#(#variant_schemas),*
];
let mut schema_obj = ::serde_json::json!({
"anyOf": variant_schemas,
"title": stringify!(#name)
});
#container_setter
::rstructor::schema::Schema::new(schema_obj)
}
fn schema_name() -> Option<String> {
Some(stringify!(#name).to_string())
}
}
}
}
fn generate_container_setters(container_attrs: &ContainerAttributes) -> TokenStream {
let mut container_setters = Vec::new();
if let Some(desc) = &container_attrs.description {
container_setters.push(quote! {
schema_obj["description"] = ::serde_json::Value::String(#desc.to_string());
});
}
if let Some(title) = &container_attrs.title {
container_setters.push(quote! {
schema_obj["title"] = ::serde_json::Value::String(#title.to_string());
});
}
if !container_attrs.examples.is_empty() {
let examples_values = &container_attrs.examples;
container_setters.push(quote! {
let examples_array = vec![
#(#examples_values),*
];
schema_obj["examples"] = ::serde_json::Value::Array(examples_array);
});
}
if !container_setters.is_empty() {
quote! {
#(#container_setters)*
}
} else {
quote! {}
}
}