From 857a27350c88fa2b1acd7d8de34214d736b444da Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Wed, 7 Apr 2021 17:20:40 +0200 Subject: Split api into modules Signed-off-by: Matthias Beyer --- librepology/src/v1/api.rs | 265 ------------------------------ librepology/src/v1/api/client.rs | 42 +++++ librepology/src/v1/api/mod.rs | 12 ++ librepology/src/v1/api/request.rs | 125 ++++++++++++++ librepology/src/v1/api/request_builder.rs | 111 +++++++++++++ librepology/src/v1/api/response.rs | 12 ++ 6 files changed, 302 insertions(+), 265 deletions(-) delete mode 100644 librepology/src/v1/api.rs create mode 100644 librepology/src/v1/api/client.rs create mode 100644 librepology/src/v1/api/mod.rs create mode 100644 librepology/src/v1/api/request.rs create mode 100644 librepology/src/v1/api/request_builder.rs create mode 100644 librepology/src/v1/api/response.rs diff --git a/librepology/src/v1/api.rs b/librepology/src/v1/api.rs deleted file mode 100644 index eb843d6..0000000 --- a/librepology/src/v1/api.rs +++ /dev/null @@ -1,265 +0,0 @@ -use crate::endpoint::EndpointUrl; -use crate::v1::error::Result; -use crate::v1::error::RepologyError; - -static APP_USER_AGENT: &str = concat!( - env!("CARGO_PKG_NAME"), - "/", - env!("CARGO_PKG_VERSION"), -); - -pub struct ApiClient { - endpoint_url: &'static str, - client: reqwest::Client, -} - -impl ApiClient { - pub fn new(timeout: std::time::Duration) -> Result { - reqwest::Client::builder() - .user_agent(APP_USER_AGENT) - .build() - .map_err(RepologyError::from) - .map(|client| ApiClient { - endpoint_url: EP::endpoint_url(), - client, - }) - } - - pub fn projects<'a>(&'a self) -> ProjectRequestBuilder<'a> { - ProjectRequestBuilder(self) - } - -} - -pub struct ProjectRequestBuilder<'a>(&'a ApiClient); - -impl<'a> ProjectRequestBuilder<'a> { - pub fn with_name(self, name: String) -> ProjectRequestBuilderWithName<'a> { - ProjectRequestBuilderWithName(self.0, name) - } - - pub fn filtered(self) -> ProjectRequestFilteredBuilder<'a> { - ProjectRequestFilteredBuilder { - client: self.0, - search: None, - category: None, - in_repo_filter: None, - not_in_repo_filter: None, - repos_filter: None, - families_filter: None, - repos_newest: None, - families_newest: None, - newest: None, - outdated: None, - problematic: None, - } - } -} - -pub trait ToRequest<'a> { - fn to_request(self) -> Request<'a>; -} - -pub struct Request<'a> { - client: &'a ApiClient, - request_string: String -} - -impl<'a> Request<'a> { - pub async fn perform(self) -> Result { - self.client - .client - .get(format!("{}{}", self.client.endpoint_url, self.request_string)) - .send() - .await? - .text() - .await - .map(Response) - .map_err(RepologyError::from) - } -} - -pub struct Response(String); - -impl Response { - pub fn deserialize<'de, T: serde::Deserialize<'de>>(&'de self) -> Result { - serde_json::from_str(&self.0).map_err(RepologyError::from) - } -} - -pub struct ProjectRequestBuilderWithName<'a>(&'a ApiClient, String); - -impl<'a> ToRequest<'a> for ProjectRequestBuilderWithName<'a> { - fn to_request(self) -> Request<'a> { - Request { - client: self.0, - request_string: format!("projects/{}", self.1), - } - } -} - -pub struct ProjectRequestFilteredBuilder<'a> { - client: &'a ApiClient, - - // From the API documentation - - search: Option, - category: Option, - in_repo_filter: Option, - not_in_repo_filter: Option, - repos_filter: Option, - families_filter: Option, - repos_newest: Option, - families_newest: Option, - newest: Option, - outdated: Option, - problematic: Option, -} - -impl<'a> ProjectRequestFilteredBuilder<'a> { - pub fn with_search(mut self, opt: Option) -> Self { - self.search = opt; - self - } - - pub fn with_category(mut self, opt: Option) -> Self { - self.category = opt; - self - } - - pub fn with_in_repo_filter(mut self, opt: Option) -> Self { - self.in_repo_filter = opt; - self - } - - pub fn with_not_in_repo_filter(mut self, opt: Option) -> Self { - self.not_in_repo_filter = opt; - self - } - - pub fn with_repos_filter(mut self, opt: Option) -> Self { - self.repos_filter = opt; - self - } - - pub fn with_families_filter(mut self, opt: Option) -> Self { - self.families_filter = opt; - self - } - - pub fn with_repos_newest(mut self, opt: Option) -> Self { - self.repos_newest = opt; - self - } - - pub fn with_families_newest(mut self, opt: Option) -> Self { - self.families_newest = opt; - self - } - - pub fn with_newest(mut self, opt: Option) -> Self { - self.newest = opt; - self - } - - pub fn with_outdated(mut self, opt: Option) -> Self { - self.outdated = opt; - self - } - - pub fn with_problematic(mut self, opt: Option) -> Self { - self.problematic = opt; - self - } - -} - -impl<'a> ToRequest<'a> for ProjectRequestFilteredBuilder<'a> { - fn to_request(self) -> Request<'a> { - let mut buf = Vec::new(); - - if let Some(search) = self.search.as_ref() { - let s = format!("search={}", search); - buf.push(s); - } - - if let Some(category) = self.category.as_ref() { - let s = format!("category={}", category); - buf.push(s); - } - - if let Some(in_repo_filter) = self.in_repo_filter.as_ref() { - let s = format!("in_repo_filter={}", in_repo_filter); - buf.push(s); - } - - if let Some(not_in_repo_filter) = self.not_in_repo_filter.as_ref() { - let s = format!("not_in_repo_filter={}", not_in_repo_filter); - buf.push(s); - } - - if let Some(repos_filter) = self.repos_filter.as_ref() { - match repos_filter { - NumberOrRange::Number(u) => { - let s = format!("repos={}", u); - buf.push(s); - }, - NumberOrRange::Range(None, None) => { - // nothing, because there is no range - }, - NumberOrRange::Range(Some(a), None) => { - let s = format!("repos={}-", a); - buf.push(s); - }, - NumberOrRange::Range(None, Some(b)) => { - let s = format!("repos=-{}", b); - buf.push(s); - }, - NumberOrRange::Range(Some(a), Some(b)) => { - let s = format!("repos={}-{}", a, b); - buf.push(s); - }, - } - } - - if let Some(families_filter) = self.families_filter.as_ref() { - let s = format!("families={}", families_filter); - buf.push(s); - } - - if let Some(newest) = self.newest.as_ref() { - if *newest { - buf.push(String::from("newest=1")); - } else { - buf.push(String::from("newest=0")); - } - } - - if let Some(outdated) = self.outdated.as_ref() { - if *outdated { - buf.push(String::from("newest=1")); - } else { - buf.push(String::from("newest=0")); - } - } - - if let Some(problematic) = self.problematic.as_ref() { - if *problematic { - buf.push(String::from("newest=1")); - } else { - buf.push(String::from("newest=0")); - } - } - - Request { - client: self.client, - request_string: format!("&{}", buf.join("&")), - } - } -} - -pub enum NumberOrRange { - Number(usize), - Range(Option, Option), -} - diff --git a/librepology/src/v1/api/client.rs b/librepology/src/v1/api/client.rs new file mode 100644 index 0000000..5a4e7f2 --- /dev/null +++ b/librepology/src/v1/api/client.rs @@ -0,0 +1,42 @@ +use crate::endpoint::EndpointUrl; +use crate::v1::error::Result; +use crate::v1::error::RepologyError; +use crate::v1::api::ProjectRequestBuilder; + +static APP_USER_AGENT: &str = concat!( + env!("CARGO_PKG_NAME"), + "/", + env!("CARGO_PKG_VERSION"), +); + +pub struct ApiClient { + pub(super) endpoint_url: &'static str, + pub(super) client: reqwest::Client, +} + +impl ApiClient { + pub fn new() -> Result { + reqwest::Client::builder() + .user_agent(APP_USER_AGENT) + .build() + .map_err(RepologyError::from) + .map(|client| ApiClient { + endpoint_url: EP::endpoint_url(), + client, + }) + } + + pub fn client(&self) -> &reqwest::Client { + &self.client + } + + pub fn client_mut(&mut self) -> &reqwest::Client { + &mut self.client + } + + pub fn projects<'a>(&'a self) -> ProjectRequestBuilder<'a> { + ProjectRequestBuilder(self) + } + +} + diff --git a/librepology/src/v1/api/mod.rs b/librepology/src/v1/api/mod.rs new file mode 100644 index 0000000..34fa9a2 --- /dev/null +++ b/librepology/src/v1/api/mod.rs @@ -0,0 +1,12 @@ +mod client; +pub use client::*; + +mod request_builder; +pub use request_builder::*; + +mod request; +pub use request::*; + +mod response; +pub use response::*; + diff --git a/librepology/src/v1/api/request.rs b/librepology/src/v1/api/request.rs new file mode 100644 index 0000000..fef4385 --- /dev/null +++ b/librepology/src/v1/api/request.rs @@ -0,0 +1,125 @@ +use crate::v1::api::ApiClient; +use crate::v1::api::NumberOrRange; +use crate::v1::api::ProjectRequestBuilderWithName; +use crate::v1::api::ProjectRequestFilteredBuilder; +use crate::v1::api::Response; +use crate::v1::error::RepologyError; +use crate::v1::error::Result; + +pub struct Request<'a> { + client: &'a ApiClient, + request_string: String +} + +impl<'a> Request<'a> { + pub async fn perform(self) -> Result { + self.client + .client + .get(format!("{}{}", self.client.endpoint_url, self.request_string)) + .send() + .await? + .text() + .await + .map(Response) + .map_err(RepologyError::from) + } +} + +pub trait ToRequest<'a> { + fn to_request(self) -> Request<'a>; +} + +impl<'a> ToRequest<'a> for ProjectRequestBuilderWithName<'a> { + fn to_request(self) -> Request<'a> { + Request { + client: self.0, + request_string: format!("projects/{}", self.1), + } + } +} + + +impl<'a> ToRequest<'a> for ProjectRequestFilteredBuilder<'a> { + fn to_request(self) -> Request<'a> { + let mut buf = Vec::new(); + + if let Some(search) = self.search.as_ref() { + let s = format!("search={}", search); + buf.push(s); + } + + if let Some(category) = self.category.as_ref() { + let s = format!("category={}", category); + buf.push(s); + } + + if let Some(in_repo_filter) = self.in_repo_filter.as_ref() { + let s = format!("in_repo_filter={}", in_repo_filter); + buf.push(s); + } + + if let Some(not_in_repo_filter) = self.not_in_repo_filter.as_ref() { + let s = format!("not_in_repo_filter={}", not_in_repo_filter); + buf.push(s); + } + + if let Some(repos_filter) = self.repos_filter.as_ref() { + match repos_filter { + NumberOrRange::Number(u) => { + let s = format!("repos={}", u); + buf.push(s); + }, + NumberOrRange::Range(None, None) => { + // nothing, because there is no range + }, + NumberOrRange::Range(Some(a), None) => { + let s = format!("repos={}-", a); + buf.push(s); + }, + NumberOrRange::Range(None, Some(b)) => { + let s = format!("repos=-{}", b); + buf.push(s); + }, + NumberOrRange::Range(Some(a), Some(b)) => { + let s = format!("repos={}-{}", a, b); + buf.push(s); + }, + } + } + + if let Some(families_filter) = self.families_filter.as_ref() { + let s = format!("families={}", families_filter); + buf.push(s); + } + + if let Some(newest) = self.newest.as_ref() { + if *newest { + buf.push(String::from("newest=1")); + } else { + buf.push(String::from("newest=0")); + } + } + + if let Some(outdated) = self.outdated.as_ref() { + if *outdated { + buf.push(String::from("newest=1")); + } else { + buf.push(String::from("newest=0")); + } + } + + if let Some(problematic) = self.problematic.as_ref() { + if *problematic { + buf.push(String::from("newest=1")); + } else { + buf.push(String::from("newest=0")); + } + } + + Request { + client: self.client, + request_string: format!("&{}", buf.join("&")), + } + } +} + diff --git a/librepology/src/v1/api/request_builder.rs b/librepology/src/v1/api/request_builder.rs new file mode 100644 index 0000000..7218caa --- /dev/null +++ b/librepology/src/v1/api/request_builder.rs @@ -0,0 +1,111 @@ +use crate::v1::api::ApiClient; + +pub struct ProjectRequestBuilder<'a>(pub(super) &'a ApiClient); + +impl<'a> ProjectRequestBuilder<'a> { + pub fn with_name(self, name: String) -> ProjectRequestBuilderWithName<'a> { + ProjectRequestBuilderWithName(self.0, name) + } + + pub fn filtered(self) -> ProjectRequestFilteredBuilder<'a> { + ProjectRequestFilteredBuilder { + client: self.0, + search: None, + category: None, + in_repo_filter: None, + not_in_repo_filter: None, + repos_filter: None, + families_filter: None, + repos_newest: None, + families_newest: None, + newest: None, + outdated: None, + problematic: None, + } + } +} + + +pub struct ProjectRequestBuilderWithName<'a>(pub(super) &'a ApiClient, pub(super) String); + +pub struct ProjectRequestFilteredBuilder<'a> { + pub(super) client: &'a ApiClient, + + // From the API documentation: + + pub(super) search: Option, + pub(super) category: Option, + pub(super) in_repo_filter: Option, + pub(super) not_in_repo_filter: Option, + pub(super) repos_filter: Option, + pub(super) families_filter: Option, + pub(super) repos_newest: Option, + pub(super) families_newest: Option, + pub(super) newest: Option, + pub(super) outdated: Option, + pub(super) problematic: Option, +} + +impl<'a> ProjectRequestFilteredBuilder<'a> { + pub fn with_search(mut self, opt: Option) -> Self { + self.search = opt; + self + } + + pub fn with_category(mut self, opt: Option) -> Self { + self.category = opt; + self + } + + pub fn with_in_repo_filter(mut self, opt: Option) -> Self { + self.in_repo_filter = opt; + self + } + + pub fn with_not_in_repo_filter(mut self, opt: Option) -> Self { + self.not_in_repo_filter = opt; + self + } + + pub fn with_repos_filter(mut self, opt: Option) -> Self { + self.repos_filter = opt; + self + } + + pub fn with_families_filter(mut self, opt: Option) -> Self { + self.families_filter = opt; + self + } + + pub fn with_repos_newest(mut self, opt: Option) -> Self { + self.repos_newest = opt; + self + } + + pub fn with_families_newest(mut self, opt: Option) -> Self { + self.families_newest = opt; + self + } + + pub fn with_newest(mut self, opt: Option) -> Self { + self.newest = opt; + self + } + + pub fn with_outdated(mut self, opt: Option) -> Self { + self.outdated = opt; + self + } + + pub fn with_problematic(mut self, opt: Option) -> Self { + self.problematic = opt; + self + } + +} + +pub enum NumberOrRange { + Number(usize), + Range(Option, Option), +} + diff --git a/librepology/src/v1/api/response.rs b/librepology/src/v1/api/response.rs new file mode 100644 index 0000000..d6a6e6c --- /dev/null +++ b/librepology/src/v1/api/response.rs @@ -0,0 +1,12 @@ +use crate::v1::error::Result; +use crate::v1::error::RepologyError; + +pub struct Response(pub(super) String); + +impl Response { + pub fn deserialize<'de, T: serde::Deserialize<'de>>(&'de self) -> Result { + serde_json::from_str(&self.0).map_err(RepologyError::from) + } +} + + -- cgit v1.2.3