handlers that don't satisfy `OperationHandler` return cryptic error messages, making it hard to find out what to do to to satisfy it
When trying to add an api route handler that returns an eyre::Result<T> (which is a type alias for Result<T, eyre::Report>), I get the following error message:
error[E0277]: the trait bound `fn() -> impl Future<Output = Result<(), ErrReport>> {eyre_example}: OperationHandler<_, _>` is not satisfied
--> src/main.rs:12:54
|
12 | .api_route("/eyre", aide::axum::routing::get(eyre_example)) //error
| ------------------------ ^^^^^^^^^^^^ the trait `OperationHandler<_, _>` is not implemented for fn item `fn() -> impl Future<Output = Result<(), ErrReport>> {eyre_example}`
| |
| required by a bound introduced by this call
|
= help: the trait `OperationHandler<I, O>` is implemented for `Layered<L, H, T, S>`
note: required by a bound in `aide::axum::routing::get`
--> /Users/vusalshahbaz/.cargo/registry/src/index.crates.io-6f17d22bba15001f/aide-0.14.1/src/axum/routing.rs:345:1
|
345 | method_router_top_level!(get, get_with);
| ^^^^^^^^^^^^^^^^^^^^^^^^^---^^^^^^^^^^^
| | |
| | required by a bound in this function
| required by this bound in `get`
= note: this error originates in the macro `method_router_top_level` (in Nightly builds, run with -Z macro-backtrace for more info)
I understand I'm on my own when I use eyre, and I'm already dealing with it myself (in my real code I'm actually wrapping it in a newtype that I'm implementing the traits for myself, such as IntoResponse, but the error messages are really cryptic, mostly because everything is hidden in a macro, but it's made worse by OperationHandler being a private type which is also hidden in docs, so I don't even know what it wants, why it's there, and how to implement it.
I have dug around in the source and found out that OperationHandler has a blanket implementation over anything that returns OperationOutput, and that has a blanket implementation for a lot of std types, including over Result<T,E> where both implement already implement it, and eyre::Report doesn't impl OperationOutput, which is a quick fix since I already have a newtype over it, but while OperationOutput does have docs, at this point, I already expected it not to so that took some more digging around in the sources. I feel like this could be documented better, because most people wouldn't even go dig in source code to find out that the undocumented type has a blanket implementation for everything that implements a documented trait, since the error message doesn't mention the documented trait, and in fact mentions some completely unrelated trait.
I know that what the compiler suggests or prints isn't really in the developer's control (there are ways to coax it, but it's infeasible most of the time), but a mention in the docs or something like that might work better
here's a quick repro:
use aide::{axum::ApiRouter, openapi::OpenApi};
async fn eyre_example() -> eyre::Result<()> {
Ok(())
}
async fn std_example() -> Result<(), ()> {
Ok(())
}
#[tokio::main]
async fn main() -> eyre::Result<()> {
let app = ApiRouter::new()
.api_route("/eyre", aide::axum::routing::get(eyre_example)) //error
.api_route("/std", aide::axum::routing::get(std_example)) //works
.finish_api(&mut OpenApi::default());
let listener = tokio::net::TcpListener::bind("127.0.0.1:8080")
.await
.unwrap();
axum::serve(listener, app).await;
Ok(())
}
with the following Cargo.toml
[package]
name = "repro"
version = "0.1.0"
edition = "2021"
[dependencies]
aide = { version = "0.14.0", features = ["axum", "scalar"] }
axum = "0.8.1"
eyre = "0.6.12"
tokio = { version = "1.43.0", features = ["full"] }
OperationHandler is hidden because it is basically a hack that allows for variadic functions. It is implemented for functions that are valid axum handlers while all the parameters implement OperationInput and the return value implements OperationOutput.
I'm not against revealing it in docs, we could also add a diagnostics message, like axum has it here. PRs are welcome.
I'm having a similar issue, but mine involves a pretty heavy use of generics for easy unit testing. The function has a generic param that is associated with app state. The state param is the only thing in the function signature that is not OperationInput, everything else resolves to a concrete type that implements either OperationInput or OperationOuput for parameters and return types respectively.
Here is the handler in question, the error is similar to the one described above but doesn't seem to resolve when implementing OperationInput and Output for the handler.
pub fn http_api_routes<DS: DataSyncService>() -> ApiRouter<AppState<DS>> {
let mut router = ApiRouter::new();
router = router.api_route(
"/do_the_thing",
post_with(http_handle::<DS>, |op| { // Error here
op.description("Do a thing with app state")
}),
);
router
}
pub(crate) async fn http_handler<DS: DataService>(
State(state): State<AppState<DS>>,
Json(body): Json<HttpRequestBody>, // HttpRequestBody implements OperationInput
) -> Result<HttpApiSuccess<HttpResponse>, HttpApiError> { // HttpApiSuccess, HttpResponse, and HttpApiError implement OperationOuput
state
.do_the_thing(body)
.await
.map(|res| HttpApiSuccess::new(StatusCode::OK, res.into()))
.map_err(|err| err.into())
}
@tamasfe After some digging I found out that my Json extractor for the input is the culprit here. But it's confusing because aide implements OperationInput for Json<T> as long as T is JsonSchema AND the axum feature is enabled. All of those conditions are true for my HttpRequest struct. Are there additional type bounds that I am not seeing here?
Wow that was a rather deep rabbit hole.
TL;DR: The issue was schemars versioning. Users adding schemars as a direct dependency need to ensure they are using the SAME VERSION as used in aide. Or rather that their version is allowed to resolve to the same version given the cargo manifest dependency syntax.
Long Version
I had already added schemars v0.8.22 as a dependency only 20 days ago (this project is moving crazy fast) and derived JsonSchema for all of my structs under that version. aide version 0.15.0 is expecting trait implementations of schemars::JsonSchema at version 0.9.0. Due to the several levels of indirection and trait bounds to make OperationHandler work. The error that I was getting was a red herring, saying that OperationInput was not implemented for my input extractor, when the real issue is that the trait bound for the blanket implementation of OperationInput on Json<T> was not satisfied.
The solution (or rather what led me to the solution) was to write a test that short circuited all of the weird layers of indirection:
When Cargo.toml is:
[dependencies]
aide = { version = "0.15.0", features = ["axum", "axum-json"] }
schemars = "0.8.22"
My test looks like this:
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_generic_works() {
struct TestGeneric<OI: OperationInput> {
input: OI,
}
TestGeneric {
/*the trait bound `interfaces::http::execute_dql::ExecuteDqlHttpRequestBody: schemars::JsonSchema` is not satisfied
perhaps two different versions of crate `schemars` are being used?
you can use `cargo tree` to explore your dependency tree
required for `axum::Json<interfaces::http::do_thing::HttpRequestBody>` to implement `aide::OperationInput` */
input: Json(HttpRequestBody {
doing_things_input: "do_the_thing".to_string()
}),
};
}
}
The error above went away when I changed my Cargo.toml to
[dependencies]
aide = { version = "0.15.0", features = ["axum", "axum-json"] }
schemars = "0.9.0" # "0" would also work as it is allowed to resolve to <1.0.0
This error will appear again when the newest release of aide is dropped.
There's not really anything actionable in here, apart from maybe the idea of having something like axum's debug_handler. Feel free to open a dedicated issue for that, because here it's just a remark amidst a discussion of some debugging issues.
And next time you've got a problem like this, please open a discussion instead of an issue :)