From 46828585f2792426be68415e447214f028a8e614 Mon Sep 17 00:00:00 2001 From: ihor rudynskyi Date: Sun, 22 Dec 2024 17:19:54 +0100 Subject: [PATCH] allow path in status oai for ApiResponse --- poem-openapi-derive/src/common_args.rs | 70 ++++++++++++++++++++++++++ poem-openapi-derive/src/response.rs | 33 +++++++----- poem-openapi/tests/api.rs | 4 +- 3 files changed, 93 insertions(+), 14 deletions(-) diff --git a/poem-openapi-derive/src/common_args.rs b/poem-openapi-derive/src/common_args.rs index a469f9a897..2947c006bc 100644 --- a/poem-openapi-derive/src/common_args.rs +++ b/poem-openapi-derive/src/common_args.rs @@ -222,6 +222,76 @@ pub(crate) struct ExtraHeader { pub(crate) deprecated: bool, } +pub(crate) enum LitOrPath { + Lit(T), + Path(syn::Ident), +} + +impl darling::FromMeta for LitOrPath +where + T: darling::FromMeta, +{ + fn from_nested_meta(item: &darling::ast::NestedMeta) -> darling::Result { + T::from_nested_meta(item) + .map(Self::Lit) + .or_else(|_| syn::Ident::from_nested_meta(item).map(Self::Path)) + } + + fn from_meta(item: &syn::Meta) -> darling::Result { + T::from_meta(item) + .map(Self::Lit) + .or_else(|_| syn::Ident::from_meta(item).map(Self::Path)) + } + + fn from_none() -> Option { + T::from_none() + .map(Self::Lit) + .or_else(|| syn::Ident::from_none().map(Self::Path)) + } + + fn from_word() -> darling::Result { + T::from_word() + .map(Self::Lit) + .or_else(|_| syn::Ident::from_word().map(Self::Path)) + } + + fn from_list(items: &[darling::ast::NestedMeta]) -> darling::Result { + T::from_list(items) + .map(Self::Lit) + .or_else(|_| syn::Ident::from_list(items).map(Self::Path)) + } + + fn from_value(value: &Lit) -> darling::Result { + T::from_value(value) + .map(Self::Lit) + .or_else(|_| syn::Ident::from_value(value).map(Self::Path)) + } + + fn from_expr(expr: &syn::Expr) -> darling::Result { + T::from_expr(expr) + .map(Self::Lit) + .or_else(|_| syn::Ident::from_expr(expr).map(Self::Path)) + } + + fn from_char(value: char) -> darling::Result { + T::from_char(value) + .map(Self::Lit) + .or_else(|_| syn::Ident::from_char(value).map(Self::Path)) + } + + fn from_string(value: &str) -> darling::Result { + T::from_string(value) + .map(Self::Lit) + .or_else(|_| syn::Ident::from_string(value).map(Self::Path)) + } + + fn from_bool(value: bool) -> darling::Result { + T::from_bool(value) + .map(Self::Lit) + .or_else(|_| syn::Ident::from_bool(value).map(Self::Path)) + } +} + #[derive(FromMeta)] pub(crate) struct CodeSample { pub(crate) lang: String, diff --git a/poem-openapi-derive/src/response.rs b/poem-openapi-derive/src/response.rs index 7be505897c..b40b553f77 100644 --- a/poem-openapi-derive/src/response.rs +++ b/poem-openapi-derive/src/response.rs @@ -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}, }; @@ -33,7 +33,7 @@ struct ResponseItem { fields: Fields, #[darling(default)] - status: Option, + status: Option>, #[darling(default)] content_type: Option, #[darling(default, multiple, rename = "header")] @@ -218,7 +218,7 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult { // #[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(), @@ -257,7 +257,7 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult { 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 { @@ -362,16 +362,23 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult { Ok(expanded) } -fn get_status(span: Span, status: Option) -> GeneratorResult { - 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>) -> GeneratorResult { + 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( diff --git a/poem-openapi/tests/api.rs b/poem-openapi/tests/api.rs index c5b9e31a79..85c6e7c025 100644 --- a/poem-openapi/tests/api.rs +++ b/poem-openapi/tests/api.rs @@ -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), /// Default Default(StatusCode, PlainText),