pinpayments/
params.rs

1use std::collections::HashMap;
2use futures::{stream::Stream};
3use futures_util::FutureExt;
4use std::pin::Pin;
5
6use serde::{Deserialize, Serialize};
7
8use crate::error::PinError;
9use crate::{
10    client::{Response},
11};
12
13#[derive(Clone, Debug, Default)]
14pub struct AppInfo {
15    pub name: String,
16    pub url: Option<String>,
17    pub version: Option<String>,
18}
19
20impl ToString for AppInfo {
21    fn to_string(&self) -> String {
22        match (&self.version, &self.url) {
23            (Some(a), Some(b)) => format!("{}/{} ({})", &self.name, a, b),
24            (Some(a), None) => format!("{}/{}", &self.name, a),
25            (None, Some(b)) => format!("{} ({})", &self.name, b),
26            _ => self.name.to_string(),
27        }
28    }
29}
30
31#[derive(Clone, Debug)]
32pub struct Headers {
33    pub user_agent: String,
34}
35
36impl Headers {
37    pub fn to_array(&self) -> [(&str, Option<&str>); 1] {
38        [
39            ("User-Agent", Some(&self.user_agent)),
40        ]
41    }
42}
43
44#[derive(Debug, serde::Deserialize)]
45pub struct Single<T> {
46    pub response: T
47}
48
49pub type Paginator<'a, T> = Pin<Box<dyn Stream<Item = T> + 'a + Send>>;
50
51pub fn paginate<'a, T, Request>(req: Request, per_page: u32) -> Paginator<'a, Result<T, PinError>>
52where
53    T: 'a + Unpin + Send,
54    Request: 'a + Fn(u32, u32) -> Response<Page<T>> + Send,
55{
56    use async_stream::stream;
57    let mut page_n = 0;
58    Box::pin(stream! {
59        loop {
60            let request = req(page_n, per_page);
61            let page = request.await?;
62            for item in page.items {
63                yield Ok(item);
64            }
65            if page.pagination.next.is_none() {
66                break;
67            }
68            page_n += 1
69        }
70    })
71}
72
73#[derive(Debug, Clone, Deserialize, Serialize)]
74pub struct PaginationDetails {
75    pub current: u64,
76    pub previous: Option<u64>,
77    pub next: Option<u64>,
78    pub per_page: u32,
79    pub pages: Option<u32>,
80    pub count: u64
81}
82
83/// A single page of a paginated list of objects.
84#[derive(Debug, Deserialize, Serialize)]
85pub struct Page<T> {
86    #[serde(rename = "response")]
87    pub items: Vec<T>,
88    pub pagination: PaginationDetails
89}
90
91pub type Metadata = HashMap<String, String>;
92
93#[derive(Clone, Debug, Deserialize, Serialize)]
94#[serde(rename_all = "lowercase")]
95pub struct RangeBounds<T> {
96    pub gt: Option<T>,
97    pub gte: Option<T>,
98    pub lt: Option<T>,
99    pub lte: Option<T>,
100}
101
102#[derive(Debug, Serialize)]
103pub enum SortDirection {
104    Asc = 1,
105    Desc = -1
106}
107
108impl<T> Default for RangeBounds<T> {
109    fn default() -> Self {
110        RangeBounds { gt: None, gte: None, lt: None, lte: None }
111    }
112}
113
114pub fn to_snakecase(camel: &str) -> String {
115    let mut i = 0;
116    let mut snake = String::new();
117    let mut chars = camel.chars().peekable();
118    while let Some(ch) = chars.next() {
119        if ch.is_uppercase() {
120            if i > 0 && !chars.peek().unwrap_or(&'A').is_uppercase() {
121                snake.push('_');
122            }
123            snake.push(ch.to_lowercase().next().unwrap_or(ch));
124        } else {
125            snake.push(ch);
126        }
127        i += 1;
128    }
129
130    snake
131}
132
133pub fn unpack_contained<T: 'static>(container_response: Response<Single<T>>) -> Response<T> {
134    Box::pin(container_response.map(|pb| pb.map(|single| single.response)))
135}
136
137#[cfg(test)]
138mod tests {
139    #[test]
140    fn to_snakecase() {
141        use super::to_snakecase;
142
143        assert_eq!(to_snakecase("snake_case").as_str(), "snake_case");
144        assert_eq!(to_snakecase("CamelCase").as_str(), "camel_case");
145        assert_eq!(to_snakecase("XMLHttpRequest").as_str(), "xml_http_request");
146        assert_eq!(to_snakecase("UPPER").as_str(), "upper");
147        assert_eq!(to_snakecase("lower").as_str(), "lower");
148    }
149}