diff --git a/src/params.rs b/src/params.rs index 36c1ea88e..1d89de6d5 100644 --- a/src/params.rs +++ b/src/params.rs @@ -179,10 +179,109 @@ pub trait Paginable { fn set_last(&mut self, item: Self::O); } -#[derive(Debug)] -pub struct ListPaginator { - pub page: List, - pub params: P, +pub trait PaginableList { + type O: Paginate + DeserializeOwned + Send + Sync + 'static + Clone + std::fmt::Debug; + fn new(data: Vec, url: String, has_more: bool, total_count: Option) -> Self; + fn get_data(&self) -> Vec; + fn get_url(&self) -> String; + fn get_total_count(&self) -> Option; + fn has_more(&self) -> bool; +} + +/// A single page of a cursor-paginated list of a search object. +/// +/// For more details, see +#[derive(Debug, Deserialize, Serialize)] +pub struct SearchList { + pub object: String, + pub url: String, + pub has_more: bool, + pub data: Vec, + pub next_page: Option, + pub total_count: Option, +} + +impl Default for SearchList { + fn default() -> Self { + SearchList { + object: String::new(), + data: Vec::new(), + has_more: false, + total_count: None, + url: String::new(), + next_page: None, + } + } +} + +impl Clone for SearchList { + fn clone(&self) -> Self { + SearchList { + object: self.object.clone(), + data: self.data.clone(), + has_more: self.has_more, + total_count: self.total_count, + url: self.url.clone(), + next_page: self.next_page.clone(), + } + } +} + +impl PaginableList + for SearchList +{ + type O = T; + + fn new( + data: Vec, + url: String, + has_more: bool, + total_count: Option, + ) -> SearchList { + Self { object: "".to_string(), url, has_more, data, next_page: None, total_count } + } + + fn get_data(&self) -> Vec { + self.data.clone() + } + fn get_url(&self) -> String { + self.url.clone() + } + fn get_total_count(&self) -> Option { + self.total_count + } + fn has_more(&self) -> bool { + self.has_more + } +} + +impl PaginableList + for List +{ + type O = T; + + fn new(data: Vec, url: String, has_more: bool, total_count: Option) -> List { + Self { url, has_more, data, total_count } + } + + fn get_data(&self) -> Vec { + self.data.clone() + } + fn get_url(&self) -> String { + self.url.clone() + } + fn get_total_count(&self) -> Option { + self.total_count + } + fn has_more(&self) -> bool { + self.has_more + } +} + +impl SearchList { + pub fn paginate

(self, params: P) -> ListPaginator, P> { + ListPaginator { page: self, params } + } } /// A single page of a cursor-paginated list of an object. @@ -214,33 +313,39 @@ impl Clone for List { } impl List { - pub fn paginate

