-
Notifications
You must be signed in to change notification settings - Fork 73
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add examples for wrapped Query and Path extractors #137
Comments
Thanks for this. Were you able to use this in a handler? I'm getting an error complaining about an unmet trait bound when using with |
@Rolv-Apneseth, here is a fully working example. GET http://localhost:3030/test/some-path?some_query=11 use aide::{
axum::{routing::get_with, ApiRouter, IntoApiResponse},
openapi::OpenApi,
transform::TransformOpenApi,
OperationIo,
};
use axum::{
extract::rejection::{PathRejection, QueryRejection},
http::StatusCode,
response::{IntoResponse, Response},
};
use axum_jsonschema::JsonSchemaRejection;
use axum_macros::{FromRequest, FromRequestParts};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use tokio::net::TcpListener;
use tower_http::trace::TraceLayer;
use tracing_subscriber::prelude::*;
// Errors
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("validation")]
SchemaValidation(JsonSchemaRejection),
#[error("validation")]
QueryValidation(QueryRejection),
#[error("validation")]
PathValidation(PathRejection),
#[error("unexpected")]
Unexpected(String),
}
impl From<QueryRejection> for Error {
fn from(rejection: QueryRejection) -> Self {
Self::QueryValidation(rejection)
}
}
impl From<PathRejection> for Error {
fn from(rejection: PathRejection) -> Self {
Self::PathValidation(rejection)
}
}
impl From<JsonSchemaRejection> for Error {
fn from(rejection: JsonSchemaRejection) -> Self {
Self::SchemaValidation(rejection)
}
}
impl IntoResponse for Error {
fn into_response(self) -> Response {
match self {
Error::QueryValidation(error) => ErrorResponse::QueryValidation {
message: error.body_text(),
}
.into_response(),
_ => ErrorResponse::Unexpected {
message: "Unexpected error".into(),
}
.into_response(),
}
}
}
#[derive(Serialize, JsonSchema)]
#[serde(tag = "error", rename_all = "snake_case")]
pub enum ErrorResponse {
QueryValidation { message: String },
Unexpected { message: String },
}
impl IntoResponse for ErrorResponse {
fn into_response(self) -> Response {
let status = match &self {
ErrorResponse::QueryValidation { message } => StatusCode::BAD_REQUEST,
_ => StatusCode::INTERNAL_SERVER_ERROR,
};
(status, Json(self)).into_response()
}
}
pub type Result<T> = std::result::Result<T, Error>;
// Aide newtypes
#[derive(FromRequest, OperationIo)]
#[from_request(via(axum_jsonschema::Json), rejection(Error))]
#[aide(
input_with = "axum_jsonschema::Json<T>",
output_with = "axum_jsonschema::Json<T>",
json_schema
)]
pub struct Json<T>(pub T);
impl<T> IntoResponse for Json<T>
where
T: Serialize,
{
fn into_response(self) -> axum::response::Response {
axum::Json(self.0).into_response()
}
}
#[derive(FromRequestParts, OperationIo)]
#[from_request(via(axum::extract::Query), rejection(Error))]
#[aide(
input_with = "axum::extract::Query<T>",
output_with = "axum_jsonschema::Json<T>",
json_schema
)]
#[aide]
pub struct Query<T>(pub T);
#[derive(FromRequestParts, OperationIo)]
#[from_request(via(axum::extract::Path), rejection(Error))]
#[aide(
input_with = "axum::extract::Path<T>",
output_with = "axum_jsonschema::Json<T>",
json_schema
)]
pub struct Path<T>(pub T);
// Example api
#[derive(Deserialize, JsonSchema)]
struct SomePath {
some_path: String,
}
#[derive(Deserialize, JsonSchema)]
struct SomeQuery {
some_query: String,
}
#[derive(Serialize, JsonSchema)]
struct SomeResponse {
hello: String,
}
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
"playground_example=debug,tower_http=debug,axum::rejection=trace".into()
}),
)
.with(tracing_subscriber::fmt::layer())
.init();
let mut api = OpenApi::default();
let app = ApiRouter::new()
.api_route(
"/test/:some_path",
get_with(test, |t| t.response::<200, Json<SomeResponse>>()),
)
.finish_api_with(&mut api, api_docs)
.layer(TraceLayer::new_for_http());
let json = serde_json::to_string_pretty(&api).unwrap();
tracing::debug!("{}", json);
let listener = TcpListener::bind(format!("127.0.0.1:3030")).await.unwrap();
tracing::info!("listening on http://{}", listener.local_addr().unwrap());
axum::serve(listener, app).await.unwrap();
Ok(())
}
#[axum::debug_handler]
async fn test(
Path(SomePath { some_path }): Path<SomePath>,
Query(SomeQuery { some_query }): Query<SomeQuery>,
) -> impl IntoApiResponse {
tracing::debug!("path: {some_path}, query: {some_query}");
Json(SomeResponse {
hello: "world".into(),
})
.into_response()
}
fn api_docs(api: TransformOpenApi) -> TransformOpenApi {
api.title("Aide axum Open API")
.summary("An example application")
.default_response::<Json<ErrorResponse>>()
} |
Ah didn't know about that. Thank you so much, I'll have a closer look at your code and try it out later. |
Yep, that was exactly it, thanks again. I agree this should be noted in the documentation or an example |
For example, I wrapped Query to Another Extractor as recommended for getting a custom error response:
And on the aide side, I did this:
If it's correct, maybe it needs to be added to the documentation?
I think it's a common pattern.
The text was updated successfully, but these errors were encountered: