1use serde::{Deserialize, Serialize};
4
5use super::{
6 shared::{
7 traits::{
8 builder::JQuantsBuilder,
9 pagination::{HasPaginationKey, MergePage, Paginatable},
10 },
11 types::index_code::IndexCode,
12 },
13 JQuantsApiClient, JQuantsPlanClient,
14};
15
16#[derive(Clone, Serialize)]
18pub struct IndicesBuilder {
19 #[serde(skip)]
20 client: JQuantsApiClient,
21
22 #[serde(skip_serializing_if = "Option::is_none")]
24 code: Option<IndexCode>,
25 #[serde(skip_serializing_if = "Option::is_none")]
27 from: Option<String>,
28 #[serde(skip_serializing_if = "Option::is_none")]
30 to: Option<String>,
31 #[serde(skip_serializing_if = "Option::is_none")]
33 date: Option<String>,
34
35 #[serde(skip_serializing_if = "Option::is_none")]
37 pagination_key: Option<String>,
38}
39
40impl JQuantsBuilder<IndicesResponse> for IndicesBuilder {
41 async fn send(self) -> Result<IndicesResponse, crate::JQuantsError> {
42 self.send_ref().await
43 }
44
45 async fn send_ref(&self) -> Result<IndicesResponse, crate::JQuantsError> {
46 self.client.inner.get("indices", self).await
47 }
48}
49
50impl Paginatable<IndicesResponse> for IndicesBuilder {
51 fn pagination_key(mut self, pagination_key: impl Into<String>) -> Self {
52 self.pagination_key = Some(pagination_key.into());
53 self
54 }
55}
56
57impl IndicesBuilder {
58 pub(crate) fn new(client: JQuantsApiClient) -> Self {
60 Self {
61 client,
62 code: None,
63 from: None,
64 to: None,
65 date: None,
66 pagination_key: None,
67 }
68 }
69
70 pub fn code(mut self, code: impl Into<IndexCode>) -> Self {
72 self.code = Some(code.into());
73 self
74 }
75
76 pub fn from(mut self, from: impl Into<String>) -> Self {
78 self.from = Some(from.into());
79 self
80 }
81
82 pub fn to(mut self, to: impl Into<String>) -> Self {
84 self.to = Some(to.into());
85 self
86 }
87
88 pub fn date(mut self, date: impl Into<String>) -> Self {
90 self.date = Some(date.into());
91 self
92 }
93}
94
95pub trait IndicesApi: JQuantsPlanClient {
97 fn get_indices(&self) -> IndicesBuilder {
101 IndicesBuilder::new(self.get_api_client().clone())
102 }
103}
104
105#[derive(Debug, Clone, PartialEq, Deserialize)]
109pub struct IndicesResponse {
110 pub indices: Vec<IndexItem>,
112 pub pagination_key: Option<String>,
114}
115
116impl HasPaginationKey for IndicesResponse {
117 fn get_pagination_key(&self) -> Option<&str> {
118 self.pagination_key.as_deref()
119 }
120}
121
122impl MergePage for IndicesResponse {
123 fn merge_page(
124 page: Result<Vec<Self>, crate::JQuantsError>,
125 ) -> Result<Self, crate::JQuantsError> {
126 let mut page = page?;
127 let mut merged = page.pop().unwrap();
128 for p in page {
129 merged.indices.extend(p.indices);
130 }
131 merged.pagination_key = None;
132
133 Ok(merged)
134 }
135}
136
137#[derive(Debug, Clone, PartialEq, Deserialize)]
139pub struct IndexItem {
140 #[serde(rename = "Date")]
142 pub date: String,
143
144 #[serde(rename = "Code")]
146 pub code: IndexCode,
147
148 #[serde(rename = "Open")]
150 pub open: f64,
151
152 #[serde(rename = "High")]
154 pub high: f64,
155
156 #[serde(rename = "Low")]
158 pub low: f64,
159
160 #[serde(rename = "Close")]
162 pub close: f64,
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[test]
170 fn test_deserialize_indices_response() {
171 let json = r#"
172 {
173 "indices": [
174 {
175 "Date": "2023-12-01",
176 "Code": "0028",
177 "Open": 1199.18,
178 "High": 1202.58,
179 "Low": 1195.01,
180 "Close": 1200.17
181 }
182 ],
183 "pagination_key": "value1.value2."
184 }
185 "#;
186
187 let response: IndicesResponse = serde_json::from_str(json).unwrap();
188 let expected_response = IndicesResponse {
189 indices: vec![IndexItem {
190 date: "2023-12-01".to_string(),
191 code: IndexCode::TOPIXCore30,
192 open: 1199.18,
193 high: 1202.58,
194 low: 1195.01,
195 close: 1200.17,
196 }],
197 pagination_key: Some("value1.value2.".to_string()),
198 };
199
200 pretty_assertions::assert_eq!(response, expected_response);
201 }
202
203 #[test]
204 fn test_deserialize_indices_response_no_pagination_key() {
205 let json = r#"
206 {
207 "indices": [
208 {
209 "Date": "2023-12-01",
210 "Code": "0028",
211 "Open": 1199.18,
212 "High": 1202.58,
213 "Low": 1195.01,
214 "Close": 1200.17
215 }
216 ]
217 }
218 "#;
219
220 let response: IndicesResponse = serde_json::from_str(json).unwrap();
221 let expected_response = IndicesResponse {
222 indices: vec![IndexItem {
223 date: "2023-12-01".to_string(),
224 code: IndexCode::TOPIXCore30,
225 open: 1199.18,
226 high: 1202.58,
227 low: 1195.01,
228 close: 1200.17,
229 }],
230 pagination_key: None,
231 };
232
233 pretty_assertions::assert_eq!(response, expected_response);
234 }
235
236 #[test]
237 fn test_deserialize_indices_response_multiple_items() {
238 let json = r#"
239 {
240 "indices": [
241 {
242 "Date": "2023-11-30",
243 "Code": "0000",
244 "Open": 1500.50,
245 "High": 1520.75,
246 "Low": 1495.00,
247 "Close": 1510.25
248 },
249 {
250 "Date": "2023-12-01",
251 "Code": "0028",
252 "Open": 1199.18,
253 "High": 1202.58,
254 "Low": 1195.01,
255 "Close": 1200.17
256 }
257 ],
258 "pagination_key": "value1.value2."
259 }
260 "#;
261
262 let response: IndicesResponse = serde_json::from_str(json).unwrap();
263 let expected_response = IndicesResponse {
264 indices: vec![
265 IndexItem {
266 date: "2023-11-30".to_string(),
267 code: IndexCode::TOPIX,
268 open: 1500.50,
269 high: 1520.75,
270 low: 1495.00,
271 close: 1510.25,
272 },
273 IndexItem {
274 date: "2023-12-01".to_string(),
275 code: IndexCode::TOPIXCore30,
276 open: 1199.18,
277 high: 1202.58,
278 low: 1195.01,
279 close: 1200.17,
280 },
281 ],
282 pagination_key: Some("value1.value2.".to_string()),
283 };
284
285 pretty_assertions::assert_eq!(response, expected_response);
286 }
287
288 #[test]
289 fn test_deserialize_indices_response_no_data() {
290 let json = r#"
291 {
292 "indices": []
293 }
294 "#;
295
296 let response: IndicesResponse = serde_json::from_str(json).unwrap();
297 let expected_response = IndicesResponse {
298 indices: vec![],
299 pagination_key: None,
300 };
301
302 pretty_assertions::assert_eq!(response, expected_response);
303 }
304}