use std::{collections::HashMap, sync::Arc};
use tracing::{event, instrument, Level};
use crate::{
credential::{AccessTokenBuilder, Credential, CredentialBuilder},
error::Error::InternalServer,
response::Response,
Result,
};
#[derive(Debug, Clone)]
pub struct Client {
inner: Arc<ClientInner>,
}
impl Client {
pub fn new(app_id: &str, secret: &str) -> Self {
let client = reqwest::Client::new();
Self {
inner: Arc::new(ClientInner {
app_id: app_id.into(),
secret: secret.into(),
client,
}),
}
}
pub(crate) fn request(&self) -> &reqwest::Client {
&self.inner.client
}
const AUTHENTICATION: &'static str = "https://api.weixin.qq.com/sns/jscode2session";
#[instrument(skip(self, code))]
pub async fn login(&self, code: &str) -> Result<Credential> {
event!(Level::DEBUG, "code: {}", code);
let mut map: HashMap<&str, &str> = HashMap::new();
map.insert("appid", &self.inner.app_id);
map.insert("secret", &self.inner.secret);
map.insert("js_code", code);
map.insert("grant_type", "authorization_code");
let response = self
.inner
.client
.get(Self::AUTHENTICATION)
.query(&map)
.send()
.await?;
event!(Level::DEBUG, "authentication response: {:#?}", response);
if response.status().is_success() {
let response = response.json::<Response<CredentialBuilder>>().await?;
let credential = response.extract()?.build();
event!(Level::DEBUG, "credential: {:#?}", credential);
Ok(credential)
} else {
Err(InternalServer(response.text().await?))
}
}
const ACCESS_TOKEN: &'static str = "https://api.weixin.qq.com/cgi-bin/token";
#[instrument(skip(self))]
pub(crate) async fn get_access_token(&self) -> Result<AccessTokenBuilder> {
let mut map: HashMap<&str, &str> = HashMap::new();
map.insert("grant_type", "client_credential");
map.insert("appid", &self.inner.app_id);
map.insert("secret", &self.inner.secret);
let response = self
.inner
.client
.get(Self::ACCESS_TOKEN)
.query(&map)
.send()
.await?;
event!(Level::DEBUG, "response: {:#?}", response);
if response.status().is_success() {
let res = response.json::<Response<AccessTokenBuilder>>().await?;
let builder = res.extract()?;
event!(Level::DEBUG, "access token builder: {:#?}", builder);
Ok(builder)
} else {
Err(InternalServer(response.text().await?))
}
}
const STABLE_ACCESS_TOKEN: &str = "https://api.weixin.qq.com/cgi-bin/stable_token";
#[instrument(skip(self, force_refresh))]
pub(crate) async fn get_stable_access_token(
&self,
force_refresh: impl Into<Option<bool>>,
) -> Result<AccessTokenBuilder> {
let mut map: HashMap<&str, String> = HashMap::new();
map.insert("grant_type", "client_credential".into());
map.insert("appid", self.inner.app_id.clone());
map.insert("secret", self.inner.secret.clone());
if let Some(force_refresh) = force_refresh.into() {
event!(Level::DEBUG, "force_refresh: {}", force_refresh);
map.insert("force_refresh", force_refresh.to_string());
}
let response = self
.inner
.client
.post(Self::STABLE_ACCESS_TOKEN)
.json(&map)
.send()
.await?;
event!(Level::DEBUG, "response: {:#?}", response);
if response.status().is_success() {
let response = response.json::<Response<AccessTokenBuilder>>().await?;
let builder = response.extract()?;
event!(Level::DEBUG, "stable access token builder: {:#?}", builder);
Ok(builder)
} else {
Err(InternalServer(response.text().await?))
}
}
}
#[derive(Debug)]
struct ClientInner {
app_id: String,
secret: String,
client: reqwest::Client,
}