Closed richardmarston closed 3 years ago
I don't know exactly why you want to wrap the whole response in a structs. And easier way would be to do the following: (note example code below still uses rocket v0.4.x, so things might be slightly different, but I hope this gives you an example)
Create a type
that you will (most likely) reuse for most of your API endpoint:
pub type Result<T> = std::result::Result<rocket_contrib::json::Json<T>, APIErrorNoContent>;
Where APIErrorNoContent
is your own custom type of error.
You can then use OpenApiResponder
to add error codes to your error type, so you can have multiple error types for direct endpoints (post, get, put,..)
For example:
impl OpenApiResponder<'_> for APIErrorNoContent {
fn responses(_: &mut OpenApiGenerator) -> OpenApiResult<Responses> {
let mut responses = Responses::default();
add_204_error(&mut responses); // These functions are defined in code linked below
add_400_error(&mut responses);
add_404_error(&mut responses);
add_500_error(&mut responses);
Ok(responses)
}
}
Example is from: https://gitlab.com/df_storyteller/df-storyteller/-/blob/master/df_st_api/src/api_errors.rs
Your endpoint mainly stays the same:
#[openapi]
#[get("/example_response", format="application/json")]
pub async fn get_example_response() -> crate::Result<Example> {
Ok(Json(Example { error: "hi".to_owned() } ))
}
If you want more example code or look at some other things: https://gitlab.com/df_storyteller/df-storyteller/-/tree/master/df_st_api/src Here is the docs that project generates: https://docs.dfstoryteller.com/rapidoc/
I hope this helps even though it is not a direct answer to your problem. I hope this gives you some inside on how to approach this. If this does not help let me know and I'll take a look at the exact problem you are having.
Hi @ralpha , thank you for your reply. I took the idea of wrapping the Response in a struct from https://api.rocket.rs/master/rocket/derive.Responder.html. In particular, the example that looks like this:
#[derive(Responder)]
enum MyResponder {
A(String),
B(File, ContentType, #[response(ignore)] Other),
}
#[derive(Responder)]
struct MyOtherResponder {
inner: NamedFile,
header: ContentType,
#[response(ignore)]
other: Other,
}
I started working with this structure because I wanted a single Responder type that was capable of returning a different response code and a different content type depending on the validity of the request, but that would leave a neater signature on the endpoint than my previous solution of nesting the Result inside a tuple with a Status:
pub async fn my_function() -> (Status, Result<Json<Example>, String>)
I think this also had the effect of making the #[openapi] requirements more complicated but I honestly can't remember now.
I think your suggestion of customizing the Error rather than customizing the Response looks much simpler, so I'll try that tomorrow.
Thanks again.
If you need to return different results in the "correct" case you still need to look at other solutions, in that case the one above does not work. But from my experience this is never desired when creating API's. Then you usually have 2 cases. 1 if all is okay. and 1 where there is an error.
And the way I suggested above works wonderfully in that case. You can turn the error into json with custom HTTP codes using the following code:
/// Error messages returned to user
#[derive(Debug, serde::Serialize, schemars::JsonSchema)]
pub struct Error {
/// The title of the error message
pub err: String,
/// The description of the error
pub msg: Option<String>,
// Internal error codes
pub code: u16,
// HTTP Status Code returned
#[serde(skip)]
pub http_status_code: u16,
}
impl<'r> Responder<'r> for Error {
fn respond_to(self, _: &Request<'_>) -> response::Result<'r> {
Response::build()
.sized_body(std::io::Cursor::new(
// Convert object to json
serde_json::to_string(&self).unwrap(),
))
.header(ContentType::JSON)
.status(Status::new(self.http_status_code, ""))
.ok()
}
}
I then just implement From<>
to convert other types of errors to the type that is returned. (like io error, serde errors, ....)
impl From<InternalError> for Error {
fn from(internal_error: InternalError) -> Self {
// ...
}
}
This way I can just use the ?
in the endpoint and can just write clean code and still return nice errors if something is wrong.
I am trying to use okapi with a custom Responder for a Json<> wrapped struct. I am getting this error:
Here is my Cargo.toml:
Here is my main.rs:
I don't understand how I can generate the schema for a Json struct. I can't use the derive(JsonSchema) attribute because it's not a struct, enum or union.