1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/*!

[Documentation](https://docs.rs/hltv/latest/hltv/) | [Crates.io](https://crates.io/crates/hltv) | [Repository](https://github.com/dist1ll/hltv-rust)

**A crate for fetching and parsing esports data from [HLTV.org](https://www.hltv.org).**


This crate allows you to fetch and parse upcoming matches, results,
event information, player performance. This crate uses async calls via [`reqwest`]
and parses the HTML document with [`tl`]. This API mimics the way you discover 
information on HLTV. Summary pages (like [HLTV Matches](https://www.hltv.org/matches))
contains less information in the HTML document than the detailed match-specific page.

Currently, the following API calls are supported:

- [`crate::upcoming`]
- [`crate::results`]
- [`crate::get_match`]

## Examples

The builders in `hltv` allow you to build a generic [`Request`] object with a [`fetch`][`Request::fetch`] method.

```rust
#[tokio::test]
async fn results() -> Result<(), hltv::Error> {
    let req = hltv::results()
        .map(Map::Inferno)
        .team(4608) // Team Na'Vi
        .year(2016) 
        .event_type(EventTypeFilter::Lan)
        .build();

    let matches = req.fetch().await?; // <-- this has type Vec<MatchResult>
    Ok(())
}
```
## More examples

### Find out if specific match is live

```rust
# #[tokio::test]
# async fn results() -> Result<(), hltv::Error> {
    let req = hltv::get_match(2346065);
    let m = req.fetch().await?;
    if m.status == hltv::data::MatchStatus::Live {
        println!("match with id:[{}] is live!", m.id);
    }
# }
```

### Get all upcoming matches for a team
```

```

*/
use std::marker::PhantomData;

pub mod converter;
pub mod data;
// Extensions to make the [`tl`] crate more ergonomic.
mod tl_extensions;

// Export builder methods
pub mod request;
pub use request::upcoming::upcoming;
pub use request::results::results;
pub use request::match_page::get_match;

/// Implements a conversion from a DOM object to a collection of its own type.
pub trait ConvertCollection
where
    Self: Sized,
{
    /// Converts a given VDOM into a vector of its own type. This is because
    /// them DOM can contain multiple instances of that type.
    fn convert<'a>(d: &'a tl::VDom<'a>) -> Result<Vec<Self>, crate::Error>;
}

/// Implements a conversion from a DOM object to a single instance of its own type.
pub trait ConvertInstance
where
    Self: Sized,
{
    /// Converts a given VDOM into a instance of its own type. If the DOM contains
    /// multiple instances, the first one is chosen.
    fn convert<'a>(d: &'a tl::VDom<'a>) -> Result<Self, crate::Error>;
}

/// A reusable request object, that fetches, parses and converts HLTV data
/// to the correct type.
#[derive(Debug)]
pub struct Request<T>
where
    T: ConvertInstance,
{
    /// Target url that the request will fetch.
    url: String,
    /// This PhantomData is used to maintain type information without dynamic dispatch.
    _m: PhantomData<T>,
}

impl<T> Request<T>
where
    T: ConvertInstance,
{
    /// Creates a new request object with given url and conversion type.
    pub fn new(url: String) -> Request<T> {
        Request::<T> {
            url,
            _m: PhantomData,
        }
    }
    /// Fetches HTML resource, parses DOM, and converts into type T.
    /// Returns an error if the resource is not reachable.
    /// If you want to create a custom data structure that can be fetched
    /// and read from HLTV, refer to the [`converter`] module.
    pub async fn fetch(&self) -> Result<T, Error> {
        let html = reqwest::get(self.url.clone()).await?.text().await?;
        let vdom = tl::parse(&html, tl::ParserOptions::default())?;
        let x = T::convert(&vdom)?;
        Ok(x)
    }
}

/// Errors that happen during request, parse or conversion of data.
#[derive(Debug)]
pub enum Error {
    /// Any non-200 status code.
    HTTPError,
    /// HTML document is invalid. Refer to `tl::parse`.
    ParseError,
    /// Parsed document can't be converted into target type.
    ConversionError(&'static str),
}

impl From<reqwest::Error> for Error {
    fn from(_: reqwest::Error) -> Self {
        Error::HTTPError
    }
}

impl From<tl::ParseError> for Error {
    fn from(_: tl::ParseError) -> Self {
        Error::ParseError
    }
}

impl std::error::Error for Error {}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Error::HTTPError => write!(f, "error with http client or remote server"),
            Error::ParseError => write!(f, "error parsing received data"),
            Error::ConversionError(_) => write!(f, "error converting data into correct type"),
        } 
    }
}