I'm trying to build an HTTP Server using Actix web framework in Rust.
I'm used to segregate Business Model and Business Error from HttpResponse.
For doing that, I've my service, CredentialService, that exposes a method that returns a result Result<String, CredentialServiceError>.
My WebServer exposes an API POST /login that accepts username and password and returns a JWT.
The enum `CredentialServiceError' is the following
use derive_more::{Display, Error};
#[derive(Debug, Display, Error)]
pub enum CredentialServiceError {
NoCredentialFound,
ErrorOnGeneratingJWT,
}
My handler is something like:
async fn login(request_body: web::Json<LoginRequest>, credential_service: web::Data<CredentialService>) -> Result<HttpResponse> {
let request_body: LoginRequest = request_body.0;
let jwt = credential_service.login(request_body.username, request_body.password);
let response: Result<LoginResponse, CredentialServiceError> = jwt.map(|jwt| {
LoginResponse { jwt }
});
response.into()
}
I receive this error:
the trait bound `std::result::Result<actix_web::HttpResponse, actix_web::Error>: std::convert::From<std::result::Result<model::LoginResponse, credential_service::CredentialServiceError>>` is not satisfied
the trait `std::convert::From<std::result::Result<model::LoginResponse, credential_service::CredentialServiceError>>` is not implemented for `std::result::Result<actix_web::HttpResponse, actix_web::Error>`
help: the following implementations were found:
<std::result::Result<(), idna::uts46::Errors> as std::convert::From<idna::uts46::Errors>>
<std::result::Result<(), ring::error::Unspecified> as std::convert::From<ring::bssl::Result>>
<std::result::Result<miniz_oxide::MZStatus, miniz_oxide::MZError> as std::convert::From<&miniz_oxide::StreamResult>>
<std::result::Result<miniz_oxide::MZStatus, miniz_oxide::MZError> as std::convert::From<&miniz_oxide::StreamResult>>
and 2 others
note: required because of the requirements on the impl of `std::convert::Into<std::result::Result<actix_web::HttpResponse, actix_web::Error>>` for `std::result::Result<model::LoginResponse, credential_service::CredentialServiceError>`rustc(E0277)
I've also tried to implement
impl error::ResponseError for CredentialServiceError { ... }
impl Into<HttpResponse> for LoginResponse { ... }
The the error doesn't change.
So, how can I convert Result<String, CredentialServiceError> into Result<HttpResponse> ?
So the problem you are facing is, that you want to convert your result into the Result that actix_web functions expect. I hope I understood that correctly.
I don't see an other way than to map your Result to the expected one. There are different ways you could accomplish that. If you would want to keep the implementation details in your LoginResponse struct you could use the Into trait.
impl Into<HttpResponse> for LoginResponse {
fn into(self) -> HttpResponse {
HttpResponse::Ok().body(self.jwt)
}
}
If you don't really care you can use the map functions provided by Result.
fn map_credential_error(error: CredentialServiceError) -> actix_web::error::Error {
match error {
CredentialServiceError::NoCredentialFound => {
actix_web::error::ErrorUnauthorized("Not authorized")
}
CredentialServiceError::ErrorOnGeneratingJWT => {
actix_web::error::ErrorInternalServerError("Something went wrong generating your jwt")
}
}
}
So in the end your login function would look something like this.
async fn login(
request_body: web::Json<LoginRequest>,
credential_service: web::Data<CredentialService>,
) -> Result<HttpResponse> {
let request_body: LoginRequest = request_body.0;
let jwt = credential_service.login(request_body.username, request_body.password);
let response: Result<HttpResponse, CredentialServiceError> =
jwt.map(|jwt| LoginResponse { jwt }.into());
response.map_err(map_credential_error)
}
Hope I could help and that I understood your question.
In addition to #Julius-Kreutz's answer you may consider impl ResponseError trait Source from Chanda, Abhishek. For example if your CredentialServiceError is an enum like:
#[derive(Debug, Display)]
pub enum CredentialServiceError {
#[display(fmt = "Internal Server Error")]
InternalServerError,
#[display(fmt = "BadRequest: {}", _0)]
BadRequest(String),
#[display(fmt = "JWKSFetchError")]
JWKSFetchError,
}
Then yuo can do something like:
impl ResponseError for CredentialServiceError {
fn error_response(&self) -> HttpResponse {
match self {
CredentialServiceError::InternalServerError => {
HttpResponse::InternalServerError().json("Internal Server Error, Please try later")
}
CredentialServiceError::BadRequest(ref message) => HttpResponse::BadRequest().json(message),
ServiceError::JWKSFetchError => {
HttpResponse::InternalServerError().json("Could not fetch JWKS")
}
}
}
}
To enable the .into()
Related
tl;dr Is it possible to extend std::result::Result to add my own variant that signals "things are Okay but also..." and keep impl Result methods like is_ok()?
I want to extend Result to signal additional states that a function caller can use for special cases.
use std::result::Result
use std::io::Error;
/// Extend Result to also signal "things are okay but check on things"
enum ResultExt<T, E> {
Result<T, E>,
OkButCheckThings(T),
}
pub fn do_stuff() -> ResultExt<u64, Error> {
// ...
}
pub fn main() -> {
let var = match do_stuff() {
Ok(val) => { val },
Err(err) => { 0 },
OkButCheckThings(val) => { check_things(); val },
}
dbg!(var);
}
It's possible to plainly extend an Enum. But I would also like to use the underlying Result<T, E> functions like is_ok.
let var2 = do_stuff();
if var2.is_ok() {
println!("It is totally Ok, nothing to check!");
}
I created a rust playground example that successfully extends Result<T, E> but the extended enum cannot use functions like is_ok().
The real-world use-case is a function that calls std::io::Read may need to "modify" the returned Result to signal additional states beyond Ok and Err. But I want these various "meta states" to be captured by one enum, as opposed to returning various other bool flags (I want to avoid return signature with (Result<T>, bool, bool). This would allow one clean match statement of all possible states; Ok, Err, "Okay but...", "Err but ...", etc..
There is no current way of "extending" and enum perse.
But it could be simply solved by embedding your own enum type into the result itself.
Simple example, similar to yours:
use std::fmt::Display;
enum StuffToCheck<T> {
Ok(T),
CheckThis(T),
}
impl<T> StuffToCheck<T>
where
T: Display + Copy,
{
pub fn check_things(&self) -> T {
match self {
Self::Ok(val) => {
*val
}
Self::CheckThis(val) => {
println!("Checking stuff for {}", val);
*val
}
}
}
}
fn do_stuff() -> ResultExt<u64> {
Ok(StuffToCheck::CheckThis(10))
}
type ResultExt<T> = Result<StuffToCheck<T>, std::io::Error>;
fn main() {
let var = match do_stuff() {
Ok(result) => result.check_things(),
Err(_err) => 0,
};
dbg!(var);
}
Playground
You could even use nested pattern matching:
...
match do_stuff() {
Err(e) => {//handle error}
Ok(StuffToCheck::Ok(value)) => { value },
Ok(StuffToCheck::CheckThis(value)) => {
check_things(value);
value
}
}
...
I think this is an instance of the X-Y problem. You can use the built-in result, you just need a different error type, that returns an option: Some(partial_result) or None.
For example you have function parse, that can attempt to adjust for a malformed input, but report the error.
pub fn parse(b: &str) -> Result<&str, CustomParseError> {
// Do something that might fail,
if failed(){
return CustomParseError::new(None)
} else if partially_failed() {
return CustomParseError::new(Some(partial_result))
} else {
return completeResult
}
}
This way you have a clean code path where nothing failed, and all of your assumptions are correct, and if it's not => instead of unwrapping, you match and check which case you have. This is vastly superior, because the error often contains enough information for you to reconstruct both what went wrong, and what could be done to fix it.
Is there a way to shorten the if/else portion of the from_request?
use uuid::Uuid;
#[derive(Debug)]
struct User {
token: Option<Uuid>,
}
impl FromRequest for User {
type Error = Error;
type Future = Ready<Result<Self>>;
type Config = ();
fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
if let Some(t) = actix_web::HttpMessage::cookie(req,"token") {
if let Ok(u) = Uuid::parse_str(&t.value().to_owned()) {
futures::future::ok(User{token: Some(u)})
} else {
futures::future::ok(User{token: None})
}
} else {
futures::future::ok(User{token: None})
}
}
}
Any way to remove the futures::future::ok(User{token: None}) twice?
I tried to use map as shown in this:
Rust: Is there a way to shorten this if/else code using map?
but couldn't get it to work for the nested if/else.
Something like this?
fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
let token = actix_web::HttpMessage::cookie(req, "token")
.and_then(|t| Uuid::parse_str(&t.value().to_owned()).ok());
futures::future::ok(User { token })
}
Since you don't care about the error from parsing the Uuid, it calls .ok() which converts the Result into an Option. This lets you chain the optional values together with and_then.
I would do something like this using flask in Python:
#app.route('/login/', methods=['POST'])
def login():
token = request.headers["token"]
I cannot figure out how to access the token header and store it as a String variable.
#![feature(proc_macro_hygiene, decl_macro)]
use rocket::{
config::{Config, Environment},
*,
};
fn main() {
let config = Config::build(Environment::Production)
.address("0.0.0.0")
.port(PORT)
.finalize()
.unwrap();
rocket::ignite().mount("/", routes![login]).launch();
}
#[post("/login")]
fn login() {
// Retrieve headers from request.
}
Ibraheem Ahmed's answer was useful, but I could not figure out how to use Infallible. I solved the issue by doing:
struct Token(String);
#[derive(Debug)]
enum ApiTokenError {
Missing,
Invalid,
}
impl<'a, 'r> FromRequest<'a, 'r> for Token {
type Error = ApiTokenError;
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
let token = request.headers().get_one("token");
match token {
Some(token) => {
// check validity
Outcome::Success(Token(token.to_string()))
}
None => Outcome::Failure((Status::Unauthorized, ApiTokenError::Missing)),
}
}
}
Rocket handlers are based on Request Guards. You do not directly access the request in your handler. Instead, you create a type that implements FromRequest.
You can create a token struct that holds a string:
struct Token(String);
And implement FromRequest for the token:
impl<'a, 'r> FromRequest<'a, 'r> for Token {
type Error = Infallible;
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
let token = request.headers().get_one("token");
match token {
Some(token) => {
// check validity
Outcome::Success(Token(token.to_string()))
},
// token does not exist
None => Outcome::Failure(Status::Unauthorized)
}
}
}
Now you can use that Token as a request guard:
#[post("/login")]
fn login(token: Token) {
}
If the from_request method for Token fails, a Status::Unauthorized will be returned. Otherwise, your handler will be called, and you can handle the authentication logic.
Using warp.rs 0.2.2, let's consider a basic web service with one route for GET /:
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let getRoot = warp::get().and(warp::path::end()).and_then(routes::getRoot);
warp::serve(getRoot).run(([0, 0, 0, 0], 3030)).await;
Ok(())
}
My goal is to use ? for error handling in the route handlers, so let's write one that can error and return early in crate::routes:
use crate::errors::ServiceError;
use url::Url;
pub async fn getRoot() -> Result<impl warp::Reply, warp::Rejection> {
let _parsed_url = Url::parse(&"https://whydoesn.it/work?").map_err(ServiceError::from)?;
Ok("Hello world !")
}
This version works.
Here the error that's returned by Url::parse() is a url::ParseError
To convert between error types, from url::ParseError to ServiceError, then from ServiceError to warp::Rejection, I've written some error helpers in crate::errors:
#[derive(thiserror::Error, Debug)]
pub enum ServiceError {
#[error(transparent)]
Other(#[from] anyhow::Error), // source and Display delegate to anyhow::Error
}
impl warp::reject::Reject for ServiceError {}
impl From<ServiceError> for warp::reject::Rejection {
fn from(e: ServiceError) -> Self {
warp::reject::custom(e)
}
}
impl From<url::ParseError> for ServiceError {
fn from(e: url::ParseError) -> Self {
ServiceError::Other(e.into())
}
}
Now, the above works, and I'm trying to shorten the second code block to use ? for error handling directly, and convert automatically from the underlying error (here url::ParseError) to a warp::Rejection.
Here's what I've tried:
use crate::errors::ServiceError;
use url::Url;
pub async fn getRoot() -> Result<impl warp::Reply, ServiceError> {
let _parsed_url = Url::parse(&"https://whydoesn.it/work?")?;
Ok("Hello world !")
}
The url::ParseError returned by Url::Parse will convert fine into a ServiceError to return, but returning a ServiceError from my handler doesn't work.
The first compilation error I get is:
error[E0277]: the trait bound `errors::ServiceError: warp::reject::sealed::CombineRejection<warp::reject::Rejection>` is not satisfied
--> src/main.rs:102:54
|
102 | let getRoot = warp::get().and(warp::path::end()).and_then(routes::getRoot);
| ^^^^^^^^ the trait `warp::reject::sealed::CombineRejection<warp::reject::Rejection>` is not implemented for `errors::ServiceError`
Is there a way I can keep the short error handling using ? only and either:
make ServiceError implement warp::reject::sealed::CombineRejection<warp::reject::Rejection> ?
work around that ?
You can implement From to convert your error type into warp::Rejection using reject::custom. Rejection encapsulates custom types which you can later choose to inspect inside of a recover handler.
This example uses a plain error struct, but if you have an error enum you can match on the variants inside the recovery handler and perform different logic as needed.
use serde::Deserialize;
use snafu::{ensure, Snafu};
use std::convert::Infallible;
use warp::{
filters::{any, query, BoxedFilter},
http::StatusCode,
reject::Reject,
Filter, Rejection, Reply,
};
// A normal error type, created by SNAFU
#[derive(Debug, Snafu)]
#[snafu(display("Expected a value less than 10, but it was {}", value))]
struct LessThanTenError {
value: i32,
}
// A function that might fail
fn validate(value: i32) -> Result<i32, LessThanTenError> {
ensure!(value < 10, LessThanTenContext { value });
Ok(value)
}
// We need a custom type to later extract from the `Rejection`. In
// this case, we can reuse the error type itself.
impl Reject for LessThanTenError {}
// To allow using `?`, we implement a conversion from our error to
// `Rejection`
impl From<LessThanTenError> for Rejection {
fn from(other: LessThanTenError) -> Self {
warp::reject::custom(other)
}
}
#[tokio::main]
async fn main() {
let api = simple_math().recover(report_invalid);
let p: std::net::SocketAddr = "0.0.0.0:8888".parse().unwrap();
warp::serve(api).run(p).await;
}
#[derive(Debug, Deserialize)]
struct QueryParams {
a: i32,
b: i32,
}
fn simple_math() -> BoxedFilter<(impl Reply,)> {
any::any()
.and(query::query())
.and_then(|args: QueryParams| async move {
// Look at us using those question marks!
let a = validate(args.a)?;
let b = validate(args.b)?;
let sum = validate(a + b)?;
// We specify that we are returning an error type of
// `Rejection`, which allows the compiler to know what
// type to convert to when using `?` here.
Ok::<_, Rejection>(format!("The sum is {}", sum))
})
.boxed()
}
async fn report_invalid(r: Rejection) -> Result<impl Reply, Infallible> {
if let Some(e) = r.find::<LessThanTenError>() {
// It was our specific error type, do whatever we want. We
// will just print out the error text.
Ok(warp::reply::with_status(
e.to_string(),
StatusCode::BAD_REQUEST,
))
} else {
// Do prettier error reporting for the default error here.
Ok(warp::reply::with_status(
String::from("Something bad happened"),
StatusCode::INTERNAL_SERVER_ERROR,
))
}
}
[dependencies]
serde = { version = "1.0.118", features = ["derive"] }
snafu = "0.6.10"
tokio = { version = "0.2.23", features = ["full"] }
warp = "0.2.5"
% curl 'http://127.0.0.1:8888'
< HTTP/1.1 500 Internal Server Error
Something bad happened
% curl -v 'http://127.0.0.1:8888?a=1&b=2'
< HTTP/1.1 200 OK
The sum is 3
% curl -v 'http://127.0.0.1:8888?a=6&b=5'
< HTTP/1.1 400 Bad Request
Expected a value less than 10, but it was 11
See also:
Is there a way to do validation as part of a filter in Warp?
When should I implement std::convert::From vs std::convert::Into?
How do you define custom `Error` types in Rust?
From my findings, there are two solutions.
Abandon ? in favor of your own macro that constructs and returns a response if there is an error.
Use PR #458 by cjbassi instead of the mainline release by:
Implementing warp::reply::Reply on your error type so that it converts into the correct user facing error message.
Replace warp = "0.2" with warp = { git = "https://github.com/cjbassi/warp.git", branch = "error"} in your Cargo.toml file
use .map_async instead of .and_then for handlers
The following is a token creation tool I have created using jsonwebtoken.
I want to somehow enforce the expected token type such that if I pass a token string in and tell it the claim set I expect, it wont return a successful result.
Below includes test cases with comments about where I think this service should fail, and a comment in the code where I think the assertion should take place.
How can I enforce these claim types to be sure I get the token type I want?
use jwt;
use jwt::{ Header, Validation };
use std::convert::AsRef;
use serde::de::DeserializeOwned;
use serde::Serialize;
#[derive(Debug, Serialize, Deserialize, PartialEq)]
enum TokenType {
User,
Reg,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct RegisterClaims {
typ:TokenType,
org_name:String,
name:String,
email:String,
exp: usize,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct UserClaims {
typ:TokenType,
usr_id:String,
sub:String,
exp: usize,
}
#[derive(Debug)]
pub struct InvalidToken {
cause: String,
}
pub struct TokenFactory {
secret:String,
}
impl TokenFactory {
pub fn new(secret:String) -> TokenFactory {
TokenFactory {
secret
}
}
pub fn validate<T: DeserializeOwned>(&self, raw_token:String) -> Result<T, InvalidToken> {
match jwt::decode::<T>(&raw_token, self.secret.as_ref(), &Validation::default()) {
Ok(tokendata) => {
/*
some how assert the type of T to match and return an Err if not matched
What
*/
Ok(tokendata.claims)
},
Err(err) => {
// todo: in the future check error kind and give better errors
Err(InvalidToken{
cause: err.to_string()
})
}
}
}
pub fn mint_token<T: Serialize>(&self, claims:&T) -> String {
jwt::encode(&Header::default(), claims, self.secret.as_ref()).unwrap()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::util;
use std::borrow::Borrow;
#[test]
fn test_valid() {
let usr = UserClaims {
typ: TokenType::User,
sub:"foobar#gmail.com".to_string(),
exp:util::current_time_secs()+1,
usr_id:"usr-1234".to_string(),
};
let tf = TokenFactory::new("my_sceret".to_string());
let token = tf.mint_token(usr.borrow());
let usr_claims: UserClaims = tf.validate(token).unwrap();
assert_eq!(usr.sub, usr_claims.sub);
}
#[test]
fn test_mixed() {
let reg = RegisterClaims {
typ:TokenType::Reg,
org_name:"foo".to_string(),
name:"bar".to_string(),
email:"foobar#inc".to_string(),
exp:util::current_time_secs()+1,
};
let tf = TokenFactory::new("my_sceret".to_string());
let token = tf.mint_token(reg.borrow());
let usr_claims: UserClaims = tf.validate(token).unwrap(); // want it to fail here
assert_eq!(reg, usr_claims); // fails here
}
}
The solution I ended up using was to implement the following train for each claim struct I have.
trait ClaimType {
fn is_type() -> TokenType;
fn has_type(&self) -> TokenType;
}
Then in my validate I did
if T::is_type() == tokendata.claims.has_type() {
return Ok(tokendata.claims);
}
Err(InvalidToken{
cause: "Wrong token type".to_string()
})
Im sure there is probably a way to use a macro to impl the trait for me, or have the library itself enforce some deserialization check. But the above gets it done