use std::collections::HashMap;
use std::fmt;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use url::{self, form_urlencoded};
use crate::labels::Label;
use crate::users::User;
use crate::{unfold, Future, Github, SortDirection, Stream};
mod repos;
pub use self::repos::*;
use crate::milestone::Milestone;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum IssuesSort {
Created,
Updated,
Comments,
}
impl fmt::Display for IssuesSort {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
IssuesSort::Comments => "comments",
IssuesSort::Created => "created",
IssuesSort::Updated => "updated",
}
.fmt(f)
}
}
#[derive(Clone)]
pub struct Search {
github: Github,
}
fn items<D>(result: SearchResult<D>) -> Vec<D>
where
D: DeserializeOwned + 'static + Send,
{
result.items
}
impl Search {
#[doc(hidden)]
pub fn new(github: Github) -> Self {
Self { github }
}
pub fn issues(&self) -> SearchIssues {
SearchIssues::new(self.clone())
}
pub fn repos(&self) -> SearchRepos {
SearchRepos::new(self.clone())
}
fn iter<D>(&self, url: &str) -> Stream<D>
where
D: DeserializeOwned + 'static + Send,
{
unfold(self.github.clone(), self.github.get_pages(url), items)
}
fn search<D>(&self, url: &str) -> Future<SearchResult<D>>
where
D: DeserializeOwned + 'static + Send,
{
self.github.get(url)
}
}
pub struct SearchIssues {
search: Search,
}
impl SearchIssues {
#[doc(hidden)]
pub fn new(search: Search) -> Self {
Self { search }
}
fn search_uri<Q>(&self, q: Q, options: &SearchIssuesOptions) -> String
where
Q: Into<String>,
{
let mut uri = vec!["/search/issues".to_string()];
let query_options = options.serialize().unwrap_or_default();
let query = form_urlencoded::Serializer::new(query_options)
.append_pair("q", &q.into())
.finish();
uri.push(query);
uri.join("?")
}
pub fn iter<Q>(&self, q: Q, options: &SearchIssuesOptions) -> Stream<IssuesItem>
where
Q: Into<String>,
{
self.search.iter::<IssuesItem>(&self.search_uri(q, options))
}
pub fn list<Q>(&self, q: Q, options: &SearchIssuesOptions) -> Future<SearchResult<IssuesItem>>
where
Q: Into<String>,
{
self.search
.search::<IssuesItem>(&self.search_uri(q, options))
}
}
#[derive(Default)]
pub struct SearchIssuesOptions {
params: HashMap<&'static str, String>,
}
impl SearchIssuesOptions {
pub fn builder() -> SearchIssuesOptionsBuilder {
SearchIssuesOptionsBuilder::default()
}
pub fn serialize(&self) -> Option<String> {
if self.params.is_empty() {
None
} else {
let encoded: String = form_urlencoded::Serializer::new(String::new())
.extend_pairs(&self.params)
.finish();
Some(encoded)
}
}
}
#[derive(Default)]
pub struct SearchIssuesOptionsBuilder(SearchIssuesOptions);
impl SearchIssuesOptionsBuilder {
pub fn per_page(&mut self, n: usize) -> &mut Self {
self.0.params.insert("per_page", n.to_string());
self
}
pub fn sort(&mut self, sort: IssuesSort) -> &mut Self {
self.0.params.insert("sort", sort.to_string());
self
}
pub fn order(&mut self, direction: SortDirection) -> &mut Self {
self.0.params.insert("order", direction.to_string());
self
}
pub fn page(&mut self, n: usize) -> &mut Self {
self.0.params.insert("page", n.to_string());
self
}
pub fn build(&self) -> SearchIssuesOptions {
SearchIssuesOptions {
params: self.0.params.clone(),
}
}
}
#[derive(Debug, Deserialize)]
pub struct SearchResult<D> {
pub total_count: u64,
pub incomplete_results: bool,
pub items: Vec<D>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct IssuesItem {
pub url: String,
pub repository_url: String,
pub labels_url: String,
pub comments_url: String,
pub events_url: String,
pub html_url: String,
pub id: u64,
pub number: u64,
pub title: String,
pub user: User,
pub labels: Vec<Label>,
pub state: String,
pub locked: bool,
pub assignee: Option<User>,
pub assignees: Vec<User>,
pub comments: u64,
pub created_at: String,
pub updated_at: String,
pub closed_at: Option<String>,
pub pull_request: Option<PullRequestInfo>,
pub body: Option<String>,
pub milestone: Option<Milestone>,
}
impl IssuesItem {
pub fn repo_tuple(&self) -> (String, String) {
let parsed = url::Url::parse(&self.repository_url).unwrap();
let mut path = parsed.path().split('/').collect::<Vec<_>>();
path.reverse();
(path[1].to_owned(), path[0].to_owned())
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct PullRequestInfo {
pub url: String,
pub html_url: String,
pub diff_url: String,
pub patch_url: String,
}