Skip to main content

polyoxide_gamma/api/
series.rs

1use polyoxide_core::{HttpClient, QueryBuilder, Request};
2
3use crate::{
4    error::GammaError,
5    types::{CountResponse, SeriesData, SeriesSummary},
6};
7
8/// Series namespace for series-related operations
9#[derive(Clone)]
10pub struct Series {
11    pub(crate) http_client: HttpClient,
12}
13
14impl Series {
15    /// List series with optional filtering
16    pub fn list(&self) -> ListSeries {
17        ListSeries {
18            request: Request::new(self.http_client.clone(), "/series"),
19        }
20    }
21
22    /// Get a series by ID
23    pub fn get(&self, id: impl Into<String>) -> GetSeries {
24        GetSeries {
25            request: Request::new(
26                self.http_client.clone(),
27                format!("/series/{}", urlencoding::encode(&id.into())),
28            ),
29        }
30    }
31
32    /// Get series summary by ID (`GET /series-summary/{id}`).
33    pub fn get_summary(&self, id: impl Into<String>) -> Request<SeriesSummary, GammaError> {
34        Request::new(
35            self.http_client.clone(),
36            format!("/series-summary/{}", urlencoding::encode(&id.into())),
37        )
38    }
39
40    /// Get series summary by slug (`GET /series-summary/slug/{slug}`).
41    pub fn get_summary_by_slug(
42        &self,
43        slug: impl Into<String>,
44    ) -> Request<SeriesSummary, GammaError> {
45        Request::new(
46            self.http_client.clone(),
47            format!("/series-summary/slug/{}", urlencoding::encode(&slug.into())),
48        )
49    }
50
51    /// Get comment count for a series (`GET /series/{id}/comments/count`).
52    pub fn comment_count(&self, id: impl Into<String>) -> Request<CountResponse, GammaError> {
53        Request::new(
54            self.http_client.clone(),
55            format!("/series/{}/comments/count", urlencoding::encode(&id.into())),
56        )
57    }
58}
59
60/// Request builder for getting a single series
61pub struct GetSeries {
62    request: Request<SeriesData, GammaError>,
63}
64
65impl GetSeries {
66    /// Include chat data in response
67    pub fn include_chat(mut self, include: bool) -> Self {
68        self.request = self.request.query("include_chat", include);
69        self
70    }
71
72    /// Execute the request
73    pub async fn send(self) -> Result<SeriesData, GammaError> {
74        self.request.send().await
75    }
76}
77
78/// Request builder for listing series
79pub struct ListSeries {
80    request: Request<Vec<SeriesData>, GammaError>,
81}
82
83impl ListSeries {
84    /// Limit the number of results
85    pub fn limit(mut self, limit: u32) -> Self {
86        self.request = self.request.query("limit", limit);
87        self
88    }
89
90    /// Offset the results
91    pub fn offset(mut self, offset: u32) -> Self {
92        self.request = self.request.query("offset", offset);
93        self
94    }
95
96    /// Sort in ascending order
97    pub fn ascending(mut self, ascending: bool) -> Self {
98        self.request = self.request.query("ascending", ascending);
99        self
100    }
101
102    /// Filter by closed status
103    pub fn closed(mut self, closed: bool) -> Self {
104        self.request = self.request.query("closed", closed);
105        self
106    }
107
108    /// Filter by slugs
109    ///
110    /// Safe batch size: ≤ 150 per request. URL length is capped at ~8 KB
111    /// upstream; slug entries vary so pick a cap based on your longest slug.
112    pub fn slug(mut self, slugs: impl IntoIterator<Item = impl ToString>) -> Self {
113        self.request = self.request.query_many("slug", slugs);
114        self
115    }
116
117    /// Filter by category IDs
118    ///
119    /// Safe batch size: ≤ 100 per request. URLs over ~8 KB are rejected
120    /// upstream with `414 URI Too Long`.
121    pub fn categories_ids(mut self, ids: impl IntoIterator<Item = impl ToString>) -> Self {
122        self.request = self.request.query_many("categories_ids", ids);
123        self
124    }
125
126    /// Filter by category labels
127    ///
128    /// Safe batch size: ≤ 150 per request. URL length is capped at ~8 KB
129    /// upstream; label entries vary so pick a cap based on your longest label.
130    pub fn categories_labels(mut self, labels: impl IntoIterator<Item = impl ToString>) -> Self {
131        self.request = self.request.query_many("categories_labels", labels);
132        self
133    }
134
135    /// Include chat data in response
136    pub fn include_chat(mut self, include: bool) -> Self {
137        self.request = self.request.query("include_chat", include);
138        self
139    }
140
141    /// Filter by recurrence pattern
142    pub fn recurrence(mut self, recurrence: impl Into<String>) -> Self {
143        self.request = self.request.query("recurrence", recurrence.into());
144        self
145    }
146
147    /// Comma-separated list of JSON field names to order by.
148    pub fn order(mut self, order: impl Into<String>) -> Self {
149        self.request = self.request.query("order", order.into());
150        self
151    }
152
153    /// Omit the nested `events` relation from each series.
154    pub fn exclude_events(mut self, exclude: bool) -> Self {
155        self.request = self.request.query("exclude_events", exclude);
156        self
157    }
158
159    /// Execute the request
160    pub async fn send(self) -> Result<Vec<SeriesData>, GammaError> {
161        self.request.send().await
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use crate::Gamma;
168
169    fn gamma() -> Gamma {
170        Gamma::new().unwrap()
171    }
172
173    #[test]
174    fn test_list_series_full_chain() {
175        let _req = gamma()
176            .series()
177            .list()
178            .limit(10)
179            .offset(0)
180            .ascending(true)
181            .closed(false)
182            .slug(vec!["nfl-2025"])
183            .categories_ids(vec!["1", "2"])
184            .categories_labels(vec!["Sports"])
185            .include_chat(true)
186            .recurrence("weekly");
187    }
188
189    #[test]
190    fn test_get_series_with_include_chat() {
191        let _req = gamma().series().get("s-123").include_chat(true);
192    }
193
194    #[test]
195    fn test_get_summary_accepts_str_and_string() {
196        let _r1 = gamma().series().get_summary("s-1");
197        let _r2 = gamma().series().get_summary(String::from("s-1"));
198    }
199
200    #[test]
201    fn test_get_summary_by_slug_accepts_str_and_string() {
202        let _r1 = gamma().series().get_summary_by_slug("nfl-2025");
203        let _r2 = gamma()
204            .series()
205            .get_summary_by_slug(String::from("nfl-2025"));
206    }
207
208    #[test]
209    fn test_comment_count_accepts_str_and_string() {
210        let _r1 = gamma().series().comment_count("s-1");
211        let _r2 = gamma().series().comment_count(String::from("s-1"));
212    }
213}