1use std::fmt;
2
3use serde::Deserialize;
4use serde_json::Value;
5
6use crate::Result;
7
8pub use query::Query;
9
10mod query;
11
12pub struct Search {
41 search_area: SearchArea,
42 query: String,
43 per_page: usize,
44 page: usize,
45 authorization: Option<String>,
46}
47
48enum SearchArea {
49 Issues,
50 Users,
51}
52
53impl fmt::Display for SearchArea {
54 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
55 use SearchArea::*;
56 write!(f, "{}", match self {
57 Issues => "issues",
58 Users => "users",
59 })
60 }
61}
62
63#[derive(Debug, Deserialize)]
64pub struct SearchResults {
65 total_count: u64,
66 items: Vec<Value>,
67}
68
69impl Search {
70 fn new(search_area: SearchArea, query: &Query) -> Self {
71 Search {
72 search_area,
73 query: query.to_string(),
74 per_page: 10,
75 page: 1,
76 authorization: None,
77 }
78 }
79
80 pub fn issues(query: &Query) -> Self {
81 Search::new(SearchArea::Issues, query)
82 }
83
84 pub fn users(query: &Query) -> Self {
85 Search::new(SearchArea::Users, query)
86 }
87
88 pub fn authorization(mut self, token: &str) -> Self {
90 self.authorization = Some(String::from(token));
91 self
92 }
93
94 pub fn get_query(&self) -> &str {
96 &self.query
97 }
98
99 pub fn per_page(mut self, per_page: usize) -> Self {
101 self.per_page = per_page;
102 self
103 }
104
105 pub fn page(mut self, page: usize) -> Self {
107 self.page = page;
108 self
109 }
110
111 pub fn next_page(&mut self) {
113 if self.page < std::usize::MAX {
114 self.page += 1;
115 }
116 }
117
118 pub fn prev_page(&mut self) {
120 if self.page > std::usize::MIN {
121 self.page -= 1;
122 }
123 }
124
125 pub async fn search(&self, user_agent: &str) -> Result<SearchResults> {
127 let request = reqwest::Client::builder()
128 .user_agent(user_agent)
129 .build()?
130 .get(&self.to_string());
131 let request = match &self.authorization {
132 Some(t) => request.header("Authorization", format!("Bearer {token}", token=t)),
133 None => request,
134 };
135 let results: SearchResults = request
136 .send()
137 .await?
138 .json()
139 .await?;
140 Ok(results)
141 }
142}
143
144impl SearchResults {
145 pub fn total_count(&self) -> u64 {
150 self.total_count
151 }
152
153 pub fn items(&self) -> &Vec<Value> {
155 &self.items
156 }
157}
158
159impl fmt::Display for Search {
160 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
161 let search_area = match self.search_area {
162 SearchArea::Issues => "issues",
163 SearchArea::Users => "users",
164 };
165 write!(
166 f,
167 "https://api.github.com/search/{0}?per_page={1}&page={2}&q={3}",
168 search_area, self.per_page, self.page, self.query,
169 )
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn built_search() {
179 const EXPECTED: &str = "https://api.github.com/search/issues?per_page=1&page=1&q=repo:rust-lang/rust+is:pr+is:merged";
180 let search = Search::issues(
181 &Query::new().repo("rust-lang", "rust").is("pr").is("merged"),
182 )
183 .per_page(1);
184
185 assert_eq!(EXPECTED, search.to_string());
186 }
187}