Skip to main content

spoo_me/
requests.rs

1use serde::{Deserialize, Serialize};
2use std::{collections::HashMap, fmt::Display};
3
4/// Response for URL-shortening endpoints (`/` and `/emoji`).
5#[derive(Debug, Serialize, Deserialize, Clone)]
6pub struct ShortenResponse {
7    /// The resulting shortened URL (full URL).
8    pub short_url: String,
9}
10
11/// Request payload for `POST /` (shorten URL).
12#[derive(Debug, Serialize, Default, Clone)]
13pub struct ShortenRequest {
14    pub(crate) url: String,
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub(crate) alias: Option<String>,
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub(crate) password: Option<String>,
19    #[serde(rename = "max-clicks", skip_serializing_if = "Option::is_none")]
20    pub(crate) max_clicks: Option<u32>,
21    #[serde(rename = "block-bots", skip_serializing_if = "Option::is_none")]
22    pub(crate) block_bots: Option<bool>,
23}
24
25impl ShortenRequest {
26    /// Creates a new ShortenRequest with the mandatory `url`.
27    pub fn new<U: Into<String>>(url: U) -> Self {
28        ShortenRequest {
29            url: url.into(),
30            ..Default::default()
31        }
32    }
33    /// Optional custom alias (must follow API rules)
34    pub fn alias<A: Into<String>>(mut self, alias: A) -> Self {
35        self.alias = Some(alias.into());
36        self
37    }
38    /// Optional password (must follow API rules).
39    pub fn password<P: Into<String>>(mut self, password: P) -> Self {
40        self.password = Some(password.into());
41        self
42    }
43    /// Optional max-clicks (must be positive).
44    pub fn max_clicks(mut self, max: u32) -> Self {
45        self.max_clicks = Some(max);
46        self
47    }
48    /// Optional block bots flag.
49    pub fn block_bots(mut self, flag: bool) -> Self {
50        self.block_bots = Some(flag);
51        self
52    }
53}
54
55/// Request payload for `POST /emoji` (uses emojis as slug).
56#[derive(Debug, Serialize, Default, Clone)]
57pub struct EmojiRequest {
58    pub(crate) url: String,
59    #[serde(rename = "emojies", skip_serializing_if = "Option::is_none")]
60    pub(crate) emojies: Option<String>,
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub(crate) password: Option<String>,
63    #[serde(rename = "max-clicks", skip_serializing_if = "Option::is_none")]
64    pub(crate) max_clicks: Option<u32>,
65    #[serde(rename = "block-bots", skip_serializing_if = "Option::is_none")]
66    pub(crate) block_bots: Option<bool>,
67}
68
69impl EmojiRequest {
70    /// Creates a new EmojiRequest with the mandatory `url`.
71    pub fn new<U: Into<String>>(url: U) -> Self {
72        EmojiRequest {
73            url: url.into(),
74            ..Default::default()
75        }
76    }
77    /// Optional emoji sequence (must follow API rules).
78    pub fn emojies<E: Into<String>>(mut self, seq: E) -> Self {
79        self.emojies = Some(seq.into());
80        self
81    }
82    /// Optional password (must follow API rules).
83    pub fn password<P: Into<String>>(mut self, password: P) -> Self {
84        self.password = Some(password.into());
85        self
86    }
87    /// Optional max-clicks (must be positive).
88    pub fn max_clicks(mut self, max: u32) -> Self {
89        self.max_clicks = Some(max);
90        self
91    }
92    /// Optional block bots flag.
93    pub fn block_bots(mut self, flag: bool) -> Self {
94        self.block_bots = Some(flag);
95        self
96    }
97}
98
99/// Response struct for `POST /emoji`, containing the shortened URL.
100#[derive(Debug, Serialize, Deserialize, Clone)]
101pub struct EmojiResponse {
102    /// The resulting shortened URL (full URL).
103    pub short_url: String,
104}
105
106/// Request payload for `POST /stats/{shortCode}`.
107#[derive(Debug, Serialize, Default, Clone)]
108pub struct StatsRequest {
109    #[serde(skip_serializing)]
110    pub(crate) short_code: String,
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub(crate) password: Option<String>,
113}
114
115impl StatsRequest {
116    /// Create a StatsRequest (optionally with password).
117    pub fn new(short_code: &str) -> Self {
118        StatsRequest {
119            short_code: short_code.to_string(),
120            password: None,
121        }
122    }
123    /// Optional password for accessing stats (if set on the short URL).
124    pub fn password<P: Into<String>>(mut self, password: P) -> Self {
125        self.password = Some(password.into());
126        self
127    }
128}
129
130/// Response struct for `POST /stats/{shortCode}`, containing URL statistics.
131#[derive(Debug, Serialize, Deserialize, Clone)]
132pub struct StatsResponse {
133    /// The code of the short URL.
134    pub short_code: String,
135    /// The original long URL.
136    pub url: String,
137    /// Total clicks since creation.
138    #[serde(rename = "total-clicks")]
139    pub total_clicks: u32,
140    /// Total unique clicks.
141    pub total_unique_clicks: u32,
142    /// Creation date (string) of the short link, if available.
143    #[serde(rename = "creation-date")]
144    pub creation_date: Option<String>,
145    /// Whether the link has expired.
146    pub expired: Option<bool>,
147    /// Last click timestamp (if any).
148    #[serde(rename = "last-click")]
149    pub last_click: Option<String>,
150    /// Last browser used.
151    #[serde(rename = "last-click-browser")]
152    pub last_click_browser: Option<String>,
153    /// Last OS used.
154    #[serde(rename = "last-click-os")]
155    pub last_click_os: Option<String>,
156    /// Max clicks allowed (if set).
157    #[serde(rename = "max-clicks")]
158    pub max_clicks: Option<u32>,
159    /// The password set on the short URL (if any).
160    pub password: Option<String>,
161    /// Whether bots were blocked.
162    pub block_bots: Option<bool>,
163    /// Click data per bot type.
164    pub bots: Option<HashMap<String, u32>>,
165    /// Click data per browser.
166    pub browser: Option<HashMap<String, u32>>,
167    /// Click data per country.
168    pub country: Option<HashMap<String, u32>>,
169    /// Clicks per day.
170    pub counter: Option<HashMap<String, u32>>,
171    /// Unique clicks per browser.
172    pub unique_browser: Option<HashMap<String, u32>>,
173    /// Unique clicks per country.
174    pub unique_country: Option<HashMap<String, u32>>,
175    /// Unique clicks per day.
176    pub unique_counter: Option<HashMap<String, u32>>,
177    /// Unique clicks per OS name.
178    pub unique_os_name: Option<HashMap<String, u32>>,
179    /// Unique clicks per referrer.
180    pub unique_referrer: Option<HashMap<String, u32>>,
181}
182
183/// Enum representing the available export formats.
184#[derive(Debug, Deserialize, Clone)]
185pub enum ExportFormat {
186    /// Export as JSON.
187    JSON,
188    /// Export as CSV, zipped together.
189    CSV,
190    /// Export as XLSX (Excel format).
191    XLSX,
192    /// Export as XML.
193    XML,
194}
195
196impl Display for ExportFormat {
197    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
198        match self {
199            ExportFormat::JSON => write!(f, "json"),
200            ExportFormat::CSV => write!(f, "csv"),
201            ExportFormat::XLSX => write!(f, "xlsx"),
202            ExportFormat::XML => write!(f, "xml"),
203        }
204    }
205}
206
207/// Request payload for `POST /export/{shortCode}/{exportFormat}`.
208#[derive(Debug, Serialize, Deserialize, Clone)]
209pub struct ExportRequest {
210    /// The short code of the URL to export.
211    #[serde(skip_serializing)]
212    pub(crate) short_code: String,
213    #[serde(skip_serializing)]
214    pub(crate) export_format: ExportFormat,
215    #[serde(skip_serializing_if = "Option::is_none")]
216    pub(crate) password: Option<String>,
217}
218
219impl ExportRequest {
220    /// Creates a new ExportRequest with the mandatory `short_code` and `export_format`.
221    pub fn new<S: Into<String>>(short_code: S, export_format: ExportFormat) -> Self {
222        ExportRequest {
223            short_code: short_code.into(),
224            export_format,
225            password: None,
226        }
227    }
228
229    /// Optional password for accessing the export (if set on the short URL).
230    pub fn password<P: Into<String>>(mut self, password: P) -> Self {
231        self.password = Some(password.into());
232        self
233    }
234}
235
236/// Implementation for creating an export request.
237#[derive(Debug, Clone)]
238pub struct ExportResponse {
239    /// The raw data returned
240    pub(crate) data: Vec<u8>,
241}
242
243impl ExportResponse {
244    /// Writes the export data to a file at the specified path.
245    pub fn save_to_file(&self, path: &str) -> std::io::Result<()> {
246        use std::fs::File;
247        use std::io::Write;
248
249        let mut file = File::create(path)?;
250        file.write_all(&self.data)?;
251        Ok(())
252    }
253
254    /// Returns the raw data of the export.
255    pub fn data(&self) -> &[u8] {
256        &self.data
257    }
258}