Сопоставьте результат домена с результатом http

#rust #actix-web #rust-actix

Вопрос:

Я пытаюсь создать HTTP-сервер с использованием веб-платформы Actix в Rust. Я привык отделять бизнес-модель и бизнес-ошибку от HttpResponse. Для этого у меня есть служба CredentialService, которая предоставляет метод, возвращающий результат Result<String, CredentialServiceError> .

Мой веб-сервер предоставляет API POST /login , который принимает username password и возвращает JWT.

Перечисление `CredentialServiceError » выглядит следующим образом

 use derive_more::{Display, Error};

#[derive(Debug, Display, Error)]
pub enum CredentialServiceError {
    NoCredentialFound,
    ErrorOnGeneratingJWT,
}
 

Мой куратор-это что-то вроде:

 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()
}
 

Я получаю эту ошибку:

 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<amp;miniz_oxide::StreamResult>>
        <std::result::Result<miniz_oxide::MZStatus, miniz_oxide::MZError> as std::convert::From<amp;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)
 

Я также попытался реализовать

 impl error::ResponseError for CredentialServiceError { ... }
impl Into<HttpResponse> for LoginResponse { ... }
 

Ошибка не меняется.

Итак, как я могу преобразовать Result<String, CredentialServiceError> в Result<HttpResponse> ?

Ответ №1:

Таким образом, проблема, с которой вы сталкиваетесь, заключается в том, что вы хотите преобразовать свой результат в результат, ожидаемый функциями actix_web. Надеюсь, я правильно это понял.

Я не вижу другого способа, кроме как сопоставить ваш результат с ожидаемым. Есть разные способы, которыми вы могли бы этого добиться. Если вы хотите сохранить детали реализации в своей LoginResponse структуре, вы можете использовать свойство Into.

 impl Into<HttpResponse> for LoginResponse {
    fn into(self) -> HttpResponse {
        HttpResponse::Ok().body(self.jwt)
    }
}
 

Если вам на самом деле все равно, вы можете использовать функции карты, предоставляемые компанией 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")
        }
    }
}
 

Таким образом, в конце концов ваша функция входа в систему будет выглядеть примерно так.

 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)
}
 

Надеюсь, я мог бы помочь и что я понял ваш вопрос.

Ответ №2:

В дополнение к ответу @Julius-Kreutz вы можете рассмотреть impl ResponseError источник признаков из Чанды, Абхишек. Например, если ваше CredentialServiceError перечисление похоже:

 #[derive(Debug, Display)]
pub enum CredentialServiceError {
    #[display(fmt = "Internal Server Error")]
    InternalServerError,

    #[display(fmt = "BadRequest: {}", _0)]
    BadRequest(String),

    #[display(fmt = "JWKSFetchError")]
    JWKSFetchError,
}
 

Тогда юо может сделать что-то вроде:

 impl ResponseError for CredentialServiceError {
    fn error_response(amp;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")
            }
        }
    }
}
 

Чтобы включить .into()

Комментарии:

1. impl error::ResponseError for CredentialServiceError { ... } разве это не одно и то же?

2. @allevo Это то же самое, однако я не был уверен, как он допустил ошибку, поэтому я привел ему пример шаблона, который в сочетании с ответом Джулиуса-Крейца (выше) даст ему результат 😛