Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new: Implement Search #20

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
321 changes: 162 additions & 159 deletions Cargo.lock

Large diffs are not rendered by default.

17 changes: 6 additions & 11 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
build:
cargo build --features client,secrets
cargo build --target wasm32-unknown-unknown --no-default-features --features builders,workers,secrets
cargo build --features client,secrets,search
cargo build --target wasm32-unknown-unknown --no-default-features --features builders,workers,secrets,search
cd examples/cloudflare && cargo install -q worker-build && worker-build

build-release:
cargo build --features client,secrets --release
cargo build --target wasm32-unknown-unknown --no-default-features --features builders,workers,secrets --release
cargo build --features client,secrets,search --release
cargo build --target wasm32-unknown-unknown --no-default-features --features builders,workers,secrets,search --release
cd examples/cloudflare && cargo install -q worker-build && worker-build --release

check:
cargo clippy --all-targets --features client,secrets
cargo clippy --all-targets --target wasm32-unknown-unknown --no-default-features --features builders,workers,secrets





cargo clippy --all-targets --features client,secrets,search
cargo clippy --all-targets --target wasm32-unknown-unknown --no-default-features --features builders,workers,secrets,search
2 changes: 2 additions & 0 deletions github-rest/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ github-api-octocat = "0.1.0"
strum = { version = "0.24.0", features = ["derive"] }
serde = { version = "1.0.137", features = ["derive"] }
serde_json = "1.0.81"
serde_urlencoded = { version = "0.7.1", optional = true }
base64 = { version = "0.13.0", optional = true }

# Misc
Expand All @@ -34,6 +35,7 @@ default = ["builders"]
full = ["builders", "client"]
builders = []
client = ["base64"]
search = ["serde_urlencoded"]

[dev-dependencies]
lazy_static = "1.4.0"
Expand Down
4 changes: 4 additions & 0 deletions github-rest/src/builders/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@ pub use gists::*;
pub use issues::*;
pub use pull_requests::*;
pub use reactions::*;
#[cfg(feature = "search")]
pub use search::*;
use serde::de::DeserializeOwned;

mod commits;
mod gists;
mod issues;
mod pull_requests;
mod reactions;
#[cfg(feature = "search")]
mod search;

