rust 我的Axum处理程序无法编译:未实现IntoResponse特征

kninwzqo  于 2023-04-12  发布在  其他
关注(0)|答案(1)|浏览(165)

Rust完全是新的。我试图实现oauth身份验证,我使用axum,没有成功。.以下是我丑陋的代码:

use axum::{
  Json,
  extract::Query,
  extract::Extension,
  http::StatusCode,
  response::IntoResponse
};
use serde_json::{json, Value};
use hyper;
use hyper_tls::HttpsConnector;
use hyper::header;
use cookie::Cookie;
use serde::{ Deserialize, Serialize };

#[derive(Clone)]
pub struct GitHubOAuth2 {
  client_id: String,
  redirect_uri: String,
  client_secret: String
}

#[derive(Serialize, Deserialize)]
pub struct CallbackAuthCode {
  code: String
}

impl GitHubOAuth2 {

  pub fn new(conf: String) -> GitHubOAuth2 {
    let json_content : Value = serde_json::from_str(&conf).expect("Invalid configuration.");
    GitHubOAuth2 {
      client_id: json_content["github_oauth2"]["client_id"].as_str().unwrap().to_string(),
      redirect_uri: json_content["github_oauth2"]["redirect_uri"].as_str().unwrap().to_string(),
      client_secret: json_content["github_oauth2"]["client_secret"].as_str().unwrap().to_string()
    }
  }
}

pub async fn callback(Query(params): Query<CallbackAuthCode>, Extension(conf): Extension<GitHubOAuth2>) -> impl IntoResponse {
  let params_code = &params.code;

  let mut get_token_url: String = String::from("https://www.github.com/login/oauth/access_token?client_id=");
  get_token_url.push_str(&conf.client_id);
  get_token_url.push_str("&redirect_uri=");
  get_token_url.push_str(&conf.redirect_uri);
  get_token_url.push_str("&client_secret=");
  get_token_url.push_str(&conf.client_secret);
  get_token_url.push_str("&code=");
  get_token_url.push_str(&params_code);
  println!("get_token_url: {}", get_token_url);

  let https = HttpsConnector::new();
  let client = hyper::Client::builder().build::<_, hyper::Body>(https);

  let req = hyper::Request::builder()
    .method(hyper::Method::POST)
    .uri(get_token_url)
    .header("Accept", "application/json")
    .body(hyper::Body::empty()).unwrap();

  match client.request(req).await {
    Ok(resp) => {
      println!("response: {}", resp.status());

      let redirectUri : String = resp.headers().get("Location").unwrap().to_str().unwrap().to_string();

      if resp.status() == 301 {
        let redirectReq = hyper::Request::builder()
          .method(hyper::Method::POST)
          .uri(redirectUri)
          .header("Accept", "application/json")
          .body(hyper::Body::empty()).unwrap();
          match client.request(redirectReq).await {
            Ok(mut redirectResp) => {
              let body = hyper::body::to_bytes(redirectResp.body_mut()).await.unwrap();
              println!("{} {:?}", redirectResp.status(), body);
              let body_as_json : Value = serde_json::from_slice(&body).unwrap();
              let bearer_token = body_as_json["access_token"].as_str().unwrap().to_string();
              let cookie = Cookie::build("hey", bearer_token).secure(true).http_only(true).finish();
              return (
                StatusCode::OK,
                [(header::SET_COOKIE, &cookie.value())],
                Json(json!({
                  "msg": "got that cookie"
                }))
              );
            },
            Err(mut redirect_e) => {
              return (
                StatusCode::INTERNAL_SERVER_ERROR,
                [(header::CONTENT_TYPE, &"application/json")],
                Json(json!({
                  "error": redirect_e.to_string()
                }))
              );
            }
          }
      } else {
        return (
          StatusCode::NOT_IMPLEMENTED,
          [(header::CONTENT_TYPE, &"application/json")],
          Json(json!({
            "error": String::from("service replies with unexpected response.")
          }))
        );
      }
    },
    Err(e) => {
      return (
        StatusCode::INTERNAL_SERVER_ERROR,
        [(header::CONTENT_TYPE, &"application/json")],
        Json(json!({
          "error": e.to_string()
        }))
      );
    }
  }
}

