1use crate::error::{Error, Result};
4use crate::models::*;
5use crate::types::*;
6use chrono::{DateTime, Utc};
7use reqwest::Client;
8use url::Url;
9
10const DEFAULT_BASE_URL: &str = "https://api.the-odds-api.com";
11const IPV6_BASE_URL: &str = "https://ipv6-api.the-odds-api.com";
12
13#[derive(Debug, Clone)]
15pub struct TheOddsApiClientBuilder {
16 api_key: String,
17 base_url: String,
18 client: Option<Client>,
19}
20
21impl TheOddsApiClientBuilder {
22 pub fn new(api_key: impl Into<String>) -> Self {
24 Self {
25 api_key: api_key.into(),
26 base_url: DEFAULT_BASE_URL.to_string(),
27 client: None,
28 }
29 }
30
31 pub fn base_url(mut self, url: impl Into<String>) -> Self {
33 self.base_url = url.into();
34 self
35 }
36
37 pub fn use_ipv6(mut self) -> Self {
39 self.base_url = IPV6_BASE_URL.to_string();
40 self
41 }
42
43 pub fn client(mut self, client: Client) -> Self {
45 self.client = Some(client);
46 self
47 }
48
49 pub fn build(self) -> TheOddsApiClient {
51 TheOddsApiClient {
52 api_key: self.api_key,
53 base_url: self.base_url,
54 client: self.client.unwrap_or_default(),
55 }
56 }
57}
58
59#[derive(Debug, Clone)]
61pub struct TheOddsApiClient {
62 api_key: String,
63 base_url: String,
64 client: Client,
65}
66
67impl TheOddsApiClient {
68 pub fn new(api_key: impl Into<String>) -> Self {
70 TheOddsApiClientBuilder::new(api_key).build()
71 }
72
73 pub fn builder(api_key: impl Into<String>) -> TheOddsApiClientBuilder {
75 TheOddsApiClientBuilder::new(api_key)
76 }
77
78 fn build_url(&self, path: &str, params: &[(&str, String)]) -> Result<Url> {
80 let mut url = Url::parse(&format!("{}{}", self.base_url, path))?;
81 {
82 let mut query = url.query_pairs_mut();
83 query.append_pair("apiKey", &self.api_key);
84 for (key, value) in params {
85 if !value.is_empty() {
86 query.append_pair(key, value);
87 }
88 }
89 }
90 Ok(url)
91 }
92
93 async fn get<T: serde::de::DeserializeOwned>(&self, url: Url) -> Result<Response<T>> {
95 let response = self.client.get(url).send().await?;
96 let usage = UsageInfo::from_headers(response.headers());
97 let status = response.status();
98
99 if status.is_success() {
100 let data = response.json().await?;
101 Ok(Response::new(data, usage))
102 } else if status == reqwest::StatusCode::UNAUTHORIZED {
103 Err(Error::Unauthorized)
104 } else if status == reqwest::StatusCode::TOO_MANY_REQUESTS {
105 Err(Error::RateLimited {
106 requests_remaining: usage.requests_remaining,
107 })
108 } else {
109 let message = response
110 .text()
111 .await
112 .unwrap_or_else(|_| "Unknown error".to_string());
113 Err(Error::Api {
114 status: status.as_u16(),
115 message,
116 })
117 }
118 }
119
120 pub async fn get_sports(&self) -> Result<Response<Vec<Sport>>> {
141 let url = self.build_url("/v4/sports", &[])?;
142 self.get(url).await
143 }
144
145 pub async fn get_all_sports(&self) -> Result<Response<Vec<Sport>>> {
149 let url = self.build_url("/v4/sports", &[("all", "true".to_string())])?;
150 self.get(url).await
151 }
152
153 pub fn get_events(&self, sport: impl Into<String>) -> GetEventsRequest<'_> {
161 GetEventsRequest::new(self, sport.into())
162 }
163
164 pub fn get_odds(&self, sport: impl Into<String>) -> GetOddsRequest<'_> {
189 GetOddsRequest::new(self, sport.into())
190 }
191
192 pub fn get_upcoming_odds(&self) -> GetOddsRequest<'_> {
194 GetOddsRequest::new(self, "upcoming".to_string())
195 }
196
197 pub fn get_scores(&self, sport: impl Into<String>) -> GetScoresRequest<'_> {
206 GetScoresRequest::new(self, sport.into())
207 }
208
209 pub fn get_event_odds(
217 &self,
218 sport: impl Into<String>,
219 event_id: impl Into<String>,
220 ) -> GetEventOddsRequest<'_> {
221 GetEventOddsRequest::new(self, sport.into(), event_id.into())
222 }
223
224 pub fn get_event_markets(
233 &self,
234 sport: impl Into<String>,
235 event_id: impl Into<String>,
236 ) -> GetEventMarketsRequest<'_> {
237 GetEventMarketsRequest::new(self, sport.into(), event_id.into())
238 }
239
240 pub async fn get_participants(
249 &self,
250 sport: impl Into<String>,
251 ) -> Result<Response<Vec<Participant>>> {
252 let url = self.build_url(&format!("/v4/sports/{}/participants", sport.into()), &[])?;
253 self.get(url).await
254 }
255
256 pub fn get_historical_odds(&self, sport: impl Into<String>) -> GetHistoricalOddsRequest<'_> {
265 GetHistoricalOddsRequest::new(self, sport.into())
266 }
267
268 pub fn get_historical_events(&self, sport: impl Into<String>) -> GetHistoricalEventsRequest<'_> {
273 GetHistoricalEventsRequest::new(self, sport.into())
274 }
275
276 pub fn get_historical_event_odds(
278 &self,
279 sport: impl Into<String>,
280 event_id: impl Into<String>,
281 ) -> GetHistoricalEventOddsRequest<'_> {
282 GetHistoricalEventOddsRequest::new(self, sport.into(), event_id.into())
283 }
284}
285
286#[derive(Debug)]
292pub struct GetEventsRequest<'a> {
293 client: &'a TheOddsApiClient,
294 sport: String,
295 date_format: Option<DateFormat>,
296 event_ids: Option<Vec<String>>,
297 commence_time_from: Option<DateTime<Utc>>,
298 commence_time_to: Option<DateTime<Utc>>,
299}
300
301impl<'a> GetEventsRequest<'a> {
302 fn new(client: &'a TheOddsApiClient, sport: String) -> Self {
303 Self {
304 client,
305 sport,
306 date_format: None,
307 event_ids: None,
308 commence_time_from: None,
309 commence_time_to: None,
310 }
311 }
312
313 pub fn date_format(mut self, format: DateFormat) -> Self {
315 self.date_format = Some(format);
316 self
317 }
318
319 pub fn event_ids(mut self, ids: impl IntoIterator<Item = impl Into<String>>) -> Self {
321 self.event_ids = Some(ids.into_iter().map(Into::into).collect());
322 self
323 }
324
325 pub fn commence_time_from(mut self, time: DateTime<Utc>) -> Self {
327 self.commence_time_from = Some(time);
328 self
329 }
330
331 pub fn commence_time_to(mut self, time: DateTime<Utc>) -> Self {
333 self.commence_time_to = Some(time);
334 self
335 }
336
337 pub async fn send(self) -> Result<Response<Vec<Event>>> {
339 let mut params = Vec::new();
340
341 if let Some(fmt) = self.date_format {
342 params.push(("dateFormat", fmt.to_string()));
343 }
344 if let Some(ids) = self.event_ids {
345 params.push(("eventIds", ids.join(",")));
346 }
347 if let Some(time) = self.commence_time_from {
348 params.push(("commenceTimeFrom", time.to_rfc3339()));
349 }
350 if let Some(time) = self.commence_time_to {
351 params.push(("commenceTimeTo", time.to_rfc3339()));
352 }
353
354 let url = self
355 .client
356 .build_url(&format!("/v4/sports/{}/events", self.sport), ¶ms)?;
357 self.client.get(url).await
358 }
359}
360
361#[derive(Debug)]
363pub struct GetOddsRequest<'a> {
364 client: &'a TheOddsApiClient,
365 sport: String,
366 regions: Vec<Region>,
367 markets: Option<Vec<Market>>,
368 date_format: Option<DateFormat>,
369 odds_format: Option<OddsFormat>,
370 event_ids: Option<Vec<String>>,
371 bookmakers: Option<Vec<String>>,
372 commence_time_from: Option<DateTime<Utc>>,
373 commence_time_to: Option<DateTime<Utc>>,
374 include_links: Option<bool>,
375 include_sids: Option<bool>,
376 include_bet_limits: Option<bool>,
377}
378
379impl<'a> GetOddsRequest<'a> {
380 fn new(client: &'a TheOddsApiClient, sport: String) -> Self {
381 Self {
382 client,
383 sport,
384 regions: Vec::new(),
385 markets: None,
386 date_format: None,
387 odds_format: None,
388 event_ids: None,
389 bookmakers: None,
390 commence_time_from: None,
391 commence_time_to: None,
392 include_links: None,
393 include_sids: None,
394 include_bet_limits: None,
395 }
396 }
397
398 pub fn regions(mut self, regions: &[Region]) -> Self {
400 self.regions = regions.to_vec();
401 self
402 }
403
404 pub fn region(mut self, region: Region) -> Self {
406 self.regions.push(region);
407 self
408 }
409
410 pub fn markets(mut self, markets: &[Market]) -> Self {
412 self.markets = Some(markets.to_vec());
413 self
414 }
415
416 pub fn market(mut self, market: Market) -> Self {
418 self.markets.get_or_insert_with(Vec::new).push(market);
419 self
420 }
421
422 pub fn date_format(mut self, format: DateFormat) -> Self {
424 self.date_format = Some(format);
425 self
426 }
427
428 pub fn odds_format(mut self, format: OddsFormat) -> Self {
430 self.odds_format = Some(format);
431 self
432 }
433
434 pub fn event_ids(mut self, ids: impl IntoIterator<Item = impl Into<String>>) -> Self {
436 self.event_ids = Some(ids.into_iter().map(Into::into).collect());
437 self
438 }
439
440 pub fn bookmakers(mut self, bookmakers: impl IntoIterator<Item = impl Into<String>>) -> Self {
442 self.bookmakers = Some(bookmakers.into_iter().map(Into::into).collect());
443 self
444 }
445
446 pub fn commence_time_from(mut self, time: DateTime<Utc>) -> Self {
448 self.commence_time_from = Some(time);
449 self
450 }
451
452 pub fn commence_time_to(mut self, time: DateTime<Utc>) -> Self {
454 self.commence_time_to = Some(time);
455 self
456 }
457
458 pub fn include_links(mut self, include: bool) -> Self {
460 self.include_links = Some(include);
461 self
462 }
463
464 pub fn include_sids(mut self, include: bool) -> Self {
466 self.include_sids = Some(include);
467 self
468 }
469
470 pub fn include_bet_limits(mut self, include: bool) -> Self {
472 self.include_bet_limits = Some(include);
473 self
474 }
475
476 pub async fn send(self) -> Result<Response<Vec<EventOdds>>> {
478 if self.regions.is_empty() {
479 return Err(Error::MissingParameter("regions"));
480 }
481
482 let mut params = vec![("regions", format_csv(&self.regions))];
483
484 if let Some(markets) = self.markets {
485 params.push(("markets", format_csv(&markets)));
486 }
487 if let Some(fmt) = self.date_format {
488 params.push(("dateFormat", fmt.to_string()));
489 }
490 if let Some(fmt) = self.odds_format {
491 params.push(("oddsFormat", fmt.to_string()));
492 }
493 if let Some(ids) = self.event_ids {
494 params.push(("eventIds", ids.join(",")));
495 }
496 if let Some(bookmakers) = self.bookmakers {
497 params.push(("bookmakers", bookmakers.join(",")));
498 }
499 if let Some(time) = self.commence_time_from {
500 params.push(("commenceTimeFrom", time.to_rfc3339()));
501 }
502 if let Some(time) = self.commence_time_to {
503 params.push(("commenceTimeTo", time.to_rfc3339()));
504 }
505 if let Some(true) = self.include_links {
506 params.push(("includeLinks", "true".to_string()));
507 }
508 if let Some(true) = self.include_sids {
509 params.push(("includeSids", "true".to_string()));
510 }
511 if let Some(true) = self.include_bet_limits {
512 params.push(("includeBetLimits", "true".to_string()));
513 }
514
515 let url = self
516 .client
517 .build_url(&format!("/v4/sports/{}/odds", self.sport), ¶ms)?;
518 self.client.get(url).await
519 }
520}
521
522#[derive(Debug)]
524pub struct GetScoresRequest<'a> {
525 client: &'a TheOddsApiClient,
526 sport: String,
527 days_from: Option<u8>,
528 date_format: Option<DateFormat>,
529 event_ids: Option<Vec<String>>,
530}
531
532impl<'a> GetScoresRequest<'a> {
533 fn new(client: &'a TheOddsApiClient, sport: String) -> Self {
534 Self {
535 client,
536 sport,
537 days_from: None,
538 date_format: None,
539 event_ids: None,
540 }
541 }
542
543 pub fn days_from(mut self, days: u8) -> Self {
545 self.days_from = Some(days.clamp(1, 3));
546 self
547 }
548
549 pub fn date_format(mut self, format: DateFormat) -> Self {
551 self.date_format = Some(format);
552 self
553 }
554
555 pub fn event_ids(mut self, ids: impl IntoIterator<Item = impl Into<String>>) -> Self {
557 self.event_ids = Some(ids.into_iter().map(Into::into).collect());
558 self
559 }
560
561 pub async fn send(self) -> Result<Response<Vec<EventScore>>> {
563 let mut params = Vec::new();
564
565 if let Some(days) = self.days_from {
566 params.push(("daysFrom", days.to_string()));
567 }
568 if let Some(fmt) = self.date_format {
569 params.push(("dateFormat", fmt.to_string()));
570 }
571 if let Some(ids) = self.event_ids {
572 params.push(("eventIds", ids.join(",")));
573 }
574
575 let url = self
576 .client
577 .build_url(&format!("/v4/sports/{}/scores", self.sport), ¶ms)?;
578 self.client.get(url).await
579 }
580}
581
582#[derive(Debug)]
584pub struct GetEventOddsRequest<'a> {
585 client: &'a TheOddsApiClient,
586 sport: String,
587 event_id: String,
588 regions: Vec<Region>,
589 markets: Option<Vec<Market>>,
590 date_format: Option<DateFormat>,
591 odds_format: Option<OddsFormat>,
592 bookmakers: Option<Vec<String>>,
593 include_links: Option<bool>,
594 include_sids: Option<bool>,
595 include_multipliers: Option<bool>,
596}
597
598impl<'a> GetEventOddsRequest<'a> {
599 fn new(client: &'a TheOddsApiClient, sport: String, event_id: String) -> Self {
600 Self {
601 client,
602 sport,
603 event_id,
604 regions: Vec::new(),
605 markets: None,
606 date_format: None,
607 odds_format: None,
608 bookmakers: None,
609 include_links: None,
610 include_sids: None,
611 include_multipliers: None,
612 }
613 }
614
615 pub fn regions(mut self, regions: &[Region]) -> Self {
617 self.regions = regions.to_vec();
618 self
619 }
620
621 pub fn region(mut self, region: Region) -> Self {
623 self.regions.push(region);
624 self
625 }
626
627 pub fn markets(mut self, markets: &[Market]) -> Self {
629 self.markets = Some(markets.to_vec());
630 self
631 }
632
633 pub fn market(mut self, market: Market) -> Self {
635 self.markets.get_or_insert_with(Vec::new).push(market);
636 self
637 }
638
639 pub fn custom_market(mut self, key: impl Into<String>) -> Self {
641 self.markets
642 .get_or_insert_with(Vec::new)
643 .push(Market::Custom(key.into()));
644 self
645 }
646
647 pub fn date_format(mut self, format: DateFormat) -> Self {
649 self.date_format = Some(format);
650 self
651 }
652
653 pub fn odds_format(mut self, format: OddsFormat) -> Self {
655 self.odds_format = Some(format);
656 self
657 }
658
659 pub fn bookmakers(mut self, bookmakers: impl IntoIterator<Item = impl Into<String>>) -> Self {
661 self.bookmakers = Some(bookmakers.into_iter().map(Into::into).collect());
662 self
663 }
664
665 pub fn include_links(mut self, include: bool) -> Self {
667 self.include_links = Some(include);
668 self
669 }
670
671 pub fn include_sids(mut self, include: bool) -> Self {
673 self.include_sids = Some(include);
674 self
675 }
676
677 pub fn include_multipliers(mut self, include: bool) -> Self {
679 self.include_multipliers = Some(include);
680 self
681 }
682
683 pub async fn send(self) -> Result<Response<EventOdds>> {
685 if self.regions.is_empty() {
686 return Err(Error::MissingParameter("regions"));
687 }
688
689 let mut params = vec![("regions", format_csv(&self.regions))];
690
691 if let Some(markets) = self.markets {
692 params.push(("markets", format_csv(&markets)));
693 }
694 if let Some(fmt) = self.date_format {
695 params.push(("dateFormat", fmt.to_string()));
696 }
697 if let Some(fmt) = self.odds_format {
698 params.push(("oddsFormat", fmt.to_string()));
699 }
700 if let Some(bookmakers) = self.bookmakers {
701 params.push(("bookmakers", bookmakers.join(",")));
702 }
703 if let Some(true) = self.include_links {
704 params.push(("includeLinks", "true".to_string()));
705 }
706 if let Some(true) = self.include_sids {
707 params.push(("includeSids", "true".to_string()));
708 }
709 if let Some(true) = self.include_multipliers {
710 params.push(("includeMultipliers", "true".to_string()));
711 }
712
713 let url = self.client.build_url(
714 &format!("/v4/sports/{}/events/{}/odds", self.sport, self.event_id),
715 ¶ms,
716 )?;
717 self.client.get(url).await
718 }
719}
720
721#[derive(Debug)]
723pub struct GetEventMarketsRequest<'a> {
724 client: &'a TheOddsApiClient,
725 sport: String,
726 event_id: String,
727 regions: Vec<Region>,
728 bookmakers: Option<Vec<String>>,
729 date_format: Option<DateFormat>,
730}
731
732impl<'a> GetEventMarketsRequest<'a> {
733 fn new(client: &'a TheOddsApiClient, sport: String, event_id: String) -> Self {
734 Self {
735 client,
736 sport,
737 event_id,
738 regions: Vec::new(),
739 bookmakers: None,
740 date_format: None,
741 }
742 }
743
744 pub fn regions(mut self, regions: &[Region]) -> Self {
746 self.regions = regions.to_vec();
747 self
748 }
749
750 pub fn region(mut self, region: Region) -> Self {
752 self.regions.push(region);
753 self
754 }
755
756 pub fn bookmakers(mut self, bookmakers: impl IntoIterator<Item = impl Into<String>>) -> Self {
758 self.bookmakers = Some(bookmakers.into_iter().map(Into::into).collect());
759 self
760 }
761
762 pub fn date_format(mut self, format: DateFormat) -> Self {
764 self.date_format = Some(format);
765 self
766 }
767
768 pub async fn send(self) -> Result<Response<EventMarkets>> {
770 if self.regions.is_empty() {
771 return Err(Error::MissingParameter("regions"));
772 }
773
774 let mut params = vec![("regions", format_csv(&self.regions))];
775
776 if let Some(bookmakers) = self.bookmakers {
777 params.push(("bookmakers", bookmakers.join(",")));
778 }
779 if let Some(fmt) = self.date_format {
780 params.push(("dateFormat", fmt.to_string()));
781 }
782
783 let url = self.client.build_url(
784 &format!(
785 "/v4/sports/{}/events/{}/markets",
786 self.sport, self.event_id
787 ),
788 ¶ms,
789 )?;
790 self.client.get(url).await
791 }
792}
793
794#[derive(Debug)]
796pub struct GetHistoricalOddsRequest<'a> {
797 client: &'a TheOddsApiClient,
798 sport: String,
799 date: Option<DateTime<Utc>>,
800 regions: Vec<Region>,
801 markets: Option<Vec<Market>>,
802 date_format: Option<DateFormat>,
803 odds_format: Option<OddsFormat>,
804 event_ids: Option<Vec<String>>,
805 bookmakers: Option<Vec<String>>,
806 commence_time_from: Option<DateTime<Utc>>,
807 commence_time_to: Option<DateTime<Utc>>,
808}
809
810impl<'a> GetHistoricalOddsRequest<'a> {
811 fn new(client: &'a TheOddsApiClient, sport: String) -> Self {
812 Self {
813 client,
814 sport,
815 date: None,
816 regions: Vec::new(),
817 markets: None,
818 date_format: None,
819 odds_format: None,
820 event_ids: None,
821 bookmakers: None,
822 commence_time_from: None,
823 commence_time_to: None,
824 }
825 }
826
827 pub fn date(mut self, date: DateTime<Utc>) -> Self {
829 self.date = Some(date);
830 self
831 }
832
833 pub fn regions(mut self, regions: &[Region]) -> Self {
835 self.regions = regions.to_vec();
836 self
837 }
838
839 pub fn region(mut self, region: Region) -> Self {
841 self.regions.push(region);
842 self
843 }
844
845 pub fn markets(mut self, markets: &[Market]) -> Self {
847 self.markets = Some(markets.to_vec());
848 self
849 }
850
851 pub fn date_format(mut self, format: DateFormat) -> Self {
853 self.date_format = Some(format);
854 self
855 }
856
857 pub fn odds_format(mut self, format: OddsFormat) -> Self {
859 self.odds_format = Some(format);
860 self
861 }
862
863 pub fn event_ids(mut self, ids: impl IntoIterator<Item = impl Into<String>>) -> Self {
865 self.event_ids = Some(ids.into_iter().map(Into::into).collect());
866 self
867 }
868
869 pub fn bookmakers(mut self, bookmakers: impl IntoIterator<Item = impl Into<String>>) -> Self {
871 self.bookmakers = Some(bookmakers.into_iter().map(Into::into).collect());
872 self
873 }
874
875 pub fn commence_time_from(mut self, time: DateTime<Utc>) -> Self {
877 self.commence_time_from = Some(time);
878 self
879 }
880
881 pub fn commence_time_to(mut self, time: DateTime<Utc>) -> Self {
883 self.commence_time_to = Some(time);
884 self
885 }
886
887 pub async fn send(self) -> Result<Response<HistoricalResponse<Vec<EventOdds>>>> {
889 let date = self.date.ok_or(Error::MissingParameter("date"))?;
890 if self.regions.is_empty() {
891 return Err(Error::MissingParameter("regions"));
892 }
893
894 let mut params = vec![
895 ("date", date.to_rfc3339()),
896 ("regions", format_csv(&self.regions)),
897 ];
898
899 if let Some(markets) = self.markets {
900 params.push(("markets", format_csv(&markets)));
901 }
902 if let Some(fmt) = self.date_format {
903 params.push(("dateFormat", fmt.to_string()));
904 }
905 if let Some(fmt) = self.odds_format {
906 params.push(("oddsFormat", fmt.to_string()));
907 }
908 if let Some(ids) = self.event_ids {
909 params.push(("eventIds", ids.join(",")));
910 }
911 if let Some(bookmakers) = self.bookmakers {
912 params.push(("bookmakers", bookmakers.join(",")));
913 }
914 if let Some(time) = self.commence_time_from {
915 params.push(("commenceTimeFrom", time.to_rfc3339()));
916 }
917 if let Some(time) = self.commence_time_to {
918 params.push(("commenceTimeTo", time.to_rfc3339()));
919 }
920
921 let url = self
922 .client
923 .build_url(&format!("/v4/historical/sports/{}/odds", self.sport), ¶ms)?;
924 self.client.get(url).await
925 }
926}
927
928#[derive(Debug)]
930pub struct GetHistoricalEventsRequest<'a> {
931 client: &'a TheOddsApiClient,
932 sport: String,
933 date: Option<DateTime<Utc>>,
934 date_format: Option<DateFormat>,
935 event_ids: Option<Vec<String>>,
936 commence_time_from: Option<DateTime<Utc>>,
937 commence_time_to: Option<DateTime<Utc>>,
938}
939
940impl<'a> GetHistoricalEventsRequest<'a> {
941 fn new(client: &'a TheOddsApiClient, sport: String) -> Self {
942 Self {
943 client,
944 sport,
945 date: None,
946 date_format: None,
947 event_ids: None,
948 commence_time_from: None,
949 commence_time_to: None,
950 }
951 }
952
953 pub fn date(mut self, date: DateTime<Utc>) -> Self {
955 self.date = Some(date);
956 self
957 }
958
959 pub fn date_format(mut self, format: DateFormat) -> Self {
961 self.date_format = Some(format);
962 self
963 }
964
965 pub fn event_ids(mut self, ids: impl IntoIterator<Item = impl Into<String>>) -> Self {
967 self.event_ids = Some(ids.into_iter().map(Into::into).collect());
968 self
969 }
970
971 pub fn commence_time_from(mut self, time: DateTime<Utc>) -> Self {
973 self.commence_time_from = Some(time);
974 self
975 }
976
977 pub fn commence_time_to(mut self, time: DateTime<Utc>) -> Self {
979 self.commence_time_to = Some(time);
980 self
981 }
982
983 pub async fn send(self) -> Result<Response<HistoricalResponse<Vec<Event>>>> {
985 let date = self.date.ok_or(Error::MissingParameter("date"))?;
986
987 let mut params = vec![("date", date.to_rfc3339())];
988
989 if let Some(fmt) = self.date_format {
990 params.push(("dateFormat", fmt.to_string()));
991 }
992 if let Some(ids) = self.event_ids {
993 params.push(("eventIds", ids.join(",")));
994 }
995 if let Some(time) = self.commence_time_from {
996 params.push(("commenceTimeFrom", time.to_rfc3339()));
997 }
998 if let Some(time) = self.commence_time_to {
999 params.push(("commenceTimeTo", time.to_rfc3339()));
1000 }
1001
1002 let url = self.client.build_url(
1003 &format!("/v4/historical/sports/{}/events", self.sport),
1004 ¶ms,
1005 )?;
1006 self.client.get(url).await
1007 }
1008}
1009
1010#[derive(Debug)]
1012pub struct GetHistoricalEventOddsRequest<'a> {
1013 client: &'a TheOddsApiClient,
1014 sport: String,
1015 event_id: String,
1016 date: Option<DateTime<Utc>>,
1017 regions: Vec<Region>,
1018 markets: Option<Vec<Market>>,
1019 date_format: Option<DateFormat>,
1020 odds_format: Option<OddsFormat>,
1021 include_multipliers: Option<bool>,
1022}
1023
1024impl<'a> GetHistoricalEventOddsRequest<'a> {
1025 fn new(client: &'a TheOddsApiClient, sport: String, event_id: String) -> Self {
1026 Self {
1027 client,
1028 sport,
1029 event_id,
1030 date: None,
1031 regions: Vec::new(),
1032 markets: None,
1033 date_format: None,
1034 odds_format: None,
1035 include_multipliers: None,
1036 }
1037 }
1038
1039 pub fn date(mut self, date: DateTime<Utc>) -> Self {
1041 self.date = Some(date);
1042 self
1043 }
1044
1045 pub fn regions(mut self, regions: &[Region]) -> Self {
1047 self.regions = regions.to_vec();
1048 self
1049 }
1050
1051 pub fn region(mut self, region: Region) -> Self {
1053 self.regions.push(region);
1054 self
1055 }
1056
1057 pub fn markets(mut self, markets: &[Market]) -> Self {
1059 self.markets = Some(markets.to_vec());
1060 self
1061 }
1062
1063 pub fn date_format(mut self, format: DateFormat) -> Self {
1065 self.date_format = Some(format);
1066 self
1067 }
1068
1069 pub fn odds_format(mut self, format: OddsFormat) -> Self {
1071 self.odds_format = Some(format);
1072 self
1073 }
1074
1075 pub fn include_multipliers(mut self, include: bool) -> Self {
1077 self.include_multipliers = Some(include);
1078 self
1079 }
1080
1081 pub async fn send(self) -> Result<Response<HistoricalResponse<EventOdds>>> {
1083 let date = self.date.ok_or(Error::MissingParameter("date"))?;
1084 if self.regions.is_empty() {
1085 return Err(Error::MissingParameter("regions"));
1086 }
1087
1088 let mut params = vec![
1089 ("date", date.to_rfc3339()),
1090 ("regions", format_csv(&self.regions)),
1091 ];
1092
1093 if let Some(markets) = self.markets {
1094 params.push(("markets", format_csv(&markets)));
1095 }
1096 if let Some(fmt) = self.date_format {
1097 params.push(("dateFormat", fmt.to_string()));
1098 }
1099 if let Some(fmt) = self.odds_format {
1100 params.push(("oddsFormat", fmt.to_string()));
1101 }
1102 if let Some(true) = self.include_multipliers {
1103 params.push(("includeMultipliers", "true".to_string()));
1104 }
1105
1106 let url = self.client.build_url(
1107 &format!(
1108 "/v4/historical/sports/{}/events/{}/odds",
1109 self.sport, self.event_id
1110 ),
1111 ¶ms,
1112 )?;
1113 self.client.get(url).await
1114 }
1115}