63281


actix-webを利用してWebアプリを作成しています。  
actix-
webのハンドラ内で`reqwest`を使い、データベースに保存されているレコードのURLにアクセスし、結果が得られなかった場合そのレコードを削除するという処理を実装したいと考え以下のコードを実装しましたがエラーが出てしまいました。

コード

[code]

    async fn valid_site(url: &str) -> Result<models::NewBackend, reqwest::Error>{
        let backend = reqwest::get(&format!("{}/.well-known/site.json", url))
            .await?
            .json()
            .await?;
        Ok(backend)
    }
    
    #[get("/backends/{id}")]
    async fn backends_update(hb: web::Data<Handlebars>, pool: web::Data<DbPool>, id: web::Path<String>) -> Result<HttpResponse, Error>{
        let conn = pool.get().expect("cant get db pool");
        let id_int: i32 = id.parse().unwrap();
        let backend = web::block(move || actions::find_backend(&id_int, &conn))
            .await
            .map_err(|e| {
                eprintln!("{}", e);
                let body = hb.render("error", &json!({
                    "reason": "Bad request"
                })).unwrap();
                HttpResponse::BadRequest().body(body)
            }
        )?;
    
        if let Some(backend) = backend{
            let site_info = valid_site(&backend.backend_url)
                .await
                .map_err(|e| async {
                    eprintln!("{}", e);
                    // リクエストに失敗した場合はレコードを削除する。
                    web::block(move || actions::delete_backend(&id_int, &conn))
                        .await
                        .map_err(|e| {
                            eprintln!("{}", e);
                            let body = hb.render("error", &json!({
                                "reason": "Unexpected error occurred"
                            })).unwrap();
                            HttpResponse::InternalServerError().body(body)
                        })?;
                    HttpResponse::SeeOther().header(http::header::LOCATION, "/").finish()
                }
            )?;
    
            let conn = pool.get().expect("cant get db pool");
            web::block(move || actions::update_backend(&backend.id, &site_info.name, &site_info.frontend_url, &site_info.backend_url, &site_info.description, &conn))
                .await
                .map_err(|e| {
                    eprintln!("{}", e);
                    let body = hb.render("error", &json!({
                        "reason": "Unexpected error occurred"
                    })).unwrap();
                    HttpResponse::InternalServerError().body(body)
                }
            )?;
    
    
            Ok(HttpResponse::SeeOther().header(http::header::LOCATION, "/").finish())
        }else{
            let body = hb.render("error", &json!({
                "reason": "not found backend"
            })).unwrap();
            Ok(HttpResponse::NotFound().body(body))
        }
    }
    
[/code]

エラー

[code]

    error[E0277]: the `?` operator can only be used in an async block that returns `Result` or `Option` (or another type that implements `std::ops::Try`)
       --> src/main.rs:108:17
        |
    105 |                .map_err(|e| async {
        |   ________________________________-
    106 |  |                 eprintln!("{}", e);
    107 |  |                 // リクエストに失敗した場合はレコードを削除する。
    108 |  |                 web::block(move || actions::delete_backend(&id_int, &conn))
        |  |_________________^
    109 | ||                     .await
    110 | ||                     .map_err(|e| {
    111 | ||                         eprintln!("{}", e);
    ...   ||
    115 | ||                         HttpResponse::InternalServerError().body(body)
    116 | ||                     })?;
        | ||_______________________^ cannot use the `?` operator in an async block that returns `actix_http::response::Response`
    117 |  |                 HttpResponse::SeeOther().header(http::header::LOCATION, "/").finish()
    118 |  |             }
        |  |_____________- this function should return `Result` or `Option` to accept `?`
        |
        = help: the trait `std::ops::Try` is not implemented for `actix_http::response::Response`
        = note: required by `std::ops::Try::from_error`
    
    error[E0277]: the trait bound `impl std::future::Future: actix_http::error::ResponseError` is not satisfied
       --> src/main.rs:119:10
        |
    119 |         )?;
        |          ^ the trait `actix_http::error::ResponseError` is not implemented for `impl std::future::Future`
        |
        = note: required because of the requirements on the impl of `std::convert::From<impl std::future::Future>` for `actix_http::error::Error`
        = note: required by `std::convert::From::from`
    
    error: aborting due to 2 previous errors
    
[/code]

実行環境

  * rustc 1.41.0 (5e1a79984 2020-01-27)
  * actix-web = "2.0.0"
  * actix-rt = "1.0.0"
  * diesel = {version = "1.4.3", features = ["sqlite", "r2d2"]}
  * dotenv = "0.15.0"
  * r2d2 = "0.8.8"
  * serde = { version = "1.0", features = ["derive"] }
  * serde_json = "1.0"
  * env_logger = "0.7.1"
  * reqwest = { version = "0.10", features = ["json"] }
  * tokio = { version = "0.2", features = ["full"] }
  * handlebars = "2.0.0"

