Skip to content

Commit

Permalink
refactor: rename ListOptions to CratesQuery + future-proofing
Browse files Browse the repository at this point in the history
BREAKING CHANGE:
* Rename ListOptions to CratesQuery
* Make all fields private
* Add a CratesQueryBuilder for construction
  Done to prevent future breaking changes
  • Loading branch information
theduke committed Jan 29, 2022
1 parent 7d70fe3 commit 18aadd0
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 57 deletions.
29 changes: 7 additions & 22 deletions src/async_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ pub struct Client {

pub struct CrateStream {
client: Client,
filter: ListOptions,
filter: CratesQuery,

closed: bool,
items: VecDeque<Crate>,
next_page_fetch: Option<BoxFuture<'static, Result<CratesResponse, Error>>>,
}

impl CrateStream {
fn new(client: Client, filter: ListOptions) -> Self {
fn new(client: Client, filter: CratesQuery) -> Self {
Self {
client,
filter,
Expand Down Expand Up @@ -376,28 +376,13 @@ impl Client {
///
/// If you want to get all results without worrying about paging,
/// use [`all_crates`].
pub async fn crates(&self, spec: ListOptions) -> Result<CratesResponse, Error> {
pub async fn crates(&self, query: CratesQuery) -> Result<CratesResponse, Error> {
let mut url = self.base_url.join("crates").unwrap();
{
let mut q = url.query_pairs_mut();

// If page is zero, bump it to one since pages start at 1.
let page = spec.page.max(1);

q.append_pair("page", &page.to_string());
q.append_pair("per_page", &spec.per_page.to_string());
q.append_pair("sort", spec.sort.to_str());
if let Some(user_id) = spec.user_id {
q.append_pair("user_id", &user_id.to_string());
}
if let Some(query) = spec.query {
q.append_pair("q", &query);
}
}
query.build(url.query_pairs_mut());
self.get(&url).await
}

pub fn crates_stream(&self, filter: ListOptions) -> CrateStream {
pub fn crates_stream(&self, filter: CratesQuery) -> CrateStream {
CrateStream::new(self.clone(), filter)
}

Expand Down Expand Up @@ -439,7 +424,7 @@ mod test {
async fn test_crates_stream_async() {
let client = build_test_client();

let mut stream = client.crates_stream(ListOptions {
let mut stream = client.crates_stream(CratesQuery {
per_page: 10,
..Default::default()
});
Expand Down Expand Up @@ -473,7 +458,7 @@ mod test {
let user = client.user("theduke").await?;

let res = client
.crates(ListOptions {
.crates(CratesQuery {
user_id: Some(user.id),
per_page: 5,
..Default::default()
Expand Down
40 changes: 13 additions & 27 deletions src/sync_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,38 +263,23 @@ impl SyncClient {
/// per page and sorted alphabetically.
///
/// ```rust
/// # use crates_io_api::{SyncClient, ListOptions, Sort, Error};
/// # use crates_io_api::{SyncClient, CratesQuery, Sort, Error};
///
/// # fn f() -> Result<(), Error> {
/// # let client = SyncClient::new( "my-bot-name (my-contact@domain.com)", std::time::Duration::from_millis(1000))?;
/// client.crates(ListOptions{
/// sort: Sort::Alphabetical,
/// per_page: 100,
/// page: 1,
/// query: Some("api".to_string()),
/// ..Default::default()
/// })?;
/// let q = CratesQuery::builder()
/// .sort(Sort::Alphabetical)
/// .search("awesome")
/// .build();
/// let crates = client.crates(q)?;
/// # std::mem::drop(crates);
/// # Ok(())
/// # }
/// ```
pub fn crates(&self, spec: ListOptions) -> Result<CratesResponse, Error> {
pub fn crates(&self, query: CratesQuery) -> Result<CratesResponse, Error> {
let mut url = self.base_url.join("crates")?;
{
let mut q = url.query_pairs_mut();
q.append_pair("page", &spec.page.to_string());
q.append_pair("per_page", &spec.per_page.to_string());
query.build(url.query_pairs_mut());

if spec.sort != Sort::Relevance {
q.append_pair("sort", spec.sort.to_str());
}

if let Some(id) = spec.user_id {
q.append_pair("user_id", &id.to_string());
}
if let Some(query) = spec.query {
q.append_pair("q", &query);
}
}
self.get(url)
}

Expand All @@ -306,12 +291,13 @@ impl SyncClient {
let mut page = 1;
let mut crates = Vec::new();
loop {
let res = self.crates(ListOptions {
query: query.clone(),
let res = self.crates(CratesQuery {
search: query.clone(),
sort: Sort::Alphabetical,
per_page: 100,
page,
user_id: None,
category: None,
})?;
if !res.crates.is_empty() {
crates.extend(res.crates);
Expand Down Expand Up @@ -385,7 +371,7 @@ mod test {

let user = client.user("theduke")?;

let res = client.crates(ListOptions {
let res = client.crates(CratesQuery {
user_id: Some(user.id),
per_page: 5,
..Default::default()
Expand Down
143 changes: 135 additions & 8 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,24 +38,151 @@ impl Sort {
///
/// Used to specify pagination, sorting and a query.
#[derive(Clone, Debug)]
pub struct ListOptions {
pub sort: Sort,
pub per_page: u64,
pub page: u64,
pub user_id: Option<u64>,
pub query: Option<String>,
pub struct CratesQuery {
/// Sort.
pub(crate) sort: Sort,
/// Number of items per page.
pub(crate) per_page: u64,
/// The page to fetch.
pub(crate) page: u64,
pub(crate) user_id: Option<u64>,
/// Search query string.
pub(crate) search: Option<String>,
}

impl CratesQuery {
pub(crate) fn build(&self, mut q: url::form_urlencoded::Serializer<'_, url::UrlQuery<'_>>) {
q.append_pair("page", &self.page.to_string());
q.append_pair("per_page", &self.per_page.to_string());
q.append_pair("sort", self.sort.to_str());
if let Some(id) = self.user_id {
q.append_pair("user_id", &id.to_string());
}
if let Some(search) = &self.search {
q.append_pair("q", search);
}
}
}
impl CratesQuery {
pub fn builder() -> CratesQueryBuilder {
CratesQueryBuilder::new()
}

/// Get a reference to the crate query's sort.
pub fn sort(&self) -> &Sort {
&self.sort
}

/// Set the crate query's sort.
pub fn set_sort(&mut self, sort: Sort) {
self.sort = sort;
}

/// Get the crate query's per page.
pub fn page_size(&self) -> u64 {
self.per_page
}

/// Set the crate query's per page.
pub fn set_page_size(&mut self, per_page: u64) {
self.per_page = per_page;
}

/// Get the crate query's page.
pub fn page(&self) -> u64 {
self.page
}

/// Set the crate query's page.
pub fn set_page(&mut self, page: u64) {
self.page = page;
}

/// Get the crate query's user id.
pub fn user_id(&self) -> Option<u64> {
self.user_id
}

/// Set the crate query's user id.
pub fn set_user_id(&mut self, user_id: Option<u64>) {
self.user_id = user_id;
}

/// Get a reference to the crate query's search.
pub fn search(&self) -> Option<&String> {
self.search.as_ref()
}

/// Set the crate query's search.
pub fn set_search(&mut self, search: Option<String>) {
self.search = search;
}
}

impl Default for ListOptions {
impl Default for CratesQuery {
fn default() -> Self {
Self {
sort: Sort::RecentUpdates,
per_page: 30,
page: 1,
user_id: None,
query: None,
search: None,
}
}
}

pub struct CratesQueryBuilder {
query: CratesQuery,
}

impl CratesQueryBuilder {
/// Construct a new builder.
#[must_use]
pub fn new() -> Self {
Self {
query: CratesQuery::default(),
}
}

/// Set the sorting method.
#[must_use]
pub fn sort(mut self, sort: Sort) -> Self {
self.query.sort = sort;
self
}

/// Set the page size.
#[must_use]
pub fn page_size(mut self, size: u64) -> Self {
self.query.per_page = size;
self
}

/// Filter by a user id.
#[must_use]
pub fn user_id(mut self, user_id: u64) -> Self {
self.query.user_id = Some(user_id);
self
}

/// Search term.
#[must_use]
pub fn search(mut self, search: impl Into<String>) -> Self {
self.query.search = Some(search.into());
self
}

/// Finalize the builder into a usable [`CratesQuery`].
#[must_use]
pub fn build(self) -> CratesQuery {
self.query
}
}

impl Default for CratesQueryBuilder {
fn default() -> Self {
Self::new()
}
}

/// Pagination information.
Expand Down

0 comments on commit 18aadd0

Please sign in to comment.