callback函数旨在实现oauth2中的'callback'阶段,因此它选择了auth代码并使用它来调用github IdP以收集授权令牌。没什么大不了的。问题是这段代码无法编译,我不明白为什么。编译器说:

error[E0277]: the trait bound `(StatusCode, [(HeaderName, &&str); 1], Json<serde_json::Value>): IntoResponse` is not satisfied

callback函数作为get处理程序附加到服务器。
我从axum的基本示例开始,尝试一步一步地构建我的怪物,但现在我被卡住了。我错过了什么?
我的Cargo.toml

[package]
name = "keymaster"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
axum = "0.5.16"
axum-macros = "0.2.3"
hyper = { version = "0.14.20", features = ["full"] }
tokio = { version = "1.21.2", features = ["full"] }
tower = "0.4.13"
serde_json = "1.0.85"
serde = "1.0.145"
jwt-simple = "0.10"
clap = { version = "4.0.19", features = ["derive"] }
hyper-tls = "0.5.0"
cookie = "0.17.0"
follow-redirects = "0.1.3"
http = "0.2.9"
[dependencies.uuid]
version = "1.2.1"
features = [
    "v4",                # Lets you generate random UUIDs
    "fast-rng",          # Use a faster (but still sufficiently random) RNG
    "macro-diagnostics", # Enable better diagnostics for compile-time UUIDs
]

20230409更新

从我的&"application/json"中删除&会导致编译器报告更多错误。完整的日志在这里:

--> src/server/handlers/github_oauth2.rs:14:5
  |
14 | use http::header::HeaderValue;
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

error[E0308]: mismatched types
 --> src/server/handlers/github_oauth2.rs:92:41
  |
92 |                 [(header::CONTENT_TYPE, "application/json")],
  |                                         ^^^^^^^^^^^^^^^^^^ expected `&str`, found `str`
  |
  = note: expected reference `&&str`
             found reference `&'static str`
note: return type inferred to be `&&str` here
 --> src/server/handlers/github_oauth2.rs:81:22
  |
81 |                 return (
  |  ______________________^
82 | |                 StatusCode::OK,
83 | |                 [(header::SET_COOKIE, &cookie.value())],
84 | |                 Json(json!({
85 | |                   "msg": "got that cookie"
86 | |                 }))
87 | |               );
  | |_______________^

error[E0277]: the trait bound `(StatusCode, [(HeaderName, &&str); 1], Json<serde_json::Value>): IntoResponse` is not satisfied
  --> src/server/handlers/github_oauth2.rs:40:126
   |
40  |   pub async fn callback(Query(params): Query<CallbackAuthCode>, Extension(conf): Extension<GitHubOAuth2>) -> impl IntoResponse {
   |  ______________________________________________________________________________________________________________________________^
41  | |   let params_code = &params.code;
42  | |
43  | |   let mut get_token_url: String = String::from("https://www.github.com/login/oauth/access_token?client_id=");
...   |
118 | |   }
119 | | }
   | |_^ the trait `IntoResponse` is not implemented for `(StatusCode, [(HeaderName, &&str); 1], Json<serde_json::Value>)`
   |
   = help: the following other types implement trait `IntoResponse`:
             ()
             (Response<()>, R)
             (Response<()>, T1, R)
             (Response<()>, T1, T2, R)
             (Response<()>, T1, T2, T3, R)
             (Response<()>, T1, T2, T3, T4, R)
             (Response<()>, T1, T2, T3, T4, T5, R)
             (Response<()>, T1, T2, T3, T4, T5, T6, R)
           and 60 others

error[E0308]: mismatched types
  --> src/server/handlers/github_oauth2.rs:102:35
   |
102 |           [(header::CONTENT_TYPE, "application/json")],
   |                                   ^^^^^^^^^^^^^^^^^^ expected `&str`, found `str`
   |
   = note: expected reference `&&str`
              found reference `&'static str`