このようなことをする場合どういったAPIを利用すればよいのでしょうか。


CC BY-SA 4.0
2020-02-22T07:52:22.120
63266
2020-02-26T01:28:51.257
7232
question
rust
reqwestで要素が取得できなかった場合にデータベースのレコードを削除したい。


エラーになる理由ですが、`map_err()`メソッドに渡しているクロージャの戻り値型と、そのクロージャ内にある`?`後置演算子が要求する戻り値型が一致しないためです。

  * 該当のコードでは、`Result`型の`map_err()`メソッドは、クロージャの戻り値型が`Error`であることを要求する
  * `?`後置演算子はクロージャの戻り値型が`Result`型か`Option`型であることを常に要求する

`?`演算子はクロージャ内ではうまく使えないことが多いです。今回のようなケースでは`?`演算子もクロージャも使わずに、`match`/`if
let`と`return`を組み合わせるのがお勧めです。

`let site_info = ...` のところを、以下のように書き換えてみてください。これでコンパイルできるはずです。

[code]

    let site_info = match valid_site(&backend.backend_url).await {
        Ok(info) => info, // Ok(..) なら値をアンラップする
        Err(e) => {
            eprintln!("{}", e);
            // リクエストに失敗した場合はレコードを削除する。
            let response = web::block(move || actions::delete_backend(&id_int, &conn))
                .await
                // delete_backend から OK(_) が返されたなら、その中身を HttpResponse で置き換える
                .map(|_| HttpResponse::SeeOther().header(http::header::LOCATION, "/").finish())
                // delete_backend から Err(e) が返されたなら、それを丸ごと Ok(HttpResponse) で置き換える
                .or_else(|e| {
                    eprintln!("{}", e);
                    let body = hb.render("error", &json!({
                        "reason": "Unexpected error occurred"
                    })).unwrap();
                    Ok(HttpResponse::InternalServerError().body(body))
                });
            // 早期リターンする
            return response;  // Result<HttpResponse, Error>型
        }
    };
    
[/code]

**追記**

>
> `actions::delete_backend`については`map`と`or_else`で書かれていていますが、これも`match`を使って書くことができますか。

`match`式でも書けます。その場合、`let site_info = ...`のところは以下のようになります。

[code]

    let site_info = match valid_site(&backend.backend_url).await {
        Ok(info) => info, // Ok(..) なら値をアンラップする
        Err(e) => {
            eprintln!("{}", e);
            // リクエストに失敗した場合はレコードを削除する。
            let response = match web::block(move || actions::delete_backend(&id_int, &conn)).await {
                // delete_backend から OK(..) が返されたなら、Ok(HttpResponse) を返す
                Ok(_) => Ok(HttpResponse::SeeOther().header(http::header::LOCATION, "/").finish()),
                // delete_backend から Err(..) が返されたなら、Ok(HttpResponse) を返す
                Err(e) => {
                    eprintln!("{}", e);
                    let body = hb.render("error", &json!({
                        "reason": "Unexpected error occurred"
                    })).unwrap();
                    Ok(HttpResponse::InternalServerError().body(body))
                }
            };
            return response;  // Result<HttpResponse, Error>型
        }
    };
    
[/code]

実は`map()`や`or_else()`などのメソッドは`match`式で実装されています。APIドキュメントでは各関数・メソッドの右側に`[src]`というリンクがあって、そこから実装を見ることができます。

`Result::map()`の実装  
<https://doc.rust-lang.org/std/result/enum.Result.html#method.map>

[code]

    #[inline]
    #[stable(feature = "rust1", since = "1.0.0")]
    pub fn map<U, F: FnOnce(T) -> U>(self, op: F) -> Result<U,E> {
        match self {
            Ok(t) => Ok(op(t)),
            Err(e) => Err(e)
        }
    }
    
[/code]

`Result::or_else()`の実装  
<https://doc.rust-lang.org/std/result/enum.Result.html#method.or_else>

[code]

    #[inline]
    #[stable(feature = "rust1", since = "1.0.0")]
    pub fn or_else<F, O: FnOnce(E) -> Result<T, F>>(self, op: O) -> Result<T, F> {
        match self {
            Ok(t) => Ok(t),
            Err(e) => op(e),
        }
    }
    
[/code]

Rustに慣れないうちは、`let site_info =
...`の最後の例のように、`map()`などを使わずに`match`式で書いたほうがいいかもしれません。そのほうがコード量は増えますが、わかりやすくなると思います。


CC BY-SA 4.0
2020-02-22T12:23:50.447
63281
2020-02-26T01:28:51.257
2020-02-26T01:28:51.257
14101
14101
63266
answer