You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Thank you for this library! poem-openapi is very elegant, but poem has some limitations that sent me back to axum, and I found this.
I'm having difficulty figuring out how to use this properly though, because there are so many options, and I can't tell what things are auto-discovered / auto-derived, and what needs manual configuration. That's what I really liked about poem-openapi -- it was just automatic. My biggest pet peeve is duplicated code, because that means I have to remember to change it twice or thrice instead of once.
I made this custom type because I want the input to be either an integer or a Roman Numeral, and axum parses either correctly:
use std::str::FromStr;use serde::Deserialize;use utoipa::{IntoParams,ToSchema};/// Integer or Roman Numeral, e.g. `19` or `XIX`#[derive(Debug,IntoParams,ToSchema,Eq,PartialEq,Hash)]#[schema( examples(19,"XIX"), value_type = String, format = Regex, pattern = r#"^\d{0,3}$|^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$"#,)]#[into_params( names("number"), style = Simple, parameter_in = Path,)]// Why do I need an example here when there's also one in the schema?// If I don't include the following example, Swagger UI never shows an example (see below).pubstructRomanNumber(#[param(example = "XIX")]pubu32);impl<'de>Deserialize<'de>forRomanNumber{fndeserialize<D>(deserializer:D) -> Result<Self,D::Error>whereD: serde::Deserializer<'de>,{let s = String::deserialize(deserializer)?;
s.parse().map_err(serde::de::Error::custom)}}implFromStrforRomanNumber{typeErr = &'staticstr;fnfrom_str(s:&str) -> Result<Self,Self::Err>{if s.chars().all(|c| c.is_ascii_digit()){returnmatch s.parse::<u32>(){Ok(num) => Ok(RomanNumber(num)),Err(_) => Err("Number too large"),};}
writings::roman::from(s).map(RomanNumber).ok_or("Invalid number or Roman Numeral")}}
Please see my comments in the code about what happens when I include or exclude the params attribute in the utoipa::path annotation:
duplicated parameters in the spec when I declare them explicitly;
incorrect parameter types in the spec when I declare them explicitly
no parameters at all if I don't use a tuple for a single parameter;
examples not showing up when I don't declare them explicitly;
response body examples are showing up just fine.
(Note: this is from a module that is mounted at /gleanings.)
use axum::{Json, extract::Path};use utoipa::OpenApiasDeriveOpenApi;use utoipa_axum::{router::OpenApiRouter, routes};use writings::{EmbedAllTraitas _,GleaningParagraph};usecrate::{ApiError,ApiResult, roman_number::RomanNumber};#[derive(DeriveOpenApi)]#[openapi(components(schemas(GleaningParagraph,RomanNumber)))]pubstructGleaningsApiDoc;pubfnrouter() -> OpenApiRouter{OpenApiRouter::with_openapi(GleaningsApiDoc::openapi()).routes(routes!(get_all_gleanings)).routes(routes!(get_gleanings_number)).routes(routes!(get_gleanings_number_paragraph))}#[utoipa::path( get, path = "/", responses((status = OK, body = Vec<GleaningParagraph>, description = "Gleanings Paragraphs"),))]pubasyncfnget_all_gleanings() -> ApiResult<Json<Vec<GleaningParagraph>>>{Ok(Json(GleaningParagraph::all().to_vec()))}#[utoipa::path( get, path = "/{number}",// When the following `params` attribute is included:// - the spec has a duplicate `number` param;// - both `number` params have the "XIX" example pre-filled;// - both `number` params show as "integer($int32)", which is wrong;// - the first `number` param only accepts an integer, ignoring the regex.// When I make the param **not** a tuple, **and** the following `params` attribute is included:// - the type is incorrect: "integer($int32)";// - the example "XIX" shows;// - the regex does **not** work, and Swagger will **only** accept an integer.// When the following `params` attribute is excluded:// - the spec has one param (correct);// - the `number` type shows as "string($regex)" (correct);// - and the regex validation works in Swagger UI;// - but there is **no** example in Swagger UI.// params(RomanNumber), responses((status = OK, body = Vec<GleaningParagraph>, description = "Gleanings Paragraphs"),(status = BAD_REQUEST, description = "bad request / invalid parameters")))]pubasyncfnget_gleanings_number(// If this is not a tuple, the spec has no parameters, unless I provide the `params` argument in `utoipa::path` above!!Path((number,)):Path<(RomanNumber,)>,) -> ApiResult<Json<Vec<GleaningParagraph>>>{Ok(Json(GleaningParagraph::all().iter().filter(|p| p.number == number.0).cloned().collect(),))}#[utoipa::path( get, path = "/{number}/{paragraph}",// When the following `params` attribute is included:// - same duplication as above, but in order: `number`, `paragraph`, `number`;// - as above, both `number` params have the "XIX" example and incorrect "integer($int32)" types;// - as above, the first `number` param only accepts an integer, ignoring the regex.// When the following `params` attribute is excluded:// - the spec has two params (correct);// - as above, the `number` type shows as "string($regex)" (correct);// - as above, the regex validation works in Swagger UI;// - as above, there is **no** example in Swagger UI.// params(RomanNumber, ("paragraph" = u32, Path)), responses((status = OK, body = GleaningParagraph, description = "Gleanings Paragraph"),(status = BAD_REQUEST, description = "bad request / invalid parameters")))]pubasyncfnget_gleanings_number_paragraph(Path((number, paragraph)):Path<(RomanNumber,u32)>,) -> ApiResult<Json<GleaningParagraph>>{Ok(Json(GleaningParagraph::all().iter().find(|p| p.number == number.0 && p.paragraph == paragraph).cloned().ok_or(ApiError::NotFound)?,))}
In conclusion, it seems to me:
utoipa is partially inferring the parameters, but only when they are provided in tuples;
overriding the params attribute on the utoipa::path annotation does not remove duplicate inferred types on which IntoParams is derived;
I'm having difficulty understanding how to use this crate properly. Maybe it's a problem due to the "custom" RomanNumber type I created? But wouldn't it be the same for any "custom" type, or am I defining it incorrectly?
The text was updated successfully, but these errors were encountered:
Thank you for this library!
poem-openapi
is very elegant, butpoem
has some limitations that sent me back toaxum
, and I found this.I'm having difficulty figuring out how to use this properly though, because there are so many options, and I can't tell what things are auto-discovered / auto-derived, and what needs manual configuration. That's what I really liked about
poem-openapi
-- it was just automatic. My biggest pet peeve is duplicated code, because that means I have to remember to change it twice or thrice instead of once.I made this custom type because I want the input to be either an integer or a Roman Numeral, and axum parses either correctly:
Please see my comments in the code about what happens when I include or exclude the
params
attribute in theutoipa::path
annotation:(Note: this is from a module that is mounted at
/gleanings
.)In conclusion, it seems to me:
utoipa
is partially inferring the parameters, but only when they are provided in tuples;params
attribute on theutoipa::path
annotation does not remove duplicate inferred types on whichIntoParams
is derived;RomanNumber
type I created? But wouldn't it be the same for any "custom" type, or am I defining it incorrectly?The text was updated successfully, but these errors were encountered: