hubcaps_ex/
search.rs

1//! Search interface
2use std::collections::HashMap;
3use std::fmt;
4
5use serde::de::DeserializeOwned;
6use serde::{Deserialize, Serialize};
7use url::{self, form_urlencoded};
8
9use crate::labels::Label;
10use crate::users::User;
11use crate::{unfold, Future, Github, SortDirection, Stream};
12
13mod repos;
14
15pub use self::repos::*;
16use crate::milestone::Milestone;
17
18/// Sort directions for pull requests
19#[derive(Clone, Copy, Debug, PartialEq)]
20pub enum IssuesSort {
21    /// Sort by time created
22    Created,
23    /// Sort by last updated
24    Updated,
25    /// Sort by number of comments
26    Comments,
27}
28
29impl fmt::Display for IssuesSort {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        match *self {
32            IssuesSort::Comments => "comments",
33            IssuesSort::Created => "created",
34            IssuesSort::Updated => "updated",
35        }
36        .fmt(f)
37    }
38}
39
40/// Provides access to general search operations
41///
42#[derive(Clone)]
43pub struct Search {
44    github: Github,
45}
46
47fn items<D>(result: SearchResult<D>) -> Vec<D>
48where
49    D: DeserializeOwned + 'static + Send,
50{
51    result.items
52}
53
54impl Search {
55    #[doc(hidden)]
56    pub fn new(github: Github) -> Self {
57        Self { github }
58    }
59
60    /// return a reference to a search interface for issues
61    pub fn issues(&self) -> SearchIssues {
62        SearchIssues::new(self.clone())
63    }
64
65    /// Return a reference to a search interface for repositories
66    pub fn repos(&self) -> SearchRepos {
67        SearchRepos::new(self.clone())
68    }
69
70    fn iter<D>(&self, url: &str) -> Stream<D>
71    where
72        D: DeserializeOwned + 'static + Send,
73    {
74        unfold(self.github.clone(), self.github.get_pages(url), items)
75    }
76
77    fn search<D>(&self, url: &str) -> Future<SearchResult<D>>
78    where
79        D: DeserializeOwned + 'static + Send,
80    {
81        self.github.get(url)
82    }
83}
84
85/// Provides access to issue search operations
86/// https://developer.github.com/v3/search/#search-issues
87pub struct SearchIssues {
88    search: Search,
89}
90
91impl SearchIssues {
92    #[doc(hidden)]
93    pub fn new(search: Search) -> Self {
94        Self { search }
95    }
96
97    fn search_uri<Q>(&self, q: Q, options: &SearchIssuesOptions) -> String
98    where
99        Q: Into<String>,
100    {
101        let mut uri = vec!["/search/issues".to_string()];
102        let query_options = options.serialize().unwrap_or_default();
103        let query = form_urlencoded::Serializer::new(query_options)
104            .append_pair("q", &q.into())
105            .finish();
106        uri.push(query);
107        uri.join("?")
108    }
109
110    /// Return a stream of search results repository query
111    /// See [github docs](https://developer.github.com/v3/search/#parameters-3)
112    /// for query format options
113    pub fn iter<Q>(&self, q: Q, options: &SearchIssuesOptions) -> Stream<IssuesItem>
114    where
115        Q: Into<String>,
116    {
117        self.search.iter::<IssuesItem>(&self.search_uri(q, options))
118    }
119
120    /// Return the first page of search result repository query
121    /// See [github docs](https://developer.github.com/v3/search/#parameters-3)
122    /// for query format options
123    pub fn list<Q>(&self, q: Q, options: &SearchIssuesOptions) -> Future<SearchResult<IssuesItem>>
124    where
125        Q: Into<String>,
126    {
127        self.search
128            .search::<IssuesItem>(&self.search_uri(q, options))
129    }
130}
131
132// representations (todo: replace with derive_builder)
133
134#[derive(Default)]
135pub struct SearchIssuesOptions {
136    params: HashMap<&'static str, String>,
137}
138
139impl SearchIssuesOptions {
140    pub fn builder() -> SearchIssuesOptionsBuilder {
141        SearchIssuesOptionsBuilder::default()
142    }
143
144    /// serialize options as a string. returns None if no options are defined
145    pub fn serialize(&self) -> Option<String> {
146        if self.params.is_empty() {
147            None
148        } else {
149            let encoded: String = form_urlencoded::Serializer::new(String::new())
150                .extend_pairs(&self.params)
151                .finish();
152            Some(encoded)
153        }
154    }
155}
156
157/// Provides access to [search operations for issues and pull requests](https://developer.github.com/v3/search/#search-issues)
158#[derive(Default)]
159pub struct SearchIssuesOptionsBuilder(SearchIssuesOptions);
160
161impl SearchIssuesOptionsBuilder {
162    pub fn per_page(&mut self, n: usize) -> &mut Self {
163        self.0.params.insert("per_page", n.to_string());
164        self
165    }
166
167    pub fn sort(&mut self, sort: IssuesSort) -> &mut Self {
168        self.0.params.insert("sort", sort.to_string());
169        self
170    }
171
172    pub fn order(&mut self, direction: SortDirection) -> &mut Self {
173        self.0.params.insert("order", direction.to_string());
174        self
175    }
176
177    pub fn page(&mut self, n: usize) -> &mut Self {
178        self.0.params.insert("page", n.to_string());
179        self
180    }
181
182    pub fn build(&self) -> SearchIssuesOptions {
183        SearchIssuesOptions {
184            params: self.0.params.clone(),
185        }
186    }
187}
188
189#[derive(Debug, Deserialize)]
190pub struct SearchResult<D> {
191    pub total_count: u64,
192    pub incomplete_results: bool,
193    pub items: Vec<D>,
194}
195
196/// May reporesent a Github Issue or PullRequest
197/// depending on the type of search
198#[derive(Debug, Deserialize, Serialize)]
199pub struct IssuesItem {
200    pub url: String,
201    pub repository_url: String,
202    pub labels_url: String,
203    pub comments_url: String,
204    pub events_url: String,
205    pub html_url: String,
206    pub id: u64,
207    pub number: u64,
208    pub title: String,
209    pub user: User,
210    pub labels: Vec<Label>,
211    pub state: String,
212    pub locked: bool,
213    pub assignee: Option<User>,
214    pub assignees: Vec<User>,
215    pub comments: u64,
216    pub created_at: String,
217    pub updated_at: String,
218    pub closed_at: Option<String>,
219    pub pull_request: Option<PullRequestInfo>,
220    pub body: Option<String>,
221    pub milestone: Option<Milestone>,
222}
223
224impl IssuesItem {
225    /// returns a tuple of (repo owner name, repo name) associated with this issue
226    pub fn repo_tuple(&self) -> (String, String) {
227        // split the last two elements off the repo url path
228        let parsed = url::Url::parse(&self.repository_url).unwrap();
229        let mut path = parsed.path().split('/').collect::<Vec<_>>();
230        path.reverse();
231        (path[1].to_owned(), path[0].to_owned())
232    }
233}
234
235#[derive(Debug, Deserialize, Serialize)]
236pub struct PullRequestInfo {
237    pub url: String,
238    pub html_url: String,
239    pub diff_url: String,
240    pub patch_url: String,
241}