对铁 rust 来说是全新的.我正在try 实现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中实现回调阶段,因此它 Select 认证码并使用它来调用GitHub IdP来收集授权令牌.别小题大作.问题是这段代码不能编译,我不知道为什么.编译器说:

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

callback function is attached to server as a get handler.
I started from the axum basic examples and tried to build my monster one step at time but now I am stuck. What am I missing?

我的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"中删除&会导致编译器报告更多错误.完整的日志(log)在这里:

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

推荐答案

错误看起来很明显吗?它在告诉你

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

不实现IntoResponse.

由于这是一个三元数,而第一个成员是StatusCode,因此最接近的Iml将是

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 implements 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(这是镜像和通常实现的特征),我们可以看到:

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了.

Rust相关问答推荐

计算具有相邻调换且没有插入或删除的序列的距离

如何使用syn插入 comments ?

为什么std repeat trait绑定在impl块和关联函数之间?

如何在Bevy/Rapier3D中获得碰撞机的计算质量?

在0..1之间将U64转换为F64

写入引用会更新基础值,但引用会打印意外的值

考虑到Rust不允许多个可变引用,类似PyTorch的自动区分如何在Rust中工作?

是否可以使用Serde/Rust全局处理无效的JSON值?

从管道读取后重置标准输入

如何从宏调用闭包?

`UnsafeCell` 在没有锁定的情况下跨线程共享 - 这可能会导致 UB,对吗?

如何将 &[T] 或 Vec<T> 转换为 Arc<Mutex<[T]>>?

trait 对象指针的生命周期

Rust中的标记特征是什么?

如何使返回 XMLError 的方法与 anyhow::Error 兼容?

使用方法、关联函数和自由函数在 Rust 中初始化函数指针之间的区别

我如何将 google_gmail1::Gmail> 传递给线程生成?

在 Rust 中获得准确时间的正确方法?

有没有办法在 Rust 中对 BigInt 进行正确的位移?

如果我立即等待,为什么 `tokio::spawn` 需要一个 `'static` 生命周期?