1pub mod breakdown_trading_data;
5pub mod cash_dividend_data;
6pub mod daily_stock_prices;
7pub mod earnings_calendar;
8pub mod financial_statement_details;
9pub mod financial_statements;
10pub mod futures_prices;
11pub mod index_option_prices;
12pub mod indicies;
13pub mod listed_issue_info;
14pub mod morning_session_stock_prices;
15pub mod options_prices;
16pub mod shared;
17pub mod short_sale_by_sector;
18pub mod topic_prices;
19pub mod trading_by_type_of_investors;
20pub mod trading_calendar;
21pub mod weekly_margin_trading_outstandings;
22
23use shared::{
24 auth::{get_id_token_from_api, get_refresh_token_from_api},
25 responses::error_response::JQuantsErrorResponse,
26};
27use std::{fmt, sync::Arc};
28use tokio::sync::RwLock;
29
30use crate::error::JQuantsError;
31use chrono::{DateTime, Local};
32use reqwest::{Client, RequestBuilder};
33use serde::{de::DeserializeOwned, Serialize};
34
35const BASE_URL: &str = "https://api.jquants.com/v1";
36fn build_url(path: &str) -> String {
48 format!("{}/{}", BASE_URL, path)
49}
50
51pub trait JQuantsPlanClient: Clone {
53 fn new(api_client: JQuantsApiClient) -> Self;
55
56 fn new_from_refresh_token(refresh_token: String) -> Self {
58 let api_client = JQuantsApiClient::new_from_refresh_token(refresh_token);
59 Self::new(api_client)
60 }
61
62 fn new_from_account(
64 mailaddress: &str,
65 password: &str,
66 ) -> impl std::future::Future<Output = Result<Self, JQuantsError>> + Send {
67 async {
68 let api_client = JQuantsApiClient::new_from_account(mailaddress, password).await?;
69 Ok(Self::new(api_client))
70 }
71 }
72
73 fn get_api_client(&self) -> &JQuantsApiClient;
75
76 fn get_current_refresh_token(&self) -> impl std::future::Future<Output = String> + Send {
78 let api_client = self.get_api_client().clone();
79 async move {
80 api_client
81 .inner
82 .token_set
83 .read()
84 .await
85 .refresh_token
86 .clone()
87 }
88 }
89
90 fn get_refresh_token_from_api(
95 &self,
96 mail_address: &str,
97 password: &str,
98 ) -> impl std::future::Future<Output = Result<String, JQuantsError>> + Send {
99 let api_client = self.get_api_client().clone();
100 async move { get_refresh_token_from_api(&api_client.inner.client, mail_address, password).await }
101 }
102
103 fn get_id_token_from_api(
108 &self,
109 refresh_token: &str,
110 ) -> impl std::future::Future<Output = Result<String, JQuantsError>> + Send {
111 let api_client = self.get_api_client().clone();
112 async move { get_id_token_from_api(&api_client.inner.client, refresh_token).await }
113 }
114
115 fn reset_refresh_token(
117 &self,
118 mail_address: &str,
119 password: &str,
120 ) -> impl std::future::Future<Output = Result<(), JQuantsError>> + Send {
121 let api_client = self.get_api_client().clone();
122 async move {
123 api_client
124 .inner
125 .reset_refresh_token(mail_address, password)
126 .await
127 }
128 }
129
130 fn reset_id_token(&self) -> impl std::future::Future<Output = Result<(), JQuantsError>> + Send {
132 let api_client = self.get_api_client().clone();
133 async move { api_client.inner.reset_id_token().await }
134 }
135
136 fn reauthenticate(
138 &self,
139 mail_address: &str,
140 password: &str,
141 ) -> impl std::future::Future<Output = Result<(), JQuantsError>> + Send {
142 let api_client = self.get_api_client().clone();
143 async move { api_client.inner.reset_tokens(mail_address, password).await }
144 }
145}
146
147#[derive(Clone)]
149pub struct JQuantsApiClient {
150 inner: Arc<JQuantsApiClientRef>,
151}
152impl JQuantsApiClient {
153 fn new_from_refresh_token(refresh_token: String) -> Self {
155 Self {
156 inner: Arc::new(JQuantsApiClientRef::new_from_refresh_token(refresh_token)),
157 }
158 }
159
160 async fn new_from_account(mailaddress: &str, password: &str) -> Result<Self, JQuantsError> {
162 let client_ref = JQuantsApiClientRef::new_from_account(mailaddress, password).await?;
163 Ok(Self {
164 inner: Arc::new(client_ref),
165 })
166 }
167}
168
169pub(crate) struct JQuantsApiClientRef {
173 client: Client,
175 token_set: Arc<RwLock<TokenSet>>,
177}
178
179impl JQuantsApiClientRef {
180 fn new_from_refresh_token(refresh_token: String) -> Self {
182 Self {
183 client: Client::new(),
184 token_set: Arc::new(RwLock::new(TokenSet {
185 refresh_token,
186 id_token: None,
187 })),
188 }
189 }
190
191 async fn new_from_account(mailaddress: &str, password: &str) -> Result<Self, JQuantsError> {
193 let client = Client::new();
194 let refresh_token = get_refresh_token_from_api(&client, mailaddress, password).await?;
195 let new_id_token = get_id_token_from_api(&client, &refresh_token).await?;
196
197 let id_token_wrapper = IdTokenWrapper::new(new_id_token);
198
199 Ok(Self {
200 client,
201 token_set: Arc::new(RwLock::new(TokenSet {
202 refresh_token,
203 id_token: Some(id_token_wrapper),
204 })),
205 })
206 }
207
208 async fn reset_refresh_token(
210 &self,
211 mail_address: &str,
212 password: &str,
213 ) -> Result<(), JQuantsError> {
214 tracing::debug!("Starting reset a refresh token process.");
215
216 match get_refresh_token_from_api(&self.client, mail_address, password).await {
217 Ok(new_refresh_token) => {
218 let mut token_set_write = self.token_set.write().await;
219 token_set_write.refresh_token = new_refresh_token;
220 tracing::debug!("Refresh token refreshed successfully.");
221 Ok(())
222 }
223 Err(e) => {
224 tracing::error!("Failed to refresh a refresh token: {:?}", e);
225 Err(e)
226 }
227 }
228 }
229
230 async fn reset_id_token(&self) -> Result<(), JQuantsError> {
232 tracing::debug!("Starting reset a refresh id process.");
233
234 let refresh_token = { self.token_set.read().await.refresh_token.clone() };
235 match get_id_token_from_api(&self.client, &refresh_token).await {
236 Ok(new_id_token) => {
237 let mut token_set_write = self.token_set.write().await;
238 token_set_write.id_token = Some(IdTokenWrapper::new(new_id_token));
239 tracing::debug!("ID token refreshed successfully.");
240 Ok(())
241 }
242 Err(e) => {
243 tracing::error!("Failed to refresh ID token: {:?}", e);
244 Err(e)
245 }
246 }
247 }
248
249 async fn reset_id_token_if_needed(&self) -> Result<(), JQuantsError> {
251 let needs_refresh = {
252 let token_set = self.token_set.read().await;
253 match &token_set.id_token {
254 Some(token) => !token.is_valid(),
255 None => true,
256 }
257 };
258
259 if needs_refresh {
260 tracing::debug!("ID token is invalid or expired. Attempting to refresh.");
261 self.reset_id_token().await
262 } else {
263 tracing::debug!("ID token is still valid.");
264 Ok(())
265 }
266 }
267
268 async fn reset_tokens(&self, mail_address: &str, password: &str) -> Result<(), JQuantsError> {
270 tracing::debug!("Starting re-authentication process.");
271
272 let new_refresh_token = get_refresh_token_from_api(&self.client, mail_address, password)
274 .await
275 .map_err(|e| {
276 tracing::error!("Failed to obtain new refresh token: {:?}", e);
277 e
278 })?;
279 tracing::debug!("Successfully obtained new refresh token.");
280
281 let new_id_token = get_id_token_from_api(&self.client, &new_refresh_token)
282 .await
283 .map_err(|e| {
284 tracing::error!("Failed to obtain new ID token: {:?}", e);
285 e
286 })?;
287 tracing::debug!("Successfully obtained new ID token.");
288
289 let expires_at = Local::now() + chrono::Duration::hours(24);
290 let new_id_token_wrapper = Some(IdTokenWrapper {
291 id_token: new_id_token,
292 expires_at,
293 });
294 {
295 let mut token_set_write = self.token_set.write().await;
296 token_set_write.refresh_token = new_refresh_token;
297 token_set_write.id_token = new_id_token_wrapper;
298 }
299
300 tracing::debug!("Re-authentication process process completed successfully.");
301 Ok(())
302 }
303
304 async fn get<T: DeserializeOwned + fmt::Debug>(
309 &self,
310 path: &str,
311 params: impl Serialize,
312 ) -> Result<T, JQuantsError> {
313 let url = format!("{BASE_URL}/{}", path);
314 let request = self.client.get(&url).query(¶ms);
315
316 self.common_send_and_refresh_token_if_needed::<T>(request)
317 .await
318 }
319
320 async fn common_send_and_refresh_token_if_needed<T: DeserializeOwned + fmt::Debug>(
322 &self,
323 request: RequestBuilder,
324 ) -> Result<T, JQuantsError> {
325 self.reset_id_token_if_needed().await?;
326
327 self.common_send(request).await
328 }
329
330 async fn common_send<T: DeserializeOwned + fmt::Debug>(
332 &self,
333 request: RequestBuilder,
334 ) -> Result<T, JQuantsError> {
335 let id_token = {
336 self.token_set
337 .read()
338 .await
339 .id_token
340 .as_ref()
341 .ok_or_else(|| {
342 tracing::error!("ID token not found.");
343 JQuantsError::BugError("ID token not found.".to_string())
344 })?
345 .id_token
346 .clone()
347 };
348 let request = request.header("Authorization", &format!("Bearer {id_token}"));
349
350 if let Some(url) = request
351 .try_clone()
352 .and_then(|req| req.build().ok().map(|r| r.url().clone()))
353 {
354 tracing::debug!("Sending API request to URL: {url}");
355 } else {
356 tracing::debug!("Sending API request.");
357 }
358
359 let response = request.send().await?;
360 let status = response.status();
361 let text = response.text().await.unwrap_or_default();
362 tracing::debug!("Received response with status: {}", status);
363
364 if status.is_success() {
365 match serde_json::from_str::<T>(&text) {
366 Ok(data) => {
367 tracing::debug!("Successfully parsed response.");
368 Ok(data)
369 }
370 Err(_) => {
371 tracing::error!("Failed to parse response");
372 Err(JQuantsError::InvalidResponseFormat {
373 status_code: status.as_u16(),
374 body: text,
375 })
376 }
377 }
378 } else {
379 match serde_json::from_str::<JQuantsErrorResponse>(&text) {
380 Ok(error_response) => match status {
381 reqwest::StatusCode::UNAUTHORIZED => {
382 tracing::warn!(
383 "Received UNAUTHORIZED error. Status code: {}",
384 status.as_u16()
385 );
386 Err(JQuantsError::IdTokenInvalidOrExpired {
387 body: error_response,
388 status_code: status.as_u16(),
389 })
390 }
391 _ => {
392 tracing::error!("API error occurred. Status code: {}", status.as_u16());
393 Err(JQuantsError::ApiError {
394 body: error_response,
395 status_code: status.as_u16(),
396 })
397 }
398 },
399 Err(_) => {
400 tracing::error!("Invalid response format. Status code: {}", status.as_u16());
401 Err(JQuantsError::InvalidResponseFormat {
402 status_code: status.as_u16(),
403 body: text,
404 })
405 }
406 }
407 }
408 }
409}
410
411pub(crate) struct TokenSet {
415 refresh_token: String,
418 id_token: Option<IdTokenWrapper>,
420}
421
422pub(crate) struct IdTokenWrapper {
426 id_token: String,
428 expires_at: DateTime<Local>,
430}
431impl IdTokenWrapper {
432 fn new(id_token: String) -> Self {
434 let expires_at = Local::now() + chrono::Duration::hours(24);
435 IdTokenWrapper {
436 id_token,
437 expires_at,
438 }
439 }
440
441 fn is_valid(&self) -> bool {
446 Local::now() < self.expires_at
447 }
448}
449
450impl fmt::Debug for IdTokenWrapper {
453 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
454 let len = self.id_token.len();
455 let masking_id_token = "*".repeat(len);
456
457 f.debug_struct("IdTokenWrapper")
458 .field("id_token", &masking_id_token)
459 .field("expires_at", &self.expires_at)
460 .finish()
461 }
462}