1use crate::error::{Error, Result};
4use crate::models::*;
5use crate::types::*;
6use chrono::{DateTime, Utc};
7use reqwest::Client;
8use url::Url;
9
10fn fmt_dt(dt: &DateTime<Utc>) -> String {
12 dt.format("%Y-%m-%dT%H:%M:%SZ").to_string()
13}
14
15const DEFAULT_BASE_URL: &str = "https://api.the-odds-api.com";
16const IPV6_BASE_URL: &str = "https://ipv6-api.the-odds-api.com";
17
18#[derive(Debug, Clone)]
20pub struct TheOddsApiClientBuilder {
21 api_key: String,
22 base_url: String,
23 client: Option<Client>,
24}
25
26impl TheOddsApiClientBuilder {
27 pub fn new(api_key: impl Into<String>) -> Self {
29 Self {
30 api_key: api_key.into(),
31 base_url: DEFAULT_BASE_URL.to_string(),
32 client: None,
33 }
34 }
35
36 pub fn base_url(mut self, url: impl Into<String>) -> Self {
38 self.base_url = url.into();
39 self
40 }
41
42 pub fn use_ipv6(mut self) -> Self {
44 self.base_url = IPV6_BASE_URL.to_string();
45 self
46 }
47
48 pub fn client(mut self, client: Client) -> Self {
50 self.client = Some(client);
51 self
52 }
53
54 pub fn build(self) -> TheOddsApiClient {
56 TheOddsApiClient {
57 api_key: self.api_key,
58 base_url: self.base_url,
59 client: self.client.unwrap_or_default(),
60 }
61 }
62}
63
64#[derive(Debug, Clone)]
66pub struct TheOddsApiClient {
67 api_key: String,
68 base_url: String,
69 client: Client,
70}
71
72impl TheOddsApiClient {
73 pub fn new(api_key: impl Into<String>) -> Self {
75 TheOddsApiClientBuilder::new(api_key).build()
76 }
77
78 pub fn builder(api_key: impl Into<String>) -> TheOddsApiClientBuilder {
80 TheOddsApiClientBuilder::new(api_key)
81 }
82
83 fn build_url(&self, path: &str, params: &[(&str, String)]) -> Result<Url> {
85 let mut url = Url::parse(&format!("{}{}", self.base_url, path))?;
86 {
87 let mut query = url.query_pairs_mut();
88 query.append_pair("apiKey", &self.api_key);
89 for (key, value) in params {
90 if !value.is_empty() {
91 query.append_pair(key, value);
92 }
93 }
94 }
95 Ok(url)
96 }
97
98 async fn get<T: serde::de::DeserializeOwned>(&self, url: Url) -> Result<Response<T>> {
100 let response = self.client.get(url).send().await?;
101 let usage = UsageInfo::from_headers(response.headers());
102 let status = response.status();
103
104 if status.is_success() {
105 let data = response.json().await?;
106 Ok(Response::new(data, usage))
107 } else if status == reqwest::StatusCode::UNAUTHORIZED {
108 Err(Error::Unauthorized)
109 } else if status == reqwest::StatusCode::TOO_MANY_REQUESTS {
110 Err(Error::RateLimited {
111 requests_remaining: usage.requests_remaining,
112 })
113 } else {
114 let message = response
115 .text()
116 .await
117 .unwrap_or_else(|_| "Unknown error".to_string());
118 Err(Error::Api {
119 status: status.as_u16(),
120 message,
121 })
122 }
123 }
124
125 pub async fn get_sports(&self) -> Result<Response<Vec<Sport>>> {
146 let url = self.build_url("/v4/sports", &[])?;
147 self.get(url).await
148 }
149
150 pub async fn get_all_sports(&self) -> Result<Response<Vec<Sport>>> {
154 let url = self.build_url("/v4/sports", &[("all", "true".to_string())])?;
155 self.get(url).await
156 }
157
158 pub fn get_events(&self, sport: impl Into<String>) -> GetEventsRequest<'_> {
166 GetEventsRequest::new(self, sport.into())
167 }
168
169 pub fn get_odds(&self, sport: impl Into<String>) -> GetOddsRequest<'_> {
194 GetOddsRequest::new(self, sport.into())
195 }
196
197 pub fn get_upcoming_odds(&self) -> GetOddsRequest<'_> {
199 GetOddsRequest::new(self, "upcoming".to_string())
200 }
201
202 pub fn get_scores(&self, sport: impl Into<String>) -> GetScoresRequest<'_> {
211 GetScoresRequest::new(self, sport.into())
212 }
213
214 pub fn get_event_odds(
222 &self,
223 sport: impl Into<String>,
224 event_id: impl Into<String>,
225 ) -> GetEventOddsRequest<'_> {
226 GetEventOddsRequest::new(self, sport.into(), event_id.into())
227 }
228
229 pub fn get_event_markets(
238 &self,
239 sport: impl Into<String>,
240 event_id: impl Into<String>,
241 ) -> GetEventMarketsRequest<'_> {
242 GetEventMarketsRequest::new(self, sport.into(), event_id.into())
243 }
244
245 pub async fn get_participants(
254 &self,
255 sport: impl Into<String>,
256 ) -> Result<Response<Vec<Participant>>> {
257 let url = self.build_url(&format!("/v4/sports/{}/participants", sport.into()), &[])?;
258 self.get(url).await
259 }
260
261 pub fn get_historical_odds(&self, sport: impl Into<String>) -> GetHistoricalOddsRequest<'_> {
270 GetHistoricalOddsRequest::new(self, sport.into())
271 }
272
273 pub fn get_historical_events(&self, sport: impl Into<String>) -> GetHistoricalEventsRequest<'_> {
278 GetHistoricalEventsRequest::new(self, sport.into())
279 }
280
281 pub fn get_historical_event_odds(
283 &self,
284 sport: impl Into<String>,
285 event_id: impl Into<String>,
286 ) -> GetHistoricalEventOddsRequest<'_> {
287 GetHistoricalEventOddsRequest::new(self, sport.into(), event_id.into())
288 }
289}
290
291#[derive(Debug)]
297pub struct GetEventsRequest<'a> {
298 client: &'a TheOddsApiClient,
299 sport: String,
300 date_format: Option<DateFormat>,
301 event_ids: Option<Vec<String>>,
302 commence_time_from: Option<DateTime<Utc>>,
303 commence_time_to: Option<DateTime<Utc>>,
304}
305
306impl<'a> GetEventsRequest<'a> {
307 fn new(client: &'a TheOddsApiClient, sport: String) -> Self {
308 Self {
309 client,
310 sport,
311 date_format: None,
312 event_ids: None,
313 commence_time_from: None,
314 commence_time_to: None,
315 }
316 }
317
318 pub fn date_format(mut self, format: DateFormat) -> Self {
320 self.date_format = Some(format);
321 self
322 }
323
324 pub fn event_ids(mut self, ids: impl IntoIterator<Item = impl Into<String>>) -> Self {
326 self.event_ids = Some(ids.into_iter().map(Into::into).collect());
327 self
328 }
329
330 pub fn commence_time_from(mut self, time: DateTime<Utc>) -> Self {
332 self.commence_time_from = Some(time);
333 self
334 }
335
336 pub fn commence_time_to(mut self, time: DateTime<Utc>) -> Self {
338 self.commence_time_to = Some(time);
339 self
340 }
341
342 pub async fn send(self) -> Result<Response<Vec<Event>>> {
344 let mut params = Vec::new();
345
346 if let Some(fmt) = self.date_format {
347 params.push(("dateFormat", fmt.to_string()));
348 }
349 if let Some(ids) = self.event_ids {
350 params.push(("eventIds", ids.join(",")));
351 }
352 if let Some(time) = self.commence_time_from {
353 params.push(("commenceTimeFrom", fmt_dt(&time)));
354 }
355 if let Some(time) = self.commence_time_to {
356 params.push(("commenceTimeTo", fmt_dt(&time)));
357 }
358
359 let url = self
360 .client
361 .build_url(&format!("/v4/sports/{}/events", self.sport), ¶ms)?;
362 self.client.get(url).await
363 }
364}
365
366#[derive(Debug)]
368pub struct GetOddsRequest<'a> {
369 client: &'a TheOddsApiClient,
370 sport: String,
371 regions: Vec<Region>,
372 markets: Option<Vec<Market>>,
373 date_format: Option<DateFormat>,
374 odds_format: Option<OddsFormat>,
375 event_ids: Option<Vec<String>>,
376 bookmakers: Option<Vec<String>>,
377 commence_time_from: Option<DateTime<Utc>>,
378 commence_time_to: Option<DateTime<Utc>>,
379 include_links: Option<bool>,
380 include_sids: Option<bool>,
381 include_bet_limits: Option<bool>,
382}
383
384impl<'a> GetOddsRequest<'a> {
385 fn new(client: &'a TheOddsApiClient, sport: String) -> Self {
386 Self {
387 client,
388 sport,
389 regions: Vec::new(),
390 markets: None,
391 date_format: None,
392 odds_format: None,
393 event_ids: None,
394 bookmakers: None,
395 commence_time_from: None,
396 commence_time_to: None,
397 include_links: None,
398 include_sids: None,
399 include_bet_limits: None,
400 }
401 }
402
403 pub fn regions(mut self, regions: &[Region]) -> Self {
405 self.regions = regions.to_vec();
406 self
407 }
408
409 pub fn region(mut self, region: Region) -> Self {
411 self.regions.push(region);
412 self
413 }
414
415 pub fn markets(mut self, markets: &[Market]) -> Self {
417 self.markets = Some(markets.to_vec());
418 self
419 }
420
421 pub fn market(mut self, market: Market) -> Self {
423 self.markets.get_or_insert_with(Vec::new).push(market);
424 self
425 }
426
427 pub fn date_format(mut self, format: DateFormat) -> Self {
429 self.date_format = Some(format);
430 self
431 }
432
433 pub fn odds_format(mut self, format: OddsFormat) -> Self {
435 self.odds_format = Some(format);
436 self
437 }
438
439 pub fn event_ids(mut self, ids: impl IntoIterator<Item = impl Into<String>>) -> Self {
441 self.event_ids = Some(ids.into_iter().map(Into::into).collect());
442 self
443 }
444
445 pub fn bookmakers(mut self, bookmakers: impl IntoIterator<Item = impl Into<String>>) -> Self {
447 self.bookmakers = Some(bookmakers.into_iter().map(Into::into).collect());
448 self
449 }
450
451 pub fn commence_time_from(mut self, time: DateTime<Utc>) -> Self {
453 self.commence_time_from = Some(time);
454 self
455 }
456
457 pub fn commence_time_to(mut self, time: DateTime<Utc>) -> Self {
459 self.commence_time_to = Some(time);
460 self
461 }
462
463 pub fn include_links(mut self, include: bool) -> Self {
465 self.include_links = Some(include);
466 self
467 }
468
469 pub fn include_sids(mut self, include: bool) -> Self {
471 self.include_sids = Some(include);
472 self
473 }
474
475 pub fn include_bet_limits(mut self, include: bool) -> Self {
477 self.include_bet_limits = Some(include);
478 self
479 }
480
481 pub async fn send(self) -> Result<Response<Vec<EventOdds>>> {
483 if self.regions.is_empty() {
484 return Err(Error::MissingParameter("regions"));
485 }
486
487 let mut params = vec![("regions", format_csv(&self.regions))];
488
489 if let Some(markets) = self.markets {
490 params.push(("markets", format_csv(&markets)));
491 }
492 if let Some(fmt) = self.date_format {
493 params.push(("dateFormat", fmt.to_string()));
494 }
495 if let Some(fmt) = self.odds_format {
496 params.push(("oddsFormat", fmt.to_string()));
497 }
498 if let Some(ids) = self.event_ids {
499 params.push(("eventIds", ids.join(",")));
500 }
501 if let Some(bookmakers) = self.bookmakers {
502 params.push(("bookmakers", bookmakers.join(",")));
503 }
504 if let Some(time) = self.commence_time_from {
505 params.push(("commenceTimeFrom", fmt_dt(&time)));
506 }
507 if let Some(time) = self.commence_time_to {
508 params.push(("commenceTimeTo", fmt_dt(&time)));
509 }
510 if let Some(true) = self.include_links {
511 params.push(("includeLinks", "true".to_string()));
512 }
513 if let Some(true) = self.include_sids {
514 params.push(("includeSids", "true".to_string()));
515 }
516 if let Some(true) = self.include_bet_limits {
517 params.push(("includeBetLimits", "true".to_string()));
518 }
519
520 let url = self
521 .client
522 .build_url(&format!("/v4/sports/{}/odds", self.sport), ¶ms)?;
523 self.client.get(url).await
524 }
525}
526
527#[derive(Debug)]
529pub struct GetScoresRequest<'a> {
530 client: &'a TheOddsApiClient,
531 sport: String,
532 days_from: Option<u8>,
533 date_format: Option<DateFormat>,
534 event_ids: Option<Vec<String>>,
535}
536
537impl<'a> GetScoresRequest<'a> {
538 fn new(client: &'a TheOddsApiClient, sport: String) -> Self {
539 Self {
540 client,
541 sport,
542 days_from: None,
543 date_format: None,
544 event_ids: None,
545 }
546 }
547
548 pub fn days_from(mut self, days: u8) -> Self {
550 self.days_from = Some(days.clamp(1, 3));
551 self
552 }
553
554 pub fn date_format(mut self, format: DateFormat) -> Self {
556 self.date_format = Some(format);
557 self
558 }
559
560 pub fn event_ids(mut self, ids: impl IntoIterator<Item = impl Into<String>>) -> Self {
562 self.event_ids = Some(ids.into_iter().map(Into::into).collect());
563 self
564 }
565
566 pub async fn send(self) -> Result<Response<Vec<EventScore>>> {
568 let mut params = Vec::new();
569
570 if let Some(days) = self.days_from {
571 params.push(("daysFrom", days.to_string()));
572 }
573 if let Some(fmt) = self.date_format {
574 params.push(("dateFormat", fmt.to_string()));
575 }
576 if let Some(ids) = self.event_ids {
577 params.push(("eventIds", ids.join(",")));
578 }
579
580 let url = self
581 .client
582 .build_url(&format!("/v4/sports/{}/scores", self.sport), ¶ms)?;
583 self.client.get(url).await
584 }
585}
586
587#[derive(Debug)]
589pub struct GetEventOddsRequest<'a> {
590 client: &'a TheOddsApiClient,
591 sport: String,
592 event_id: String,
593 regions: Vec<Region>,
594 markets: Option<Vec<Market>>,
595 date_format: Option<DateFormat>,
596 odds_format: Option<OddsFormat>,
597 bookmakers: Option<Vec<String>>,
598 include_links: Option<bool>,
599 include_sids: Option<bool>,
600 include_multipliers: Option<bool>,
601}
602
603impl<'a> GetEventOddsRequest<'a> {
604 fn new(client: &'a TheOddsApiClient, sport: String, event_id: String) -> Self {
605 Self {
606 client,
607 sport,
608 event_id,
609 regions: Vec::new(),
610 markets: None,
611 date_format: None,
612 odds_format: None,
613 bookmakers: None,
614 include_links: None,
615 include_sids: None,
616 include_multipliers: None,
617 }
618 }
619
620 pub fn regions(mut self, regions: &[Region]) -> Self {
622 self.regions = regions.to_vec();
623 self
624 }
625
626 pub fn region(mut self, region: Region) -> Self {
628 self.regions.push(region);
629 self
630 }
631
632 pub fn markets(mut self, markets: &[Market]) -> Self {
634 self.markets = Some(markets.to_vec());
635 self
636 }
637
638 pub fn market(mut self, market: Market) -> Self {
640 self.markets.get_or_insert_with(Vec::new).push(market);
641 self
642 }
643
644 pub fn custom_market(mut self, key: impl Into<String>) -> Self {
646 self.markets
647 .get_or_insert_with(Vec::new)
648 .push(Market::Custom(key.into()));
649 self
650 }
651
652 pub fn date_format(mut self, format: DateFormat) -> Self {
654 self.date_format = Some(format);
655 self
656 }
657
658 pub fn odds_format(mut self, format: OddsFormat) -> Self {
660 self.odds_format = Some(format);
661 self
662 }
663
664 pub fn bookmakers(mut self, bookmakers: impl IntoIterator<Item = impl Into<String>>) -> Self {
666 self.bookmakers = Some(bookmakers.into_iter().map(Into::into).collect());
667 self
668 }
669
670 pub fn include_links(mut self, include: bool) -> Self {
672 self.include_links = Some(include);
673 self
674 }
675
676 pub fn include_sids(mut self, include: bool) -> Self {
678 self.include_sids = Some(include);
679 self
680 }
681
682 pub fn include_multipliers(mut self, include: bool) -> Self {
684 self.include_multipliers = Some(include);
685 self
686 }
687
688 pub async fn send(self) -> Result<Response<EventOdds>> {
690 if self.regions.is_empty() {
691 return Err(Error::MissingParameter("regions"));
692 }
693
694 let mut params = vec![("regions", format_csv(&self.regions))];
695
696 if let Some(markets) = self.markets {
697 params.push(("markets", format_csv(&markets)));
698 }
699 if let Some(fmt) = self.date_format {
700 params.push(("dateFormat", fmt.to_string()));
701 }
702 if let Some(fmt) = self.odds_format {
703 params.push(("oddsFormat", fmt.to_string()));
704 }
705 if let Some(bookmakers) = self.bookmakers {
706 params.push(("bookmakers", bookmakers.join(",")));
707 }
708 if let Some(true) = self.include_links {
709 params.push(("includeLinks", "true".to_string()));
710 }
711 if let Some(true) = self.include_sids {
712 params.push(("includeSids", "true".to_string()));
713 }
714 if let Some(true) = self.include_multipliers {
715 params.push(("includeMultipliers", "true".to_string()));
716 }
717
718 let url = self.client.build_url(
719 &format!("/v4/sports/{}/events/{}/odds", self.sport, self.event_id),
720 ¶ms,
721 )?;
722 self.client.get(url).await
723 }
724}
725
726#[derive(Debug)]
728pub struct GetEventMarketsRequest<'a> {
729 client: &'a TheOddsApiClient,
730 sport: String,
731 event_id: String,
732 regions: Vec<Region>,
733 bookmakers: Option<Vec<String>>,
734 date_format: Option<DateFormat>,
735}
736
737impl<'a> GetEventMarketsRequest<'a> {
738 fn new(client: &'a TheOddsApiClient, sport: String, event_id: String) -> Self {
739 Self {
740 client,
741 sport,
742 event_id,
743 regions: Vec::new(),
744 bookmakers: None,
745 date_format: None,
746 }
747 }
748
749 pub fn regions(mut self, regions: &[Region]) -> Self {
751 self.regions = regions.to_vec();
752 self
753 }
754
755 pub fn region(mut self, region: Region) -> Self {
757 self.regions.push(region);
758 self
759 }
760
761 pub fn bookmakers(mut self, bookmakers: impl IntoIterator<Item = impl Into<String>>) -> Self {
763 self.bookmakers = Some(bookmakers.into_iter().map(Into::into).collect());
764 self
765 }
766
767 pub fn date_format(mut self, format: DateFormat) -> Self {
769 self.date_format = Some(format);
770 self
771 }
772
773 pub async fn send(self) -> Result<Response<EventMarkets>> {
775 if self.regions.is_empty() {
776 return Err(Error::MissingParameter("regions"));
777 }
778
779 let mut params = vec![("regions", format_csv(&self.regions))];
780
781 if let Some(bookmakers) = self.bookmakers {
782 params.push(("bookmakers", bookmakers.join(",")));
783 }
784 if let Some(fmt) = self.date_format {
785 params.push(("dateFormat", fmt.to_string()));
786 }
787
788 let url = self.client.build_url(
789 &format!(
790 "/v4/sports/{}/events/{}/markets",
791 self.sport, self.event_id
792 ),
793 ¶ms,
794 )?;
795 self.client.get(url).await
796 }
797}
798
799#[derive(Debug)]
801pub struct GetHistoricalOddsRequest<'a> {
802 client: &'a TheOddsApiClient,
803 sport: String,
804 date: Option<DateTime<Utc>>,
805 regions: Vec<Region>,
806 markets: Option<Vec<Market>>,
807 date_format: Option<DateFormat>,
808 odds_format: Option<OddsFormat>,
809 event_ids: Option<Vec<String>>,
810 bookmakers: Option<Vec<String>>,
811 commence_time_from: Option<DateTime<Utc>>,
812 commence_time_to: Option<DateTime<Utc>>,
813}
814
815impl<'a> GetHistoricalOddsRequest<'a> {
816 fn new(client: &'a TheOddsApiClient, sport: String) -> Self {
817 Self {
818 client,
819 sport,
820 date: None,
821 regions: Vec::new(),
822 markets: None,
823 date_format: None,
824 odds_format: None,
825 event_ids: None,
826 bookmakers: None,
827 commence_time_from: None,
828 commence_time_to: None,
829 }
830 }
831
832 pub fn date(mut self, date: DateTime<Utc>) -> Self {
834 self.date = Some(date);
835 self
836 }
837
838 pub fn regions(mut self, regions: &[Region]) -> Self {
840 self.regions = regions.to_vec();
841 self
842 }
843
844 pub fn region(mut self, region: Region) -> Self {
846 self.regions.push(region);
847 self
848 }
849
850 pub fn markets(mut self, markets: &[Market]) -> Self {
852 self.markets = Some(markets.to_vec());
853 self
854 }
855
856 pub fn date_format(mut self, format: DateFormat) -> Self {
858 self.date_format = Some(format);
859 self
860 }
861
862 pub fn odds_format(mut self, format: OddsFormat) -> Self {
864 self.odds_format = Some(format);
865 self
866 }
867
868 pub fn event_ids(mut self, ids: impl IntoIterator<Item = impl Into<String>>) -> Self {
870 self.event_ids = Some(ids.into_iter().map(Into::into).collect());
871 self
872 }
873
874 pub fn bookmakers(mut self, bookmakers: impl IntoIterator<Item = impl Into<String>>) -> Self {
876 self.bookmakers = Some(bookmakers.into_iter().map(Into::into).collect());
877 self
878 }
879
880 pub fn commence_time_from(mut self, time: DateTime<Utc>) -> Self {
882 self.commence_time_from = Some(time);
883 self
884 }
885
886 pub fn commence_time_to(mut self, time: DateTime<Utc>) -> Self {
888 self.commence_time_to = Some(time);
889 self
890 }
891
892 pub async fn send(self) -> Result<Response<HistoricalResponse<Vec<EventOdds>>>> {
894 let date = self.date.ok_or(Error::MissingParameter("date"))?;
895 if self.regions.is_empty() {
896 return Err(Error::MissingParameter("regions"));
897 }
898
899 let mut params = vec![
900 ("date", fmt_dt(&date)),
901 ("regions", format_csv(&self.regions)),
902 ];
903
904 if let Some(markets) = self.markets {
905 params.push(("markets", format_csv(&markets)));
906 }
907 if let Some(fmt) = self.date_format {
908 params.push(("dateFormat", fmt.to_string()));
909 }
910 if let Some(fmt) = self.odds_format {
911 params.push(("oddsFormat", fmt.to_string()));
912 }
913 if let Some(ids) = self.event_ids {
914 params.push(("eventIds", ids.join(",")));
915 }
916 if let Some(bookmakers) = self.bookmakers {
917 params.push(("bookmakers", bookmakers.join(",")));
918 }
919 if let Some(time) = self.commence_time_from {
920 params.push(("commenceTimeFrom", fmt_dt(&time)));
921 }
922 if let Some(time) = self.commence_time_to {
923 params.push(("commenceTimeTo", fmt_dt(&time)));
924 }
925
926 let url = self
927 .client
928 .build_url(&format!("/v4/historical/sports/{}/odds", self.sport), ¶ms)?;
929 self.client.get(url).await
930 }
931}
932
933#[derive(Debug)]
935pub struct GetHistoricalEventsRequest<'a> {
936 client: &'a TheOddsApiClient,
937 sport: String,
938 date: Option<DateTime<Utc>>,
939 date_format: Option<DateFormat>,
940 event_ids: Option<Vec<String>>,
941 commence_time_from: Option<DateTime<Utc>>,
942 commence_time_to: Option<DateTime<Utc>>,
943}
944
945impl<'a> GetHistoricalEventsRequest<'a> {
946 fn new(client: &'a TheOddsApiClient, sport: String) -> Self {
947 Self {
948 client,
949 sport,
950 date: None,
951 date_format: None,
952 event_ids: None,
953 commence_time_from: None,
954 commence_time_to: None,
955 }
956 }
957
958 pub fn date(mut self, date: DateTime<Utc>) -> Self {
960 self.date = Some(date);
961 self
962 }
963
964 pub fn date_format(mut self, format: DateFormat) -> Self {
966 self.date_format = Some(format);
967 self
968 }
969
970 pub fn event_ids(mut self, ids: impl IntoIterator<Item = impl Into<String>>) -> Self {
972 self.event_ids = Some(ids.into_iter().map(Into::into).collect());
973 self
974 }
975
976 pub fn commence_time_from(mut self, time: DateTime<Utc>) -> Self {
978 self.commence_time_from = Some(time);
979 self
980 }
981
982 pub fn commence_time_to(mut self, time: DateTime<Utc>) -> Self {
984 self.commence_time_to = Some(time);
985 self
986 }
987
988 pub async fn send(self) -> Result<Response<HistoricalResponse<Vec<Event>>>> {
990 let date = self.date.ok_or(Error::MissingParameter("date"))?;
991
992 let mut params = vec![("date", fmt_dt(&date))];
993
994 if let Some(fmt) = self.date_format {
995 params.push(("dateFormat", fmt.to_string()));
996 }
997 if let Some(ids) = self.event_ids {
998 params.push(("eventIds", ids.join(",")));
999 }
1000 if let Some(time) = self.commence_time_from {
1001 params.push(("commenceTimeFrom", fmt_dt(&time)));
1002 }
1003 if let Some(time) = self.commence_time_to {
1004 params.push(("commenceTimeTo", fmt_dt(&time)));
1005 }
1006
1007 let url = self.client.build_url(
1008 &format!("/v4/historical/sports/{}/events", self.sport),
1009 ¶ms,
1010 )?;
1011 self.client.get(url).await
1012 }
1013}
1014
1015#[derive(Debug)]
1017pub struct GetHistoricalEventOddsRequest<'a> {
1018 client: &'a TheOddsApiClient,
1019 sport: String,
1020 event_id: String,
1021 date: Option<DateTime<Utc>>,
1022 regions: Vec<Region>,
1023 markets: Option<Vec<Market>>,
1024 date_format: Option<DateFormat>,
1025 odds_format: Option<OddsFormat>,
1026 include_multipliers: Option<bool>,
1027}
1028
1029impl<'a> GetHistoricalEventOddsRequest<'a> {
1030 fn new(client: &'a TheOddsApiClient, sport: String, event_id: String) -> Self {
1031 Self {
1032 client,
1033 sport,
1034 event_id,
1035 date: None,
1036 regions: Vec::new(),
1037 markets: None,
1038 date_format: None,
1039 odds_format: None,
1040 include_multipliers: None,
1041 }
1042 }
1043
1044 pub fn date(mut self, date: DateTime<Utc>) -> Self {
1046 self.date = Some(date);
1047 self
1048 }
1049
1050 pub fn regions(mut self, regions: &[Region]) -> Self {
1052 self.regions = regions.to_vec();
1053 self
1054 }
1055
1056 pub fn region(mut self, region: Region) -> Self {
1058 self.regions.push(region);
1059 self
1060 }
1061
1062 pub fn markets(mut self, markets: &[Market]) -> Self {
1064 self.markets = Some(markets.to_vec());
1065 self
1066 }
1067
1068 pub fn date_format(mut self, format: DateFormat) -> Self {
1070 self.date_format = Some(format);
1071 self
1072 }
1073
1074 pub fn odds_format(mut self, format: OddsFormat) -> Self {
1076 self.odds_format = Some(format);
1077 self
1078 }
1079
1080 pub fn include_multipliers(mut self, include: bool) -> Self {
1082 self.include_multipliers = Some(include);
1083 self
1084 }
1085
1086 pub async fn send(self) -> Result<Response<HistoricalResponse<EventOdds>>> {
1088 let date = self.date.ok_or(Error::MissingParameter("date"))?;
1089 if self.regions.is_empty() {
1090 return Err(Error::MissingParameter("regions"));
1091 }
1092
1093 let mut params = vec![
1094 ("date", fmt_dt(&date)),
1095 ("regions", format_csv(&self.regions)),
1096 ];
1097
1098 if let Some(markets) = self.markets {
1099 params.push(("markets", format_csv(&markets)));
1100 }
1101 if let Some(fmt) = self.date_format {
1102 params.push(("dateFormat", fmt.to_string()));
1103 }
1104 if let Some(fmt) = self.odds_format {
1105 params.push(("oddsFormat", fmt.to_string()));
1106 }
1107 if let Some(true) = self.include_multipliers {
1108 params.push(("includeMultipliers", "true".to_string()));
1109 }
1110
1111 let url = self.client.build_url(
1112 &format!(
1113 "/v4/historical/sports/{}/events/{}/odds",
1114 self.sport, self.event_id
1115 ),
1116 ¶ms,
1117 )?;
1118 self.client.get(url).await
1119 }
1120}