1use std::{collections::HashMap, sync::Arc};
2
3use tracing::{event, instrument, Level};
4
5use crate::{
6 credential::{AccessTokenBuilder, Credential, CredentialBuilder},
7 error::Error::InternalServer,
8 response::Response,
9 Result,
10};
11
12#[derive(Debug, Clone)]
14pub struct Client {
15 inner: Arc<ClientInner>,
16}
17
18impl Client {
19 pub fn new(app_id: &str, secret: &str) -> Self {
33 let client = reqwest::Client::new();
34
35 Self {
36 inner: Arc::new(ClientInner {
37 app_id: app_id.into(),
38 secret: secret.into(),
39 client,
40 }),
41 }
42 }
43
44 pub(crate) fn request(&self) -> &reqwest::Client {
45 &self.inner.client
46 }
47
48 const AUTHENTICATION: &'static str = "https://api.weixin.qq.com/sns/jscode2session";
49
50 #[instrument(skip(self, code))]
73 pub async fn login(&self, code: &str) -> Result<Credential> {
74 event!(Level::DEBUG, "code: {}", code);
75
76 let mut map: HashMap<&str, &str> = HashMap::new();
77
78 map.insert("appid", &self.inner.app_id);
79 map.insert("secret", &self.inner.secret);
80 map.insert("js_code", code);
81 map.insert("grant_type", "authorization_code");
82
83 let response = self
84 .inner
85 .client
86 .get(Self::AUTHENTICATION)
87 .query(&map)
88 .send()
89 .await?;
90
91 event!(Level::DEBUG, "authentication response: {:#?}", response);
92
93 if response.status().is_success() {
94 let response = response.json::<Response<CredentialBuilder>>().await?;
95
96 let credential = response.extract()?.build();
97
98 event!(Level::DEBUG, "credential: {:#?}", credential);
99
100 Ok(credential)
101 } else {
102 Err(InternalServer(response.text().await?))
103 }
104 }
105
106 const ACCESS_TOKEN: &'static str = "https://api.weixin.qq.com/cgi-bin/token";
107
108 #[instrument(skip(self))]
111 pub(crate) async fn get_access_token(&self) -> Result<AccessTokenBuilder> {
112 let mut map: HashMap<&str, &str> = HashMap::new();
113
114 map.insert("grant_type", "client_credential");
115 map.insert("appid", &self.inner.app_id);
116 map.insert("secret", &self.inner.secret);
117
118 let response = self
119 .inner
120 .client
121 .get(Self::ACCESS_TOKEN)
122 .query(&map)
123 .send()
124 .await?;
125
126 event!(Level::DEBUG, "response: {:#?}", response);
127
128 if response.status().is_success() {
129 let res = response.json::<Response<AccessTokenBuilder>>().await?;
130
131 let builder = res.extract()?;
132
133 event!(Level::DEBUG, "access token builder: {:#?}", builder);
134
135 Ok(builder)
136 } else {
137 Err(InternalServer(response.text().await?))
138 }
139 }
140
141 const STABLE_ACCESS_TOKEN: &str = "https://api.weixin.qq.com/cgi-bin/stable_token";
142
143 #[instrument(skip(self, force_refresh))]
146 pub(crate) async fn get_stable_access_token(
147 &self,
148 force_refresh: impl Into<Option<bool>>,
149 ) -> Result<AccessTokenBuilder> {
150 let mut map: HashMap<&str, String> = HashMap::new();
151
152 map.insert("grant_type", "client_credential".into());
153 map.insert("appid", self.inner.app_id.clone());
154 map.insert("secret", self.inner.secret.clone());
155
156 if let Some(force_refresh) = force_refresh.into() {
157 event!(Level::DEBUG, "force_refresh: {}", force_refresh);
158
159 map.insert("force_refresh", force_refresh.to_string());
160 }
161
162 let response = self
163 .inner
164 .client
165 .post(Self::STABLE_ACCESS_TOKEN)
166 .json(&map)
167 .send()
168 .await?;
169
170 event!(Level::DEBUG, "response: {:#?}", response);
171
172 if response.status().is_success() {
173 let response = response.json::<Response<AccessTokenBuilder>>().await?;
174
175 let builder = response.extract()?;
176
177 event!(Level::DEBUG, "stable access token builder: {:#?}", builder);
178
179 Ok(builder)
180 } else {
181 Err(InternalServer(response.text().await?))
182 }
183 }
184}
185
186#[derive(Debug)]
187struct ClientInner {
188 app_id: String,
189 secret: String,
190 client: reqwest::Client,
191}