Skip to content

Commit

Permalink
allow path in status for ApiResponse (#937)
Browse files Browse the repository at this point in the history
  • Loading branch information
ihor-rud authored Jan 2, 2025
1 parent e170d13 commit 38e6002
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 14 deletions.
70 changes: 70 additions & 0 deletions poem-openapi-derive/src/common_args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,76 @@ pub(crate) struct ExtraHeader {
pub(crate) deprecated: bool,
}

pub(crate) enum LitOrPath<T> {
Lit(T),
Path(syn::Path),
}

impl<T> darling::FromMeta for LitOrPath<T>
where
T: darling::FromMeta,
{
fn from_nested_meta(item: &darling::ast::NestedMeta) -> darling::Result<Self> {
T::from_nested_meta(item)
.map(Self::Lit)
.or_else(|_| syn::Path::from_nested_meta(item).map(Self::Path))
}

fn from_meta(item: &syn::Meta) -> darling::Result<Self> {
T::from_meta(item)
.map(Self::Lit)
.or_else(|_| syn::Path::from_meta(item).map(Self::Path))
}

fn from_none() -> Option<Self> {
T::from_none()
.map(Self::Lit)
.or_else(|| syn::Path::from_none().map(Self::Path))
}

fn from_word() -> darling::Result<Self> {
T::from_word()
.map(Self::Lit)
.or_else(|_| syn::Path::from_word().map(Self::Path))
}

fn from_list(items: &[darling::ast::NestedMeta]) -> darling::Result<Self> {
T::from_list(items)
.map(Self::Lit)
.or_else(|_| syn::Path::from_list(items).map(Self::Path))
}

fn from_value(value: &Lit) -> darling::Result<Self> {
T::from_value(value)
.map(Self::Lit)
.or_else(|_| syn::Path::from_value(value).map(Self::Path))
}

fn from_expr(expr: &syn::Expr) -> darling::Result<Self> {
T::from_expr(expr)
.map(Self::Lit)
.or_else(|_| syn::Path::from_expr(expr).map(Self::Path))
}

fn from_char(value: char) -> darling::Result<Self> {
T::from_char(value)
.map(Self::Lit)
.or_else(|_| syn::Path::from_char(value).map(Self::Path))
}

fn from_string(value: &str) -> darling::Result<Self> {
T::from_string(value)
.map(Self::Lit)
.or_else(|_| syn::Path::from_string(value).map(Self::Path))
}

fn from_bool(value: bool) -> darling::Result<Self> {
T::from_bool(value)
.map(Self::Lit)
.or_else(|_| syn::Path::from_bool(value).map(Self::Path))
}
}

#[derive(FromMeta)]
pub(crate) struct CodeSample {
pub(crate) lang: String,
Expand Down
33 changes: 20 additions & 13 deletions poem-openapi-derive/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use quote::quote;
use syn::{Attribute, DeriveInput, Error, Generics, Path, Type};

use crate::{
common_args::ExtraHeader,
common_args::{ExtraHeader, LitOrPath},
error::GeneratorResult,
utils::{get_crate_name, get_description, optional_literal, optional_literal_string},
};
Expand All @@ -33,7 +33,7 @@ struct ResponseItem {
fields: Fields<ResponseField>,

#[darling(default)]
status: Option<u16>,
status: Option<LitOrPath<u16>>,
#[darling(default)]
content_type: Option<String>,
#[darling(default, multiple, rename = "header")]
Expand Down Expand Up @@ -218,7 +218,7 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
// #[oai(status = 200)]
// Item(media)
let media_ty = &values[0].ty;
let status = get_status(variant.ident.span(), variant.status)?;
let status = get_status(variant.ident.span(), &variant.status)?;
let (update_response_content_type, update_meta_content_type) = update_content_type(
&crate_name,
variant.content_type.as_deref(),
Expand Down Expand Up @@ -257,7 +257,7 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
0 => {
// #[oai(status = 200)]
// Item
let status = get_status(variant.ident.span(), variant.status)?;
let status = get_status(variant.ident.span(), &variant.status)?;
let item = if !headers.is_empty() {
quote!(#ident::#item_ident(#(#match_headers),*))
} else {
Expand Down Expand Up @@ -362,16 +362,23 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
Ok(expanded)
}

fn get_status(span: Span, status: Option<u16>) -> GeneratorResult<TokenStream> {
let status = status.ok_or_else(|| Error::new(span, "Missing status attribute"))?;
if !(100..1000).contains(&status) {
return Err(Error::new(
span,
"Invalid status code, it must be greater or equal to 100 and less than 1000.",
)
.into());
fn get_status(span: Span, status: &Option<LitOrPath<u16>>) -> GeneratorResult<TokenStream> {
let status = status
.as_ref()
.ok_or_else(|| Error::new(span, "Missing status attribute"))?;
match status {
LitOrPath::Lit(status) => {
if !(100..1000).contains(status) {
return Err(Error::new(
span,
"Invalid status code, it must be greater or equal to 100 and less than 1000.",
)
.into());
}
Ok(quote!(#status))
}
LitOrPath::Path(ident) => Ok(quote!(#ident)),
}
Ok(quote!(#status))
}

fn parse_fields(
Expand Down
4 changes: 3 additions & 1 deletion poem-openapi/tests/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -353,13 +353,15 @@ async fn payload_request() {

#[tokio::test]
async fn response() {
const ALREADY_EXISTS_CODE: u16 = 409;

#[derive(ApiResponse)]
enum MyResponse {
/// Ok
#[oai(status = 200)]
Ok,
/// Already exists
#[oai(status = 409)]
#[oai(status = ALREADY_EXISTS_CODE)]
AlreadyExists(Json<u16>),
/// Default
Default(StatusCode, PlainText<String>),
Expand Down

0 comments on commit 38e6002

Please sign in to comment.