openfigi_rs/model/response/
mapping_response.rs1use crate::error::{OpenFIGIError, Result};
42use crate::model::response::common::FigiResult;
43use serde::{Deserialize, Serialize};
44
45#[derive(Debug)]
58pub struct MappingResponses(Vec<Result<MappingData>>);
59
60impl MappingResponses {
61 #[doc(hidden)]
62 pub(crate) fn new(results: Vec<Result<MappingData>>) -> Self {
66 Self(results)
67 }
68
69 pub fn successes(&self) -> impl Iterator<Item = (usize, &MappingData)> {
73 self.0
74 .iter()
75 .enumerate()
76 .filter_map(|(i, r)| r.as_ref().ok().map(|data| (i, data)))
77 }
78
79 pub fn failures(&self) -> impl Iterator<Item = (usize, &OpenFIGIError)> {
83 self.0
84 .iter()
85 .enumerate()
86 .filter_map(|(i, r)| r.as_ref().err().map(|err| (i, err)))
87 }
88
89 #[must_use]
91 pub fn len(&self) -> usize {
92 self.0.len()
93 }
94
95 #[must_use]
97 pub fn is_empty(&self) -> bool {
98 self.0.is_empty()
99 }
100
101 pub fn as_slice(&self) -> &[Result<MappingData>] {
103 &self.0
104 }
105}
106
107#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
124pub struct MappingData {
125 pub data: Vec<FigiResult>,
135}
136
137impl MappingData {
138 #[must_use]
142 pub fn data(&self) -> &[FigiResult] {
143 &self.data
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150 use crate::{model::response::common::ResponseResult, test_utils::load_test_data};
151
152 fn from_response_results(raw: Vec<ResponseResult<MappingData>>) -> MappingResponses {
154 MappingResponses::new(
155 raw.into_iter()
156 .map(|res| match res {
157 ResponseResult::Success(data) => Ok(data),
158 ResponseResult::Error(err) => Err(OpenFIGIError::response_error(
159 reqwest::StatusCode::OK,
160 err.error,
161 String::new(),
162 )),
163 })
164 .collect(),
165 )
166 }
167
168 #[test]
169 fn test_deserialize_isin_example() {
170 let json_str = load_test_data("mapping", "isin_example.json");
171
172 let raw: Vec<ResponseResult<MappingData>> =
173 serde_json::from_str(&json_str).expect("Failed to deserialize mapping response");
174 let mapping_response = from_response_results(raw);
175
176 assert_eq!(mapping_response.len(), 1);
177 let response_result = &mapping_response.as_slice()[0];
178 match response_result {
179 Ok(mapping_data) => {
180 let figi_result = mapping_data.data();
181 assert!(!figi_result.is_empty());
182
183 let first_entry = &figi_result[0];
185 assert_eq!(first_entry.figi, "BBG000BLNNH6");
186 assert_eq!(first_entry.ticker, Some("IBM".to_string()));
187 assert_eq!(first_entry.display_name(), "INTL BUSINESS MACHINES CORP");
188
189 assert!(first_entry.has_composite_figi());
191 assert!(first_entry.has_share_class_figi());
192
193 assert_eq!(first_entry.composite_figi, Some("BBG000BLNNH6".to_string()));
195 assert_eq!(
196 first_entry.share_class_figi,
197 Some("BBG001S5S399".to_string())
198 );
199 }
200 Err(e) => panic!("Expected success, got error: {e}"),
201 }
202 }
203
204 #[test]
205 fn test_deserialize_invalid_identifier() {
206 let json_str = load_test_data("mapping", "invalid_identifier.json");
207 let raw: Vec<ResponseResult<MappingData>> =
208 serde_json::from_str(&json_str).expect("Failed to deserialize mapping response");
209 let mapping_response = from_response_results(raw);
210
211 assert_eq!(mapping_response.len(), 1);
212 let response_result = &mapping_response.as_slice()[0];
213 match response_result {
214 Ok(_) => panic!("Expected error, got success"),
215 Err(OpenFIGIError::ResponseError(resp)) => {
216 assert!(resp.message.contains("Invalid idValue format."));
217 }
218 Err(e) => panic!("Unexpected error variant: {e}"),
219 }
220 }
221
222 #[test]
223 fn test_deserialize_bulk_request() {
224 let json_str = load_test_data("mapping", "bulk_request.json");
225 let raw: Vec<ResponseResult<MappingData>> =
226 serde_json::from_str(&json_str).expect("Failed to deserialize mapping response");
227 let mapping_response = from_response_results(raw);
228
229 assert_eq!(mapping_response.len(), 2);
230
231 let ibm_response_result = &mapping_response.as_slice()[0];
233 match ibm_response_result {
234 Ok(mapping_data) => {
235 let ibm_figi_result = mapping_data.data();
236 assert!(!ibm_figi_result.is_empty());
237 assert_eq!(ibm_figi_result[0].ticker, Some("IBM".to_string()));
238 }
239 Err(e) => panic!("Expected IBM success, got error: {e}"),
240 }
241
242 let aapl_response_result = &mapping_response.as_slice()[1];
244 match aapl_response_result {
245 Ok(mapping_data) => {
246 let aapl_figi_result = mapping_data.data();
247 assert!(!aapl_figi_result.is_empty());
248 assert_eq!(aapl_figi_result[0].ticker, Some("AAPL".to_string()));
249 }
250 Err(e) => panic!("Expected AAPL success, got error: {e}"),
251 }
252 }
253
254 #[test]
255 fn test_deserialize_cusip_with_exchange() {
256 let json_str = load_test_data("mapping", "cusip_with_exchange.json");
257 let raw: Vec<ResponseResult<MappingData>> =
258 serde_json::from_str(&json_str).expect("Failed to deserialize mapping response");
259 let mapping_response = from_response_results(raw);
260
261 assert_eq!(mapping_response.len(), 1);
262 let response_result = &mapping_response.as_slice()[0];
263 match response_result {
264 Ok(mapping_data) => {
265 let figi_result = mapping_data.data();
266 assert!(!figi_result.is_empty());
267
268 for data in figi_result {
270 assert!(!data.figi.is_empty());
271 assert!(data.ticker.is_some());
272 }
273 }
274 Err(e) => panic!("Expected success, got error: {e}"),
275 }
276 }
277
278 #[test]
279 fn test_deserialize_ticker_with_security_type() {
280 let json_str = load_test_data("mapping", "ticker_with_security_type.json");
281 let raw: Vec<ResponseResult<MappingData>> =
282 serde_json::from_str(&json_str).expect("Failed to deserialize mapping response");
283 let mapping_response = from_response_results(raw);
284
285 assert_eq!(mapping_response.len(), 1);
286 let response_result = &mapping_response.as_slice()[0];
287 match response_result {
288 Ok(mapping_data) => {
289 let figi_result = mapping_data.data();
290 assert!(!figi_result.is_empty());
291
292 for data in figi_result {
294 assert!(data.security_type.is_some());
295 assert!(data.market_sector.is_some());
296 }
297 }
298 Err(e) => panic!("Expected success, got error: {e}"),
299 }
300 }
301
302 #[test]
303 fn test_deserialize_option_example() {
304 let json_str = load_test_data("mapping", "option_example.json");
305 let raw: Vec<ResponseResult<MappingData>> =
306 serde_json::from_str(&json_str).expect("Failed to deserialize mapping response");
307 let mapping_response = from_response_results(raw);
308
309 assert_eq!(mapping_response.len(), 1);
310 let response_result = &mapping_response.as_slice()[0];
311 match response_result {
312 Ok(mapping_data) => {
313 let figi_result = mapping_data.data();
314 for data in figi_result {
315 assert!(!data.figi.is_empty());
316 }
317 }
318 Err(e) => {
319 assert!(!e.to_string().is_empty());
321 }
322 }
323 }
324
325 #[test]
326 fn test_deserialize_currency_mic_example() {
327 let json_str = load_test_data("mapping", "currency_mic_example.json");
328 let raw: Vec<ResponseResult<MappingData>> =
329 serde_json::from_str(&json_str).expect("Failed to deserialize mapping response");
330 let mapping_response = from_response_results(raw);
331
332 assert_eq!(mapping_response.len(), 1);
333 let response_result = &mapping_response.as_slice()[0];
334 match response_result {
335 Ok(mapping_data) => {
336 let figi_result = mapping_data.data();
337 for data in figi_result {
338 assert!(!data.figi.is_empty());
339 }
340 }
341 Err(e) => {
342 assert!(!e.to_string().is_empty());
344 }
345 }
346 }
347
348 #[test]
349 fn test_figi_result_display_name_fallback() {
350 let figi_with_ticker = FigiResult {
352 figi: "BBG000BLNNH6".to_string(),
353 name: None,
354 ticker: Some("IBM".to_string()),
355 security_type: None,
356 market_sector: None,
357 exch_code: None,
358 share_class_figi: None,
359 composite_figi: None,
360 security_type2: None,
361 security_description: None,
362 metadata: None,
363 };
364 assert_eq!(figi_with_ticker.display_name(), "IBM");
365
366 let figi_only = FigiResult {
368 figi: "BBG000BLNNH6".to_string(),
369 name: None,
370 ticker: None,
371 security_type: None,
372 market_sector: None,
373 exch_code: None,
374 share_class_figi: None,
375 composite_figi: None,
376 security_type2: None,
377 security_description: None,
378 metadata: None,
379 };
380 assert_eq!(figi_only.display_name(), "BBG000BLNNH6");
381 }
382}