rustoa/
lib.rs

1//! # RusTOA
2//!
3//! `rustoa` is a crate you can use to access The Orange Alliance API.
4//! This crate makes it easy to access the official First Tech Challenge API
5//! and use it in your Rust projects.
6
7use reqwest::blocking::Response;
8use reqwest::header::CONTENT_TYPE;
9use std::collections::hash_map::RandomState;
10use std::collections::HashMap;
11
12/// The main RusTOA client.
13///
14/// You can use the [Client](struct.Client.html) to get the API version
15/// and create a [Team](struct.Team.html) object.
16#[derive(Clone, Debug)]
17pub struct Client {
18    api_key: String,
19    application_name: String,
20}
21
22impl Client {
23    #[doc(hidden)]
24    pub fn request(&self, target: &str) -> Result<Response, Box<dyn std::error::Error>> {
25        let url = format!("https://theorangealliance.org/api{}", target);
26        let client = reqwest::blocking::Client::new();
27        let resp = client
28            .get(&url[..])
29            .header("X-TOA-Key", &self.api_key)
30            .header("X-Application-Origin", &self.application_name)
31            .header(CONTENT_TYPE, "application/json")
32            .send()?;
33
34        Ok(resp)
35    }
36    #[doc(hidden)]
37    pub fn api_key(&self) -> &str {
38        &self.api_key[..]
39    }
40    #[doc(hidden)]
41    pub fn application_name(&self) -> &str {
42        &self.application_name[..]
43    }
44
45    /// Create a new Client object.
46    /// # Arguments
47    ///
48    /// * `api_key` - Your Orange Alliance API key as a `String`.
49    ///
50    /// It returns a [Client](struct.Client.html) object.
51    pub fn new(api_key: &str) -> Client {
52        Client {
53            api_key: api_key.to_string(),
54            application_name: "rustoa".to_string(),
55        }
56    }
57
58    /// Get the version of The Orange Alliance API that this crate is using.
59    /// This method takes no arguments and returns the version as a String.
60    ///
61    /// # Panics
62    /// This method can panic in three ways:
63    /// - The HTTP request to the API fails. This can be because the API is either down or you are
64    /// being ratelimited.
65    /// - Serde cannot properly deserialize the JSON data in the response. This happens because the
66    /// API has sent invalid JSON.
67    /// - The HashMap does not have the needed keys to process the data. This happens because
68    /// the request was made to the wrong target or the API has sent back an error in JSON form.
69    pub fn api_version(&self) -> String {
70        let resp = match self.request("/") {
71            Ok(resp) => resp,
72            Err(e) => {
73                panic!("Something went wrong: {}", e);
74            }
75        };
76
77        let map = match resp.json::<HashMap<String, String>>() {
78            Ok(m) => m,
79            Err(e) => panic!("Something went wrong: {}", e),
80        };
81
82        match map.get("version") {
83            Some(vers) => vers.to_string(),
84            None => panic!("Something went wrong with the API."),
85        }
86    }
87    /// This method is used to get an instance of [`Team`](struct.Team.html).
88    /// # Arguments
89    ///
90    /// * `team_number` - The FTC team number as a `u32` integer.
91    ///
92    /// It returns a [Team](struct.Team.html) object with the necessary data.
93    pub fn team(&self, team_number: u32) -> Team {
94        Team::new(team_number, self.clone())
95    }
96}
97
98/// A struct used to access an FTC team.
99///
100/// Do not create this struct yourself. Instead use your [`Client`](struct.Client.html) instance.
101pub struct Team {
102    #[doc(hidden)]
103    pub client: Client,
104    pub team_number: u32,
105}
106
107impl Team {
108    #[doc(hidden)]
109    pub fn new(team_number: u32, client: Client) -> Team {
110        Team {
111            // api_key: client.api_key().to_string(),
112            // application_name: client.application_name().to_string(),
113            client,
114            team_number,
115        }
116    }
117    fn get_wlt(&self) -> HashMap<String, u32, RandomState> {
118        let resp = match self.client.request(&format!("/team/{}/wlt", self.team_number)[..]) {
119            Ok(r) => r,
120            Err(e) => panic!("Something went wrong: {}", e)
121        };
122
123        let map = match resp.json::<Vec<HashMap<String, u32>>>() {
124            Ok(m) => m[0].clone(),
125            Err(e) => panic!("Something went wrong: {}", e)
126        };
127
128        map
129    }
130    /// The total amount of times the team has won a match.
131    ///
132    /// This method takes no arguments.
133    ///
134    /// It returns a `u32` integer.
135    pub fn wins(&self) -> u32 {
136        let map = self.get_wlt();
137
138        match map.get("wins") {
139            Some(w) => w.clone(),
140            None => panic!("Something went wrong with the API."),
141        }
142    }
143    /// The total amount of times the team has lost a match.
144    ///
145    /// This method takes no arguments.
146    ///
147    /// It returns a `u32` integer.
148    pub fn losses(&self) -> u32 {
149        let map = self.get_wlt();
150
151        match map.get("losses") {
152            Some(l) => l.clone(),
153            None => panic!("Something went wrong with the API."),
154        }
155    }
156    /// The amount of times the team has tied a match.
157    ///
158    /// This method takes no arguments.
159    ///
160    /// It returns a `u32` integer.
161    pub fn ties(&self) -> u32 {
162        let map = self.get_wlt();
163
164        match map.get("ties") {
165            Some(t) => t.clone(),
166            None => panic!("Something went wrong with the API."),
167        }
168    }
169
170    /// Basic information of the team.
171    ///
172    /// This method takes no arguments.
173    ///
174    /// It returns a `HashMap<String, String>`.
175    ///
176    /// # Panics
177    ///
178    /// This method can panic in the following ways:
179    /// - The HTTP request was not successful
180    /// - The data received from the API was invalid JSON
181    /// - The data received was in the wrong format
182    pub fn properties(&self) -> HashMap<String, String, RandomState> {
183        let resp = match self
184            .client
185            .request(&format!("/team/{}/", self.team_number)[..])
186        {
187            Ok(resp) => resp,
188            Err(e) => panic!("Something went wrong: {}", e),
189        };
190
191        let map: serde_json::Value = match serde_json::from_str(&*match resp.text() {
192            Ok(text) => text,
193            Err(e) => panic!("Something went wrong: {}", e),
194        }) {
195            Ok(m) => m,
196            Err(e) => panic!("Something went wrong: {}", e),
197        };
198
199        let item = match map.as_array() {
200            Some(n) => n,
201            None => panic!("Something went wrong"),
202        };
203
204        let value = item[0].clone();
205
206        let new = match value.as_object() {
207            Some(m) => m,
208            None => panic!("Something went wrong"),
209        };
210
211        let mut new_map: HashMap<String, String> = HashMap::new();
212
213        for x in new.iter() {
214            let key = x.0.clone();
215            let value = match x.1 {
216                serde_json::Value::String(s) => s.clone(),
217                serde_json::Value::Number(n) => match n.as_u64() {
218                    Some(u) => u.to_string(),
219                    None => panic!("Something went wrong"),
220                },
221                serde_json::Value::Null => "null".to_string(),
222                _ => panic!("Something went wrong"),
223            };
224            let key_orig = key.clone();
225            if key == "last_active".to_string() {
226                let season = Season::value_of(value.clone());
227                let season = format!("{}", season);
228                new_map.insert(key_orig, season);
229                continue;
230            }
231            new_map.insert(key, value);
232        }
233
234        new_map
235    }
236    fn get_season_data(
237        &self,
238        season: Season,
239        query: &str
240    ) -> Result<f64, Box<dyn std::error::Error>> {
241        let season = season.value();
242        let resp = self
243            .client
244            .request(&format!("/team/{}/results/{}", self.team_number, season)[..])?;
245        let map: serde_json::Value = serde_json::from_str(&*resp.text()?)?;
246
247        let arr = match map.as_array() {
248            Some(a) => a,
249            None => panic!("Something went wrong")
250        };
251        let query = query.to_string();
252        let mut i = 0 as f64;
253        for val in arr.iter() {
254            let val = val.clone();
255            let val = &val[&query];
256            let num = match val.as_f64() {
257                Some(n) => n,
258                None => panic!("Something went wrong")
259            };
260            i += num;
261        }
262        i = (i * 100.0).round() / 100.0;
263        Ok(i)
264    }
265
266    /// The amount of times the team has won in a particular season
267    ///
268    /// # Arguments
269    ///
270    /// * [`season: Season`](enum.Season.html) - A rustoa `Season` object.
271    ///
272    /// # Panics
273    ///
274    /// This method will panic if the data sent by the API was in the wrong format.
275    pub fn season_wins(&self, season: Season) -> f64 {
276        let data = match self.get_season_data(season, "wins") {
277            Ok(m) => m,
278            Err(e) => panic!("Something went wrong: {}", e),
279        };
280
281        data
282    }
283
284    /// The amount of times the team has lost in a particular season
285    ///
286    /// # Arguments
287    ///
288    /// * [`season: Season`](enum.Season.html) - A rustoa `Season` object.
289    ///
290    /// # Panics
291    ///
292    /// This method will panic if the data sent by the API was in the wrong format.
293    pub fn season_losses(&self, season: Season) -> f64 {
294        let data = match self.get_season_data(season, "losses") {
295            Ok(m) => m,
296            Err(e) => panic!("Something went wrong: {}", e),
297        };
298
299        data
300    }
301
302    /// The amount of times the team has tied a match in a particular season
303    ///
304    /// # Arguments
305    ///
306    /// * [`season: Season`](enum.Season.html) - A rustoa `Season` object.
307    ///
308    /// # Panics
309    ///
310    /// This method will panic if the data sent by the API was in the wrong format.
311    pub fn season_ties(&self, season: Season) -> f64 {
312        let data = match self.get_season_data(season, "ties") {
313            Ok(m) => m,
314            Err(e) => panic!("Something went wrong: {}", e),
315        };
316
317        data
318    }
319
320    /// OPR stands for Offensive Power Rating.
321    ///
322    /// This is a system that attempts
323    /// to deduce the average point contribution of a team to an alliance.
324    ///
325    /// Penalties are also factored in.
326    ///
327    /// # Arguments
328    ///
329    /// * [`season: Season`](enum.Season.html) - A rustoa `Season` object.
330    ///
331    /// # Panics
332    ///
333    /// This method will panic if the data sent by the API was in the wrong format.
334    pub fn opr(&self, season: Season) -> f64 {
335        let data = match self.get_season_data(season, "opr") {
336            Ok(m) => m,
337            Err(e) => panic!("Something went wrong: {}", e),
338        };
339
340        data
341    }
342
343    /// NP_OPR is the OPR without penalties.
344    ///
345    /// # Arguments
346    ///
347    /// * [`season: Season`](enum.Season.html) - A rustoa `Season` object.
348    ///
349    /// # Panics
350    ///
351    /// This method will panic if the data sent by the API was in the wrong format.
352    pub fn np_opr(&self, season: Season) -> f64 {
353        let data = match self.get_season_data(season, "np_opr") {
354            Ok(m) => m,
355            Err(e) => panic!("Something went wrong: {}", e),
356        };
357
358        data
359    }
360
361    /// Ranking points are the number of points scored by the
362    /// losing alliance in a qualification match.
363    /// If you win the match, then the RP awarded to you is the score of
364    /// your opponent alliance (which lost).
365    /// If you lose the match, then the RP awarded to you is your own alliance’s score.
366    ///
367    /// # Arguments
368    ///
369    /// * [`season: Season`](enum.Season.html) - A rustoa `Season` object.
370    ///
371    /// # Panics
372    ///
373    /// This method will panic if the data sent by the API was in the wrong format.
374    pub fn ranking_points(&self, season: Season) -> f64 {
375        let data = match self.get_season_data(season, "ranking_points") {
376            Ok(m) => m,
377            Err(e) => panic!("Something went wrong: {}", e),
378        };
379
380        data
381    }
382
383    /// Winning teams of a qualifying match each receive 2 QP.
384    /// Losing teams receive 0. If a match ends in a tie,
385    /// all four teams receive 1 QP.
386    ///
387    /// # Arguments
388    ///
389    /// * [`season: Season`](enum.Season.html) - A rustoa `Season` object.
390    ///
391    /// # Panics
392    ///
393    /// This method will panic if the data sent by the API was in the wrong format.
394    pub fn qualifying_points(&self, season: Season) -> f64 {
395        let data = match self.get_season_data(season, "qualifying_points") {
396            Ok(m) => m,
397            Err(e) => panic!("Something went wrong: {}", e),
398        };
399
400        data
401    }
402
403    /// Tiebreaker points are the pre-penalty score of the losing alliance for each match.
404    /// This method returns the total tiebreaker points of a team in one season.
405    ///
406    /// # Arguments
407    ///
408    /// * [`season: Season`](enum.Season.html) - A rustoa `Season` object.
409    ///
410    /// # Panics
411    ///
412    /// This method will panic if the data sent by the API was in the wrong format.
413    pub fn tiebreaker_points(&self, season: Season) -> f64 {
414        let data = match self.get_season_data(season, "tie_breaker_points") {
415            Ok(m) => m,
416            Err(e) => panic!("Something went wrong: {}", e),
417        };
418
419        data
420    }
421
422    pub fn events(&self, season: Season) -> HashMap<String, Event, RandomState> {
423        let resp = match self
424            .client
425            .request(&format!("/team/{}/events/{}", self.team_number, season.value())[..])
426        {
427            Ok(r) => match r.text() {
428                Ok(t) => t,
429                Err(e) => panic!("Something went wrong: {}", e),
430            },
431            Err(e) => panic!("Something went wrong: {}", e),
432        };
433        let json: serde_json::Value = match serde_json::from_str(&*resp) {
434            Ok(m) => m,
435            Err(e) => panic!("Something went wrong: {}", e),
436        };
437
438        let map = match json.as_array() {
439            Some(m) => m,
440            None => panic!("Something went wrong"),
441        };
442
443        let mut keys = Vec::new();
444
445        for val in map.iter() {
446            let key = match val["event_key"].as_str() {
447                Some(k) => k.to_string(),
448                None => panic!("Something went wrong"),
449            };
450            keys.push(key);
451        }
452
453        let mut emap: HashMap<String, Event> = HashMap::new();
454
455        for key in keys.iter() {
456            let event_key = key.clone();
457            let event = Event::new(&*key.clone(), &self.client);
458            let raw_key = event.name();
459            let mut key = raw_key.replace(" ", "_");
460            key = key.to_lowercase();
461            if emap.contains_key(&key[..]) {
462                let re = regex::Regex::new(r"\d{4}-\w+-").unwrap();
463                let raw_key_right = re.replace_all(&event_key[..], "");
464                key = format!("{}_{}", key, raw_key_right.to_lowercase());
465            }
466            emap.insert(key, event);
467        }
468
469        emap
470    }
471}
472
473/// The main class for representation of an FTC event.
474///
475/// Instances of this class should not be created directly;
476/// instead use your [`Team`](struct.Team.html) object.
477#[derive(Clone, Debug)]
478pub struct Event {
479    pub event_key: String,
480    client: Client,
481}
482
483impl Event {
484    #[doc(hidden)]
485    pub fn new(event_key: &str, client: &Client) -> Event {
486        let event_key = event_key.to_string();
487        let client = client.clone();
488
489        Event { event_key, client }
490    }
491    #[doc(hidden)]
492    pub fn name(&self) -> String {
493        let resp = match self.client.request(&*format!("/event/{}", self.event_key)) {
494            Ok(r) => match r.text() {
495                Ok(t) => t,
496                Err(e) => panic!("Something went wrong: {}", e),
497            },
498            Err(e) => panic!("Something went wrong: {}", e),
499        };
500
501        let json: serde_json::Value = match serde_json::from_str(&resp[..]) {
502            Ok(v) => v,
503            Err(e) => panic!("Something went wrong: {}", e),
504        };
505
506        let val = match json.as_array() {
507            Some(v) => v[0].clone(),
508            None => panic!("Something went wrong"),
509        };
510        let val = &val["event_name"];
511        match &val.as_str() {
512            Some(s) => s.to_string(),
513            None => panic!("Something went wrong"),
514        }
515    }
516    /// Basic information of the team.
517    ///
518    /// This method takes no arguments.
519    ///
520    /// It returns a `HashMap<String, String>`.
521    ///
522    /// # Panics
523    ///
524    /// This method can panic in the following ways:
525    /// - The HTTP request was not successful
526    /// - The data received from the API was invalid JSON
527    /// - The data received was in the wrong format
528    pub fn properties(&self) -> HashMap<String, String, RandomState> {
529        let resp = match self
530            .client
531            .request(&format!("/event/{}", self.event_key)[..])
532        {
533            Ok(r) => match r.text() {
534                Ok(t) => t,
535                Err(e) => panic!("Something went wrong: {}", e),
536            },
537            Err(e) => panic!("Something went wrong: {}", e),
538        };
539
540        let json: serde_json::Value = match serde_json::from_str(&resp[..]) {
541            Ok(v) => v,
542            Err(e) => panic!("Something went wrong: {}", e),
543        };
544
545        let map = match json.as_array() {
546            Some(m) => m,
547            None => panic!("Something went wrong"),
548        };
549
550        let val = map[0].clone();
551
552        let new = match val.as_object() {
553            Some(n) => n,
554            None => panic!("Something went wrong"),
555        };
556
557        let mut new_map: HashMap<String, String> = HashMap::new();
558
559        for x in new.iter() {
560            let key = x.0.clone();
561            let value = match x.1 {
562                serde_json::Value::String(s) => s.clone(),
563                serde_json::Value::Number(n) => match n.as_u64() {
564                    Some(u) => u.to_string(),
565                    None => panic!("Something went wrong"),
566                },
567                serde_json::Value::Null => "null".to_string(),
568                serde_json::Value::Bool(b) => match b {
569                    true => "true".to_string(),
570                    false => "false".to_string(),
571                },
572                _ => panic!("Something went wrong"),
573            };
574            new_map.insert(key, value);
575        }
576
577        new_map
578    }
579    fn get_rankings_data(
580        &self,
581        team_number: u32,
582        query: &str,
583    ) -> Result<f64, Box<dyn std::error::Error>> {
584        let resp = self
585            .client
586            .request(&*format!("/event/{}/rankings", self.event_key))?;
587        let map: serde_json::Value = serde_json::from_str(&*resp.text()?)?;
588        let arr = match map.as_array() {
589            Some(a) => a,
590            None => panic!("Something went wrong"),
591        };
592        for val in arr.iter() {
593            let num = &val["team"]["team_number"];
594            let num = match num.as_f64() {
595                Some(n) => n as u32,
596                None => panic!("Something went wrong"),
597            };
598            if num == team_number {
599                match &val[query].as_f64() {
600                    Some(n) => return Ok(n.clone()),
601                    None => continue,
602                };
603            }
604        }
605        panic!("Something went wrong");
606    }
607
608    /// The specified team's rank at the end of the match.
609    ///
610    /// # Arguments
611    ///
612    /// * team_number: `u32` - The number of the team.
613    ///
614    /// # Panics
615    ///
616    /// This method will panic if the data sent by the API is in the wrong format.
617    pub fn rank(&self, team_number: u32) -> f64 {
618        let resp = match self.get_rankings_data(team_number, "rank") {
619            Ok(o) => o,
620            Err(e) => panic!("Something went wrong: {}", e),
621        };
622        resp
623    }
624    /// The amount of times the team's rank changes during the event.
625    ///
626    /// # Arguments
627    ///
628    /// * team_number: `u32` - The number of the team.
629    ///
630    /// # Panics
631    ///
632    /// This method will panic if the data sent by the API is in the wrong format.
633    pub fn rank_change(&self, team_number: u32) -> f64 {
634        let resp = match self.get_rankings_data(team_number, "rank_change") {
635            Ok(o) => o,
636            Err(e) => panic!("Something went wrong: {}", e),
637        };
638        resp
639    }
640    /// The amount of times within the event that the specified team won a match.
641    ///
642    /// # Arguments
643    ///
644    /// * team_number: `u32` - The number of the team.
645    ///
646    /// # Panics
647    ///
648    /// This method will panic if the data sent by the API is in the wrong format.
649    pub fn wins(&self, team_number: u32) -> f64 {
650        let resp = match self.get_rankings_data(team_number, "wins") {
651            Ok(o) => o,
652            Err(e) => panic!("Something went wrong: {}", e),
653        };
654        resp
655    }
656    /// The amount of times within the event that the specified team lost a match.
657    ///
658    /// # Arguments
659    ///
660    /// * team_number: `u32` - The number of the team.
661    ///
662    /// # Panics
663    ///
664    /// This method will panic if the data sent by the API is in the wrong format.
665    pub fn losses(&self, team_number: u32) -> f64 {
666        let resp = match self.get_rankings_data(team_number, "losses") {
667            Ok(o) => o,
668            Err(e) => panic!("Something went wrong: {}", e),
669        };
670        resp
671    }
672    /// The amount of times within the event that the specified team tied a match.
673    ///
674    /// # Arguments
675    ///
676    /// * team_number: `u32` - The number of the team.
677    ///
678    /// # Panics
679    ///
680    /// This method will panic if the data sent by the API is in the wrong format.
681    pub fn ties(&self, team_number: u32) -> f64 {
682        let resp = match self.get_rankings_data(team_number, "ties") {
683            Ok(o) => o,
684            Err(e) => panic!("Something went wrong: {}", e),
685        };
686        resp
687    }
688    /// The specified team's OPR for this event only. Penalties are factored in.
689    ///
690    /// # Arguments
691    ///
692    /// * team_number: `u32` - The number of the team.
693    ///
694    /// # Panics
695    ///
696    /// This method will panic if the data sent by the API is in the wrong format.
697    pub fn opr(&self, team_number: u32) -> f64 {
698        let resp = match self.get_rankings_data(team_number, "opr") {
699            Ok(o) => o,
700            Err(e) => panic!("Something went wrong: {}", e),
701        };
702        resp
703    }
704    /// The specified team's OPR for this event only. Penaltied are not factored in.
705    ///
706    /// # Arguments
707    ///
708    /// * team_number: `u32` - The number of the team.
709    ///
710    /// # Panics
711    ///
712    /// This method will panic if the data sent by the API is in the wrong format.
713    pub fn np_opr(&self, team_number: u32) -> f64 {
714        let resp = match self.get_rankings_data(team_number, "np_opr") {
715            Ok(o) => o,
716            Err(e) => panic!("Something went wrong: {}", e),
717        };
718        resp
719    }
720    /// The specified team's highest score in a qualifier.
721    ///
722    /// # Arguments
723    ///
724    /// * team_number: `u32` - The number of the team.
725    ///
726    /// # Panics
727    ///
728    /// This method will panic if the data sent by the API is in the wrong format.
729    pub fn highest_qualifier_score(&self, team_number: u32) -> f64 {
730        let resp = match self.get_rankings_data(team_number, "highest_qual_score") {
731            Ok(o) => o,
732            Err(e) => panic!("Something went wrong: {}", e),
733        };
734        resp
735    }
736    /// The specified team's ranking points for this event only.
737    ///
738    /// # Arguments
739    ///
740    /// * team_number: `u32` - The number of the team.
741    ///
742    /// # Panics
743    ///
744    /// This method will panic if the data sent by the API is in the wrong format.
745    pub fn ranking_points(&self, team_number: u32) -> f64 {
746        let resp = match self.get_rankings_data(team_number, "ranking_points") {
747            Ok(o) => o,
748            Err(e) => panic!("Something went wrong: {}", e),
749        };
750        resp
751    }
752    /// The specified team's qualifying points for this event only.
753    ///
754    /// # Arguments
755    ///
756    /// * team_number: `u32` - The number of the team.
757    ///
758    /// # Panics
759    ///
760    /// This method will panic if the data sent by the API is in the wrong format.
761    pub fn qualifying_points(&self, team_number: u32) -> f64 {
762        let resp = match self.get_rankings_data(team_number, "qualifying_points") {
763            Ok(o) => o,
764            Err(e) => panic!("Something went wrong: {}", e),
765        };
766        resp
767    }
768    /// The specified team's tiebreaker points for this event only.
769    ///
770    /// # Arguments
771    ///
772    /// * team_number: `u32` - The number of the team.
773    ///
774    /// # Panics
775    ///
776    /// This method will panic if the data sent by the API is in the wrong format.
777    pub fn tiebreaker_points(&self, team_number: u32) -> f64 {
778        let resp = match self.get_rankings_data(team_number, "tie_breaker_points") {
779            Ok(o) => o,
780            Err(e) => panic!("Something went wrong: {}", e),
781        };
782        resp
783    }
784}
785
786/// This enum is used for expressing FTC seasons.
787///
788/// Do not create instances, instead just pass the types to methods
789/// which require you to provide a season.
790///
791/// For example:
792///
793/// ```no_run
794/// # let team = rustoa::Team::new(16405, rustoa::Client::new("api_key"));
795/// let wins = team.season_wins(rustoa::Season::SkyStone);
796/// ```
797pub enum Season {
798    SkyStone,
799    RoverRuckus,
800    RelicRecovery,
801    VelocityVortex,
802}
803
804impl Season {
805    #[doc(hidden)]
806    pub fn value(&self) -> i32 {
807        match self {
808            Season::SkyStone => 1920,
809            Season::RoverRuckus => 1819,
810            Season::RelicRecovery => 1718,
811            Season::VelocityVortex => 1617,
812        }
813    }
814    #[doc(hidden)]
815    pub fn value_of(s: String) -> Season {
816        match &s[..] {
817            "1920" => Season::SkyStone,
818            "1819" => Season::RoverRuckus,
819            "1718" => Season::RelicRecovery,
820            "1617" => Season::VelocityVortex,
821            _ => panic!("That season does not exist in the TOA database."),
822        }
823    }
824}
825
826impl std::fmt::Display for Season {
827    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
828        match self {
829            Season::SkyStone => write!(f, "Season::SkyStone"),
830            Season::RoverRuckus => write!(f, "Season::RoverRuckus"),
831            Season::RelicRecovery => write!(f, "Season::RelicRecovery"),
832            Season::VelocityVortex => write!(f, "Season::VelocityVortex"),
833        }
834    }
835}
836
837#[cfg(test)]
838mod tests {
839    fn create_client() -> super::Client {
840        let key = match std::env::var("API_KEY") {
841            Ok(k) => k,
842            Err(e) => panic!("Something went wrong: {}", e),
843        };
844        super::Client::new(&*key)
845    }
846    #[test]
847    fn correct_version() {
848        let client = create_client();
849        assert_eq!("3.7.0", client.api_version());
850    }
851    #[test]
852    fn check_number() {
853        let client = create_client();
854        let team = client.team(16405);
855        assert_eq!(team.team_number, 16405);
856    }
857    #[test]
858    fn check_compat() {
859        let client = create_client();
860        let team1 = client.team(16405);
861        let team2 = client.team(16405);
862        assert_eq!(team1.wins(), team2.wins());
863        let year1 = match team1.properties().get("rookie_year") {
864            Some(y) => y.clone(),
865            None => panic!("Something went wrong"),
866        };
867        let year2 = match team2.properties().get("rookie_year") {
868            Some(y) => y.clone(),
869            None => panic!("Something went wrong"),
870        };
871        assert_eq!(year1, year2);
872        let event1 = match team1
873            .events(super::Season::SkyStone)
874            .get("trinity_river_qualifier")
875        {
876            Some(e) => e.clone(),
877            None => panic!("No value was found"),
878        };
879        let event2 = match team2
880            .events(super::Season::SkyStone)
881            .get("trinity_river_qualifier")
882        {
883            Some(e) => e.clone(),
884            None => panic!("No value was found"),
885        };
886        assert_eq!(event1.name(), event2.name());
887        assert_eq!(event1.opr(16405), event2.opr(16405));
888    }
889    #[test]
890    fn check_numbers() {
891        let client = create_client();
892        let team1 = client.team(16405);
893        let team2 = client.team(16405);
894        assert_eq!(team1.team_number, team2.team_number);
895    }
896    #[test]
897    fn test_property() {
898        let client = create_client();
899        let team = client.team(16405);
900        let year = match team.properties().get("rookie_year") {
901            Some(y) => y.clone(),
902            None => panic!("Something went wrong"),
903        };
904        assert_eq!("2019", year);
905    }
906
907    #[test]
908    fn test_season() {
909        let season = super::Season::SkyStone;
910        assert_eq!(season.value(), 1920);
911    }
912
913    #[test]
914    fn test_event() {
915        let client = create_client();
916        let team = client.team(16405);
917        let event = match team
918            .events(super::Season::SkyStone)
919            .get("trinity_river_qualifier")
920        {
921            Some(e) => e.clone(),
922            None => panic!("No value was found"),
923        };
924        let name1 = event.name();
925        let name2 = match event.properties().get("event_name") {
926            Some(n) => n.clone(),
927            None => panic!("Something went wrong"),
928        };
929        assert_eq!(name1, name2);
930    }
931}