#[async_trait]
pub trait Builder {
Expand Down
205 changes: 205 additions & 0 deletions github-rest/src/builders/search.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
use crate::{
builders::{builder, builder_nested_setters, Builder},
methods::{SearchIssuesBody, SearchIssuesResponse, SearchRepositoriesBody, SearchRepositoriesResponse},
GithubRestError, Requester,
};
use async_trait::async_trait;
use github_api_octocat::end_points::EndPoints;
use std::{fmt::Display, ops::Range};

builder!(
/// * tags search
/// * get `/search/issues`
/// * docs <https://docs.github.com/en/rest/search#search-issues-and-pull-requests=>
///
/// Search issues and pull requests
/// Find issues by state and keyword. This method returns up to 100 results [per page](https://docs.github.com/rest/overview/resources-in-the-rest-api#pagination).
///
/// When searching for issues, you can get text match metadata for the issue
/// **title**, issue **body**, and issue **comment body** fields when you
/// pass the `text-match` media type. For more details about how to receive
/// highlighted search results, see [Text match metadata](https://docs.github.com/rest/reference/search#text-match-metadata).
///
/// For example, if you want to find the oldest unresolved Python bugs on
/// Windows. Your query might look something like this.
///
/// `q=windows+label:bug+language:python+state:open&sort=created&order=asc`
///
/// This query searches for the keyword `windows`, within any open issue
/// that is labeled as `bug`. The search runs across repositories whose
/// primary language is Python. The results are sorted by creation date in
/// ascending order, which means the oldest issues appear first in the
/// search results.
///
/// **Note:** For [user-to-server](https://docs.github.com/developers/apps/identifying-and-authorizing-users-for-github-apps#user-to-server-requests) GitHub App requests, you can't retrieve a combination of issues and pull requests in a single query. Requests that don't include the `is:issue` or `is:pull-request` qualifier will receive an HTTP `422 Unprocessable Entity` response. To get results for both issues and pull requests, you must send separate queries for issues and pull requests. For more information about the `is` qualifier, see "[Searching only issues or pull requests](https://docs.github.com/github/searching-for-information-on-github/searching-issues-and-pull-requests#search-only-issues-or-pull-requests)."
SearchIssuesBuilder {
query: String,
body: SearchIssuesBody
}
);

builder_nested_setters!(SearchIssuesBuilder {
body {
comments: Range<usize>,
interactions: Range<usize>,
reactions: Range<usize>
}
});

#[async_trait]
impl Builder for SearchIssuesBuilder {
type Response = SearchIssuesResponse;

async fn execute<T>(mut self, client: &T) -> Result<Self::Response, GithubRestError>
where
T: Requester,
{
self.query.push_str(self.body.into_query().as_str());

client
.req::<_, String, _>(EndPoints::GetSearchIssues(), Some(&[("q", self.query)]), None)
.await
}
}

impl SearchIssuesBuilder {
pub fn query<T: Into<String>>(mut self, query: T) -> Self {
self.query = {
serde_urlencoded::to_string(
serde_urlencoded::from_str::<Vec<(String, String)>>(query.into().as_str()).expect("Invalid query!"),
)
.unwrap()
};

self
}
}

builder!(
/// * tags search
/// * get `/search/repositories`
/// * docs <https://docs.github.com/en/rest/search#search-repositories=>
///
/// Search repositories
/// Find repositories via various criteria. This method returns up to 100 results [per page](https://docs.github.com/rest/overview/resources-in-the-rest-api#pagination).
///
/// When searching for repositories, you can get text match metadata for the **name** and **description** fields when you pass the `text-match` media type. For more details about how to receive highlighted search results, see [Text match metadata](https://docs.github.com/rest/reference/search#text-match-metadata).
///
/// For example, if you want to search for popular Tetris repositories
/// written in assembly code, your query might look like this:
///
/// `q=tetris+language:assembly&sort=stars&order=desc`
///
/// This query searches for repositories with the word `tetris` in the name,
/// the description, or the README. The results are limited to repositories
/// where the primary language is assembly. The results are sorted by stars
/// in descending order, so that the most popular repositories appear first
/// in the search results.
SearchRepositoriesBuilder {
query: String,
body: SearchRepositoriesBody
}
);

builder_nested_setters!(SearchRepositoriesBuilder {
body {
size: Range<usize>,
followers: Range<usize>,
forks: Range<usize>,
stars: Range<usize>,
topics: Range<usize>,
help_wanted_issues: Range<usize>,
good_first_issues: Range<usize>
}
});

impl SearchRepositoriesBuilder {
pub fn query<T: Into<String>>(mut self, query: T) -> Self {
self.query = {
serde_urlencoded::to_string(
serde_urlencoded::from_str::<Vec<(String, String)>>(query.into().as_str()).expect("Invalid query!"),
)
.unwrap()
};

self
}

pub fn language<T: Display>(mut self, val: T) -> Self {
self.query.push_str(format!("&language:{val}").as_str());

self
}

pub fn topic<T: Display>(mut self, val: T) -> Self {
self.query.push_str(format!("&topic:{val}").as_str());

self
}
}

#[async_trait]
impl Builder for SearchRepositoriesBuilder {
type Response = SearchRepositoriesResponse;

async fn execute<T>(mut self, client: &T) -> Result<Self::Response, GithubRestError>
where
T: Requester,
{
self.query.push_str(self.body.into_query().as_str());

client
.req::<_, String, _>(EndPoints::GetSearchRepositories(), Some(&[("q", self.query)]), None)
.await
}
}

#[cfg(all(feature = "builders", feature = "client"))]
#[cfg(test)]
mod tests {
use super::*;
use crate::client::DefaultRequester;

#[tokio::test]
async fn test_search_issues_builder() -> Result<(), GithubRestError> {
let requester = DefaultRequester::new_none();

let res = SearchIssuesBuilder::new()
.query("[feature request]")
// TODO: Figure out why using more than one of the range helpers causes errors despite
// the query itself turning out fine.
.comments(1..50)
.reactions(50..150)
.execute(&requester)
.await?;

dbg!(res);
Ok(())
}

#[tokio::test]
async fn test_search_repositories_builder() -> Result<(), GithubRestError> {
let requester = DefaultRequester::new_none();

let res = SearchRepositoriesBuilder::new()
.query("tetris")
.language("assembly")
.stars(20..30)
.execute(&requester)
.await?;

dbg!(res);

let res = SearchRepositoriesBuilder::new()
.query("doom")
.language("rust")
.topic("game")
.stars(1000..usize::MAX)
.execute(&requester)
.await?;

dbg!(res);

Ok(())
}
}
2 changes: 1 addition & 1 deletion github-rest/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ impl DefaultRequester {
{
let mut encoder = Base64Encoder::new(&mut auth_header, base64::STANDARD);

write!(encoder, "{}", auth).unwrap();
write!(encoder, "{auth}").unwrap();
}

let mut headers = header::HeaderMap::new();
Expand Down
3 changes: 2 additions & 1 deletion github-rest/src/methods/issues.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,12 @@ pub struct GetIssuesBody {
///
/// Represents the state of an issue. Possible variants are open, closed, and
/// all.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum IssueState {
Open,
Closed,
#[default]
All,
}

Expand Down
9 changes: 2 additions & 7 deletions github-rest/src/methods/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,15 @@ where

//Role enum used for add to org function determines the function a user has
// within a organization
#[derive(Deserialize, Serialize, EnumString, EnumVariantNames, Debug, Clone)]
#[derive(Deserialize, Serialize, EnumString, EnumVariantNames, Debug, Default, Clone)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum Role {
Admin,
#[default]
Member,
}

impl Default for Role {
fn default() -> Role {
Role::Member
}
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct AddToOrgBody {
role: Role,
Expand Down
4 changes: 4 additions & 0 deletions github-rest/src/methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ pub use commits::*;
pub use gists::*;
pub use issues::*;
pub use misc::*;
#[cfg(feature = "search")]
pub use search::*;
pub use users::*;

mod commits;
mod gists;
mod issues;
mod misc;
#[cfg(feature = "search")]
mod search;
mod users;
pub(crate) mod util;

Expand Down
Loading