use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct User {
pub login: String,
}
pub struct GitHubClient {
http_client: reqwest::Client,
api_base_url: String,
}
impl GitHubClient {
pub fn new(token: &str) -> Self {
Self::build(token, "https://api.github.com".to_string())
}
pub fn with_base_url(token: &str, api_base_url: String) -> Self {
Self::build(token, api_base_url)
}
fn build(token: &str, api_base_url: String) -> Self {
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(
reqwest::header::AUTHORIZATION,
format!("Bearer {token}")
.parse()
.expect("valid authorization header value"),
);
headers.insert(
reqwest::header::ACCEPT,
"application/vnd.github+json"
.parse()
.expect("valid accept header value"),
);
let http_client = reqwest::Client::builder()
.default_headers(headers)
.user_agent("ghtkn-rust-sdk")
.build()
.expect("failed to build reqwest client");
Self {
http_client,
api_base_url,
}
}
pub async fn get_user(&self) -> crate::Result<User> {
let url = format!("{}/user", self.api_base_url);
self.http_client
.get(&url)
.send()
.await
.map_err(|e| crate::Error::GitHub(format!("request user: {e}")))?
.json::<User>()
.await
.map_err(|e| crate::Error::GitHub(format!("decode user response: {e}")))
}
}
#[cfg(test)]
mod tests {
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};
use super::*;
#[tokio::test]
async fn test_get_user() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/user"))
.respond_with(
ResponseTemplate::new(200).set_body_json(serde_json::json!({"login": "octocat"})),
)
.mount(&server)
.await;
let client = GitHubClient::with_base_url("test_token", server.uri());
let user = client.get_user().await.unwrap();
assert_eq!(user.login, "octocat");
}
#[tokio::test]
async fn test_get_user_error() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/user"))
.respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
.mount(&server)
.await;
let client = GitHubClient::with_base_url("test_token", server.uri());
let result = client.get_user().await;
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
matches!(err, crate::Error::GitHub(_)),
"expected Error::GitHub, got: {err:?}"
);
}
#[tokio::test]
async fn test_get_user_with_extra_fields() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/user"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"login": "testuser",
"id": 12345,
"avatar_url": "https://avatars.githubusercontent.com/u/12345",
"name": "Test User"
})))
.mount(&server)
.await;
let client = GitHubClient::with_base_url("test_token", server.uri());
let user = client.get_user().await.unwrap();
assert_eq!(user.login, "testuser");
}
}