note: return type inferred to be `&&str` here
  --> src/server/handlers/github_oauth2.rs:81:22
   |
81  |                 return (
   |  ______________________^
82  | |                 StatusCode::OK,
83  | |                 [(header::SET_COOKIE, &cookie.value())],
84  | |                 Json(json!({
85  | |                   "msg": "got that cookie"
86  | |                 }))
87  | |               );
   | |_______________^

error[E0308]: mismatched types
  --> src/server/handlers/github_oauth2.rs:112:33
   |
112 |         [(header::CONTENT_TYPE, "application/json")],
   |                                 ^^^^^^^^^^^^^^^^^^ expected `&str`, found `str`
   |
   = note: expected reference `&&str`
              found reference `&'static str`
note: return type inferred to be `&&str` here
  --> src/server/handlers/github_oauth2.rs:81:22
   |
81  |                 return (
   |  ______________________^
82  | |                 StatusCode::OK,
83  | |                 [(header::SET_COOKIE, &cookie.value())],
84  | |                 Json(json!({
85  | |                   "msg": "got that cookie"
86  | |                 }))
87  | |               );
   | |_______________^

error[E0277]: the trait bound `(StatusCode, [(HeaderName, &&str); 1], Json<serde_json::Value>): IntoResponse` is not satisfied
 --> src/server/handlers/github_oauth2.rs:40:108
  |
40 | pub async fn callback(Query(params): Query<CallbackAuthCode>, Extension(conf): Extension<GitHubOAuth2>) -> impl IntoResponse {
  |                                                                                                            ^^^^^^^^^^^^^^^^^ the trait `IntoResponse` is not implemented for `(StatusCode, [(HeaderName, &&str); 1], Json<serde_json::Value>)`
  |
  = help: the following other types implement trait `IntoResponse`:
            ()
            (Response<()>, R)
            (Response<()>, T1, R)
            (Response<()>, T1, T2, R)
            (Response<()>, T1, T2, T3, R)
            (Response<()>, T1, T2, T3, T4, R)
            (Response<()>, T1, T2, T3, T4, T5, R)
            (Response<()>, T1, T2, T3, T4, T5, T6, R)
          and 60 others

Some errors have detailed explanations: E0277, E0308.
For more information about an error, try `rustc --explain E0277`.
warning: `keymaster` (bin "keymaster") generated 1 warning
error: could not compile `keymaster` due to 5 previous errors; 1 warning emitted
e3bfsja2

e3bfsja21#

错误看起来很清楚?它在告诉你

(StatusCode, [(HeaderName, &&str); 1], Json<serde_json::Value>)

不支持IntoResponse
由于这是一个3元组,第一个成员是StatusCode,因此最接近的impl为

impl<R, T1> IntoResponse for (StatusCode, T1, R)
where
    T1: IntoResponseParts,
    R: IntoResponse,

有一个

impl<T> IntoResponse for Json<T> where T: Serialize

serde_value::Value实现了Serialize,剩下中间的项:

[(HeaderName, &&str); 1]

这是一个(k,v)元组的数组

impl<K, V, const N: usize> IntoResponse for [(K, V); N]
where
    K: TryInto<HeaderName>,
    <K as TryInto<HeaderName>>::Error: Display,
    V: TryInto<HeaderValue>,
    <V as TryInto<HeaderValue>>::Error: Display

HeaderName可以简单地转换为HeaderName,但是&&str as TryInto<HeaderValue>呢?看看impl TryFrom<> for HeaderValue(它是镜像,通常是实现的trait),我们可以看到:

impl<'a> TryFrom<&'a [u8]> for HeaderValue
impl<'a> TryFrom<&'a String> for HeaderValue
impl<'a> TryFrom<&'a str> for HeaderValue
impl TryFrom<String> for HeaderValue
impl TryFrom<Vec<u8, Global>> for HeaderValue

实际上,没有&&str的实现,所以&&str不能转换为HeaderValue
只要删除完全不必要的字符串字面量引用,字符串字面量已经是&'static str

相关问题