This is a question.
[dependencies]
rocket = "0.2.8"
rocket_codegen = "0.2.8"
rocket_contrib = "0.2.8"
serde = "^1.0.8"
serde_derive = "^1.0.8"
serde_json = "^1.0"
state = "^0.2"
I want to serve JSON for a route, so I have a function signature looking like the following:
#[get("/word/<vocabulary_id>/<word_id>")]
fn get_word(vocabulary_context: State<VocabularyContext>,
vocabulary_id: &str,
word_id: &str) -> JSON<String> {
...
}
However, in case vocabulary_id or word_id does not fit anything existing on the server side, I would like to return a Failure. So I naively tried something like the following:
#[get("/word/<vocabulary_id>/<word_id>")]
fn get_word(vocabulary_context: State<VocabularyContext>,
vocabulary_id: &str,
word_id: &str) -> JSON<String> {
if condition {
Failure(Status::NotFound)
} else {
JSON(the_word_as_string)
}
}
This does not work, because the JSON return type is expected and I get the following error:
|
82 | return Failure(Status::NotFound)
| ^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `rocket::response::content::JSON`, found struct `rocket::response::Failure`
Of course this is to be expected, but I do not understand how to use wrapping of Responses (if that is the right tool for the job) to make both responses look like the same type, lets call it A, so that I can put A in the function signature.
How can I return JSON or an error response?
Someone else is probably better qualified to answer this, but I think it's just a matter of changing your function's return type to rocket::Outcome:
fn get_word(vocabulary_context: State<VocabularyContext>,
vocabulary_id: &str,
word_id: &str) -> Outcome<JSON<String>, Status> {
In the successful case, you'll need to return the Success variant of Outcome, like
if condition {
Failure(Status::NotFound)
} else {
Success(JSON(the_word_as_string))
}
@uniphil I am new to Rust error handling, so this gave me some new idea about how it works. Very useful. The Outcome seems to take 3 types (Success case, Error case, Forward case) according to https://api.rocket.rs/rocket/outcome/index.html . So I try using Result<S, E> as follows:
#[get("/word/<vocabulary_id>/<word_id>")]
fn get_word(vocabulary_context: State<VocabularyContext>,
vocabulary_id: &str,
word_id: &str) -> Result<JSON<String>, Failure> {
let the_vocabularies: Vec<Vocabulary> = vocabulary_context.vocabularies.iter()
.filter(|voc| voc.metadata.identifier == vocabulary_id)
.cloned()
.collect::<Vec<Vocabulary>>();
if the_vocabularies.is_empty() {
return Failure(Status::NotFound)
}
let the_words: Vec<Word> = the_vocabularies.first().unwrap().words.iter()
.filter(|word| word.metadata.id == word_id)
.cloned()
.collect::<Vec<Word>>();
if the_words.is_empty() {
return Failure(Status::NotFound)
}
let the_word_as_string: String = serde_json::to_string(&the_words[0])
.expect("could not serialize struct Vocabulary");
Success(JSON(the_word_as_string))
}
It tells me that:
expected type `std::result::Result<rocket::response::content::JSON<std::string::String>, rocket::response::Failure>`
found type `rocket::response::Failure` [E0308]
mismatched types (expected enum `std::result::Result`, found struct `rocket::response::Failure`) [E0308]
I think I still need to somehow wrap it?
Idiomatically, I think you're supposed to just return Result<JSON<String>, Status>
You need to read up more on how Result (and enums in general) works ( https://rustbyexample.com/custom_types/enum.html ).
Failure(Status::NotFound) should be Err(Status::NotFound)
And
Success(JSON(the_word_as_string)) should be Ok(JSON(the_word_as_string))
Thanks, I think I have it now:
#[get("/word/<vocabulary_id>/<word_id>")]
fn get_word(
vocabulary_context: State<VocabularyContext>,
vocabulary_id: &str,
word_id: &str
) -> Result<JSON<String>, Failure> {
let the_vocabularies: Vec<Vocabulary> = vocabulary_context.vocabularies.iter()
.filter(|voc| voc.metadata.identifier == vocabulary_id)
.cloned()
.collect::<Vec<Vocabulary>>();
if the_vocabularies.is_empty() {
return Err(Failure(Status::NotFound));
}
let the_words: Vec<Word> = the_vocabularies.first().unwrap().words.iter()
.filter(|word| word.metadata.id == word_id)
.cloned()
.collect::<Vec<Word>>();
if the_words.is_empty() {
return Err(Failure(Status::NotFound));
}
let the_word_as_string: String = serde_json::to_string(&the_words[0])
.expect("could not serialize struct Vocabulary");
Ok(JSON(the_word_as_string))
}
If all you need to do is return a NotFound, you can simply return an Option<JSON<String>>. Rocket will return a "not found" failure automatically on the None case.
Since it looks like you've figured out things, I'm closing out this issue. Feel free to continue commenting if you have other issues.
@SergioBenitez That makes things simpler in my case. I am unsure how I would make use of the automatic return of failure though.
Would I use something like this:
match get_me_the_option() {
Some(value) => value
}
and simply leave away the _ => ... or None => ... ?
You would just return the Option from get_me_the_option() directly. Rocket will take care of 404ing if it's None and sending the JSON if it's Some.
Your handler is just a function, and if it's defined to return an Option<T>, then you can't return the unwrapped value -- that's not valid rust. You have to return one of the members of Option: Some(value) or None.
PS: #rust-begginers is full of friendly folks :)
This bit me too, thank you @ZelphirKaltstahl for posting your working example! @SergioBenitez this would be a great addition to the otherwise excellent docs. :)