(self, params: P) -> ListPaginator { + pub fn paginate

(self, params: P) -> ListPaginator, P> { ListPaginator { page: self, params } } } +#[derive(Debug)] +pub struct ListPaginator { + pub page: T, + pub params: P, +} + impl< - T: Paginate + DeserializeOwned + Send + Sync + 'static + Clone + std::fmt::Debug, + T: PaginableList + Send + DeserializeOwned + 'static, P: Clone + Serialize + Send + 'static + std::fmt::Debug, > ListPaginator where - P: Paginable, + P: Paginable, { /// Repeatedly queries Stripe for more data until all elements in list are fetched, using /// Stripe's default page size. /// /// Requires `feature = "blocking"`. #[cfg(feature = "blocking")] - pub fn get_all(self, client: &Client) -> Response> { - let mut data = Vec::with_capacity(self.page.total_count.unwrap_or(0) as usize); + pub fn get_all(self, client: &Client) -> Response> { + let mut data = Vec::with_capacity(self.page.get_total_count().unwrap_or(0) as usize); let mut paginator = self; loop { - if !paginator.page.has_more { - data.extend(paginator.page.data.into_iter()); + if !paginator.page.has_more() { + data.extend(paginator.page.get_data().into_iter()); break; } let next_paginator = paginator.next(client)?; - data.extend(paginator.page.data.into_iter()); + data.extend(paginator.page.get_data().into_iter()); paginator = next_paginator } Ok(data) @@ -276,11 +381,11 @@ where /// Requires `feature = ["async", "stream"]`. #[cfg(all(feature = "async", feature = "stream"))] pub fn stream( - mut self, + self, client: &Client, - ) -> impl futures_util::Stream> + Unpin { + ) -> impl futures_util::Stream> + Unpin { // We are going to be popping items off the end of the list, so we need to reverse it. - self.page.data.reverse(); + self.page.get_data().reverse(); Box::pin(futures_util::stream::unfold(Some((self, client.clone())), Self::unfold_stream)) } @@ -289,22 +394,22 @@ where #[cfg(all(feature = "async", feature = "stream"))] async fn unfold_stream( state: Option<(Self, Client)>, - ) -> Option<(Result, Option<(Self, Client)>)> { - let (mut paginator, client) = state?; // If none, we sent the last item in the last iteration + ) -> Option<(Result, Option<(Self, Client)>)> { + let (paginator, client) = state?; // If none, we sent the last item in the last iteration - if paginator.page.data.len() > 1 { - return Some((Ok(paginator.page.data.pop()?), Some((paginator, client)))); + if paginator.page.get_data().len() > 1 { + return Some((Ok(paginator.page.get_data().pop()?), Some((paginator, client)))); // We have more data on this page } - if !paginator.page.has_more { - return Some((Ok(paginator.page.data.pop()?), None)); // Final value of the stream, no errors + if !paginator.page.has_more() { + return Some((Ok(paginator.page.get_data().pop()?), None)); // Final value of the stream, no errors } match paginator.next(&client).await { - Ok(mut next_paginator) => { - let data = paginator.page.data.pop()?; - next_paginator.page.data.reverse(); + Ok(next_paginator) => { + let data = paginator.page.get_data().pop()?; + next_paginator.page.get_data().reverse(); // Yield last value of thimuts page, the next page (and client) becomes the state Some((Ok(data), Some((next_paginator, client)))) @@ -315,9 +420,9 @@ where /// Fetch an additional page of data from stripe. pub fn next(&self, client: &Client) -> Response { - if let Some(last) = self.page.data.last() { - if self.page.url.starts_with("/v1/") { - let path = self.page.url.trim_start_matches("/v1/").to_string(); // the url we get back is prefixed + if let Some(last) = self.page.get_data().last() { + if self.page.get_url().starts_with("/v1/") { + let path = self.page.get_url().trim_start_matches("/v1/").to_string(); // the url we get back is prefixed // clone the params and set the cursor let params_next = { @@ -334,12 +439,7 @@ where } } else { ok(ListPaginator { - page: List { - data: Vec::new(), - has_more: false, - total_count: self.page.total_count, - url: self.page.url.clone(), - }, + page: T::new(Vec::new(), self.page.get_url(), false, self.page.get_total_count()), params: self.params.clone(), }) } @@ -348,13 +448,13 @@ where /// Pin a new future which maps the result inside the page future into /// a ListPaginator #[cfg(feature = "async")] - fn create_paginator(page: Response>, params: P) -> Response { + fn create_paginator(page: Response, params: P) -> Response { use futures_util::FutureExt; Box::pin(page.map(|page| page.map(|page| ListPaginator { page, params }))) } #[cfg(feature = "blocking")] - fn create_paginator(page: Response>, params: P) -> Response { + fn create_paginator(page: Response, params: P) -> Response { page.map(|page| ListPaginator { page, params }) } } diff --git a/src/resources.rs b/src/resources.rs index ea3626aa7..062b25890 100644 --- a/src/resources.rs +++ b/src/resources.rs @@ -49,6 +49,13 @@ mod billing { pub mod usage_record_ext; } +#[path = "resources"] +#[cfg(feature = "products")] +mod products { + pub mod price_ext; + pub mod product_ext; +} + #[path = "resources"] #[cfg(feature = "checkout")] mod checkout { diff --git a/src/resources/charge_ext.rs b/src/resources/charge_ext.rs index 411d2b714..f6669b46c 100644 --- a/src/resources/charge_ext.rs +++ b/src/resources/charge_ext.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use crate::client::{Client, Response}; use crate::ids::{AccountId, BankAccountId, CardId, ChargeId, SourceId, TokenId}; -use crate::params::Object; +use crate::params::{Object, SearchList}; use crate::resources::{Charge, Rule}; /// The set of PaymentSource parameters that can be used to create a charge. @@ -44,6 +44,13 @@ impl Charge { ) -> Response { client.post_form(&format!("/charges/{}/capture", charge_id), params) } + + /// Searches for a charge. + /// + /// For more details see . + pub fn search(client: &Client, params: ChargeSearchParams) -> Response> { + client.get_query("/charges/search", params) + } } impl Object for Rule { @@ -55,3 +62,19 @@ impl Object for Rule { "" } } + +#[derive(Clone, Debug, Default, Serialize)] +pub struct ChargeSearchParams<'a> { + pub query: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub limit: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub page: Option, + pub expand: &'a [&'a str], +} + +impl<'a> ChargeSearchParams<'a> { + pub fn new() -> ChargeSearchParams<'a> { + ChargeSearchParams { query: String::new(), limit: None, page: None, expand: &[] } + } +} diff --git a/src/resources/customer_ext.rs b/src/resources/customer_ext.rs index 24c41230e..5e1b03215 100644 --- a/src/resources/customer_ext.rs +++ b/src/resources/customer_ext.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use crate::client::{Client, Response}; use crate::ids::{BankAccountId, CardId, CustomerId, PaymentSourceId}; -use crate::params::{Deleted, Expand, List}; +use crate::params::{Deleted, Expand, List, SearchList}; use crate::resources::{ BankAccount, Customer, PaymentMethod, PaymentSource, PaymentSourceParams, Source, }; @@ -70,6 +70,22 @@ pub enum CustomerPaymentMethodRetrievalType { WechatPay, } +#[derive(Clone, Debug, Default, Serialize)] +pub struct CustomerSearchParams<'a> { + pub query: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub limit: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub page: Option, + pub expand: &'a [&'a str], +} + +impl<'a> CustomerSearchParams<'a> { + pub fn new() -> CustomerSearchParams<'a> { + CustomerSearchParams { query: String::new(), limit: None, page: None, expand: &[] } + } +} + impl Customer { /// Attaches a source to a customer, does not change default Source for the Customer /// @@ -132,6 +148,13 @@ impl Customer { ) -> Response> { client.get_query(&format!("/customers/{}/payment_methods", customer_id), ¶ms) } + + /// Searches for a customer. + /// + /// For more details see . + pub fn search(client: &Client, params: CustomerSearchParams) -> Response> { + client.get_query("/customers/search", params) + } } /// The set of parameters that can be used when verifying a Bank Account. diff --git a/src/resources/invoice_ext.rs b/src/resources/invoice_ext.rs index cfcbabe8e..5d1f8daaf 100644 --- a/src/resources/invoice_ext.rs +++ b/src/resources/invoice_ext.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::client::{Client, Response}; use crate::ids::{CouponId, CustomerId, InvoiceId, PlanId, SubscriptionId, SubscriptionItemId}; -use crate::params::{Metadata, Timestamp}; +use crate::params::{Metadata, SearchList, Timestamp}; use crate::resources::{CollectionMethod, Invoice}; #[deprecated(since = "0.12.0")] @@ -22,6 +22,13 @@ impl Invoice { pub fn pay(client: &Client, invoice_id: &InvoiceId) -> Response { client.post(&format!("/invoices/{}/pay", invoice_id)) } + + /// Searches for an invoice. + /// + /// For more details see . + pub fn search(client: &Client, params: InvoiceSearchParams) -> Response> { + client.get_query("/invoices/search", params) + } } #[derive(Clone, Debug, Serialize)] @@ -71,3 +78,19 @@ pub struct SubscriptionItemFilter { #[serde(skip_serializing_if = "Option::is_none")] pub quantity: Option, } + +#[derive(Clone, Debug, Default, Serialize)] +pub struct InvoiceSearchParams<'a> { + pub query: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub limit: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub page: Option, + pub expand: &'a [&'a str], +} + +impl<'a> InvoiceSearchParams<'a> { + pub fn new() -> InvoiceSearchParams<'a> { + InvoiceSearchParams { query: String::new(), limit: None, page: None, expand: &[] } + } +} diff --git a/src/resources/payment_intent_ext.rs b/src/resources/payment_intent_ext.rs index 1728ddd76..4f18679dc 100644 --- a/src/resources/payment_intent_ext.rs +++ b/src/resources/payment_intent_ext.rs @@ -1,9 +1,9 @@ use serde::{Deserialize, Serialize}; use crate::client::{Client, Response}; -use crate::params::{Expandable, Metadata}; -use crate::resources::{Currency, PaymentIntent, PaymentSource, Shipping}; -use crate::PaymentIntentCancellationReason; +use crate::params::{Expandable, Metadata, SearchList}; +use crate::resources::{Currency, PaymentSource, Shipping}; +use crate::{PaymentIntent, PaymentIntentCancellationReason}; impl PaymentIntent { /// Confirm that customer intends to pay with current or provided source. Upon confirmation, the PaymentIntent will attempt to initiate a payment. @@ -38,6 +38,16 @@ impl PaymentIntent { ) -> Response { client.post_form(&format!("/payment_intents/{}/cancel", payment_intent_id), params) } + + /// Searches for a payment intent. + /// + /// For more details see . + pub fn search( + client: &Client, + params: PaymentIntentSearchParams, + ) -> Response> { + client.get_query("/payment_intents/search", params) + } } /// The resource representing a Stripe PaymentError object. /// @@ -205,3 +215,19 @@ pub struct CancelPaymentIntent { #[serde(skip_serializing_if = "Option::is_none")] pub cancellation_reason: Option, } + +#[derive(Clone, Debug, Default, Serialize)] +pub struct PaymentIntentSearchParams<'a> { + pub query: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub limit: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub page: Option, + pub expand: &'a [&'a str], +} + +impl<'a> PaymentIntentSearchParams<'a> { + pub fn new() -> PaymentIntentSearchParams<'a> { + PaymentIntentSearchParams { query: String::new(), limit: None, page: None, expand: &[] } + } +} diff --git a/src/resources/price_ext.rs b/src/resources/price_ext.rs new file mode 100644 index 000000000..549943697 --- /dev/null +++ b/src/resources/price_ext.rs @@ -0,0 +1,29 @@ +use serde::Serialize; + +use crate::params::SearchList; +use crate::{Client, Price, Response}; + +#[derive(Clone, Debug, Default, Serialize)] +pub struct PriceSearchParams<'a> { + pub query: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub limit: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub page: Option, + pub expand: &'a [&'a str], +} + +impl<'a> PriceSearchParams<'a> { + pub fn new() -> PriceSearchParams<'a> { + PriceSearchParams { query: String::new(), limit: None, page: None, expand: &[] } + } +} + +impl Price { + /// Searches for a price. + /// + /// For more details see . + pub fn search(client: &Client, params: PriceSearchParams) -> Response> { + client.get_query("/prices/search", params) + } +} diff --git a/src/resources/product_ext.rs b/src/resources/product_ext.rs new file mode 100644 index 000000000..052136c92 --- /dev/null +++ b/src/resources/product_ext.rs @@ -0,0 +1,29 @@ +use serde::Serialize; + +use crate::params::SearchList; +use crate::{Client, Product, Response}; + +#[derive(Clone, Debug, Default, Serialize)] +pub struct ProductSearchParams<'a> { + pub query: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub limit: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub page: Option, + pub expand: &'a [&'a str], +} + +impl<'a> ProductSearchParams<'a> { + pub fn new() -> ProductSearchParams<'a> { + ProductSearchParams { query: String::new(), limit: None, page: None, expand: &[] } + } +} + +impl Product { + /// Searches for a product. + /// + /// For more details see . + pub fn search(client: &Client, params: ProductSearchParams) -> Response> { + client.get_query("/products/search", params) + } +} diff --git a/src/resources/subscription_ext.rs b/src/resources/subscription_ext.rs index ee0955853..c246564d5 100644 --- a/src/resources/subscription_ext.rs +++ b/src/resources/subscription_ext.rs @@ -2,6 +2,7 @@ use serde::Serialize; use crate::client::{Client, Response}; use crate::ids::SubscriptionId; +use crate::params::SearchList; use crate::resources::{CreateSubscriptionItems, Subscription}; #[derive(Clone, Debug, Default, Serialize)] @@ -16,6 +17,22 @@ impl CancelSubscription { } } +#[derive(Clone, Debug, Default, Serialize)] +pub struct SubscriptionSearchParams<'a> { + pub query: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub limit: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub page: Option, + pub expand: &'a [&'a str], +} + +impl<'a> SubscriptionSearchParams<'a> { + pub fn new() -> SubscriptionSearchParams<'a> { + SubscriptionSearchParams { query: String::new(), limit: None, page: None, expand: &[] } + } +} + impl Subscription { /// Cancels a subscription. /// @@ -27,6 +44,15 @@ impl Subscription { ) -> Response { client.delete_query(&format!("/subscriptions/{}", subscription_id), params) } + /// Searches for a subscription. + /// + /// For more details see . + pub fn search( + client: &Client, + params: SubscriptionSearchParams, + ) -> Response> { + client.get_query("/subscriptions/search", params) + } } impl CreateSubscriptionItems {