Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .changepacks/changepack_log_SlPnaNu320dejvkld6zlv.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"changes":{"crates/vespera/Cargo.toml":"Patch","crates/vespera_macro/Cargo.toml":"Patch","crates/vespera_core/Cargo.toml":"Patch"},"note":"Support serde flatten","date":"2026-02-05T04:33:01.526326100Z"}
1 change: 1 addition & 0 deletions .changepacks/changepack_log_VL8xtXZ0Ty1P8XW4hfaso.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"changes":{"crates/vespera_core/Cargo.toml":"Patch","crates/vespera/Cargo.toml":"Patch","crates/vespera_macro/Cargo.toml":"Patch"},"note":"Support serde flatten, tagged, untagged","date":"2026-02-05T12:30:36.574038700Z"}
7 changes: 0 additions & 7 deletions .claude/settings.local.json

This file was deleted.

4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@ lcov.info
coverage
build_rs_cov.profraw
.sisyphus/
/docs
.claude
.DS_Store
coverage-report
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions crates/vespera_core/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,10 @@ pub struct Schema {
#[serde(skip_serializing_if = "Option::is_none")]
pub not: Option<Box<SchemaRef>>,

/// Discriminator for polymorphic schemas (used with oneOf/anyOf/allOf)
#[serde(skip_serializing_if = "Option::is_none")]
pub discriminator: Option<Discriminator>,

/// Nullable flag
#[serde(skip_serializing_if = "Option::is_none")]
pub nullable: Option<bool>,
Expand Down Expand Up @@ -246,6 +250,7 @@ impl Schema {
any_of: None,
one_of: None,
not: None,
discriminator: None,
nullable: None,
read_only: None,
write_only: None,
Expand Down Expand Up @@ -305,6 +310,20 @@ pub struct ExternalDocumentation {
pub url: String,
}

/// Discriminator object for polymorphism support (OpenAPI 3.0/3.1)
///
/// Used with `oneOf`, `anyOf`, `allOf` to aid in serialization, deserialization,
/// and validation when request bodies or response payloads may be one of several types.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Discriminator {
/// The name of the property in the payload that will hold the discriminator value
pub property_name: String,
/// An object to hold mappings between payload values and schema names or references
#[serde(skip_serializing_if = "Option::is_none")]
pub mapping: Option<BTreeMap<String, String>>,
}

/// OpenAPI Components (reusable components)
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
Expand Down
1 change: 0 additions & 1 deletion crates/vespera_macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ proc-macro2 = "1"
vespera_core = { workspace = true }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
anyhow = "1.0"

[dev-dependencies]
rstest = "0.26"
Expand Down
52 changes: 24 additions & 28 deletions crates/vespera_macro/src/args.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::http::is_http_method;

pub struct RouteArgs {
pub method: Option<syn::Ident>,
pub path: Option<syn::LitStr>,
Expand All @@ -22,33 +24,26 @@ impl syn::parse::Parse for RouteArgs {
// Try to parse as method identifier (get, post, etc.)
let ident: syn::Ident = input.parse()?;
let ident_str = ident.to_string().to_lowercase();
match ident_str.as_str() {
"get" | "post" | "put" | "patch" | "delete" | "head" | "options" => {
method = Some(ident);
}
"path" => {
input.parse::<syn::Token![=]>()?;
let lit: syn::LitStr = input.parse()?;
path = Some(lit);
}
"error_status" => {
input.parse::<syn::Token![=]>()?;
let array: syn::ExprArray = input.parse()?;
error_status = Some(array);
}
"tags" => {
input.parse::<syn::Token![=]>()?;
let array: syn::ExprArray = input.parse()?;
tags = Some(array);
}
"description" => {
input.parse::<syn::Token![=]>()?;
let lit: syn::LitStr = input.parse()?;
description = Some(lit);
}
_ => {
return Err(lookahead.error());
}
if is_http_method(&ident_str) {
method = Some(ident);
} else if ident_str == "path" {
input.parse::<syn::Token![=]>()?;
let lit: syn::LitStr = input.parse()?;
path = Some(lit);
} else if ident_str == "error_status" {
input.parse::<syn::Token![=]>()?;
let array: syn::ExprArray = input.parse()?;
error_status = Some(array);
} else if ident_str == "tags" {
input.parse::<syn::Token![=]>()?;
let array: syn::ExprArray = input.parse()?;
tags = Some(array);
} else if ident_str == "description" {
input.parse::<syn::Token![=]>()?;
let lit: syn::LitStr = input.parse()?;
description = Some(lit);
} else {
return Err(lookahead.error());
}

// Check if there's a comma
Expand All @@ -74,9 +69,10 @@ impl syn::parse::Parse for RouteArgs {

#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;

use super::*;

#[rstest]
// Method only
#[case("get", true, Some("get"), None, None)]
Expand Down
Loading