Skip to main content

polyoxide_gamma/api/
events.rs

1use polyoxide_core::{HttpClient, QueryBuilder, Request};
2
3use crate::{
4    error::GammaError,
5    types::{CountResponse, Event, EventCreator, EventsPagination, KeysetEventsResponse, Tag},
6};
7
8/// Events namespace for event-related operations
9#[derive(Clone)]
10pub struct Events {
11    pub(crate) http_client: HttpClient,
12}
13
14impl Events {
15    /// List events with optional filtering
16    pub fn list(&self) -> ListEvents {
17        ListEvents {
18            request: Request::new(self.http_client.clone(), "/events"),
19        }
20    }
21
22    /// Get an event by ID
23    pub fn get(&self, id: impl Into<String>) -> GetEvent {
24        GetEvent {
25            request: Request::new(
26                self.http_client.clone(),
27                format!("/events/{}", urlencoding::encode(&id.into())),
28            ),
29        }
30    }
31
32    /// Get an event by slug
33    pub fn get_by_slug(&self, slug: impl Into<String>) -> GetEvent {
34        GetEvent {
35            request: Request::new(
36                self.http_client.clone(),
37                format!("/events/slug/{}", urlencoding::encode(&slug.into())),
38            ),
39        }
40    }
41
42    /// Get tags for an event
43    pub fn tags(&self, id: impl Into<String>) -> Request<Vec<Tag>, GammaError> {
44        Request::new(
45            self.http_client.clone(),
46            format!("/events/{}/tags", urlencoding::encode(&id.into())),
47        )
48    }
49
50    /// Get tweet count for an event
51    pub fn tweet_count(&self, id: impl Into<String>) -> Request<CountResponse, GammaError> {
52        Request::new(
53            self.http_client.clone(),
54            format!("/events/{}/tweet-count", urlencoding::encode(&id.into())),
55        )
56    }
57
58    /// Get comment count for an event
59    pub fn comment_count(&self, id: impl Into<String>) -> Request<CountResponse, GammaError> {
60        Request::new(
61            self.http_client.clone(),
62            format!("/events/{}/comments/count", urlencoding::encode(&id.into())),
63        )
64    }
65
66    /// List event creators with optional filtering
67    /// (`GET /events/creators`).
68    pub fn list_creators(&self) -> ListEventCreators {
69        ListEventCreators {
70            request: Request::new(self.http_client.clone(), "/events/creators"),
71        }
72    }
73
74    /// Get an event creator by ID (`GET /events/creators/{id}`).
75    pub fn get_creator(&self, id: impl Into<String>) -> Request<EventCreator, GammaError> {
76        Request::new(
77            self.http_client.clone(),
78            format!("/events/creators/{}", urlencoding::encode(&id.into())),
79        )
80    }
81
82    /// List events with offset-style pagination metadata
83    /// (`GET /events/pagination`).
84    pub fn list_paginated(&self) -> ListPaginatedEvents {
85        ListPaginatedEvents {
86            request: Request::new(self.http_client.clone(), "/events/pagination"),
87        }
88    }
89
90    /// List sport event results (`GET /events/results`).
91    pub fn list_results(&self) -> ListEventResults {
92        ListEventResults {
93            request: Request::new(self.http_client.clone(), "/events/results"),
94        }
95    }
96
97    /// List events using cursor-based (keyset) pagination
98    /// (`GET /events/keyset`).
99    ///
100    /// Prefer this over [`Self::list`] for stable paging through large result
101    /// sets. Use `next_cursor` from each response as `after_cursor` in the
102    /// next request; pagination is complete when `next_cursor` is `None`.
103    ///
104    /// Note: a handful of obscure upstream query parameters
105    /// (`start_time_min/max`, `event_date`, `event_week`, `recurrence`,
106    /// `created_by`, `parent_event_id`, `include_children`, `partner_slug`,
107    /// `include_best_lines`, `locale`, `decimalized`, `tag_match`) are not
108    /// yet exposed. The majority of filters are available; callers needing
109    /// the omitted params can reach the endpoint directly via
110    /// [`Request::query`].
111    pub fn list_keyset(&self) -> ListKeysetEvents {
112        ListKeysetEvents {
113            request: Request::new(self.http_client.clone(), "/events/keyset"),
114        }
115    }
116}
117
118/// Request builder for [`Events::list_creators`].
119pub struct ListEventCreators {
120    request: Request<Vec<EventCreator>, GammaError>,
121}
122
123impl ListEventCreators {
124    /// Limit the number of results (minimum: 0).
125    pub fn limit(mut self, limit: u32) -> Self {
126        self.request = self.request.query("limit", limit);
127        self
128    }
129
130    /// Pagination offset (minimum: 0).
131    pub fn offset(mut self, offset: u32) -> Self {
132        self.request = self.request.query("offset", offset);
133        self
134    }
135
136    /// Comma-separated list of fields to order by.
137    pub fn order(mut self, order: impl Into<String>) -> Self {
138        self.request = self.request.query("order", order.into());
139        self
140    }
141
142    /// Sort direction.
143    pub fn ascending(mut self, ascending: bool) -> Self {
144        self.request = self.request.query("ascending", ascending);
145        self
146    }
147
148    /// Filter by creator name.
149    pub fn creator_name(mut self, name: impl Into<String>) -> Self {
150        self.request = self.request.query("creator_name", name.into());
151        self
152    }
153
154    /// Filter by creator handle.
155    pub fn creator_handle(mut self, handle: impl Into<String>) -> Self {
156        self.request = self.request.query("creator_handle", handle.into());
157        self
158    }
159
160    /// Execute the request.
161    pub async fn send(self) -> Result<Vec<EventCreator>, GammaError> {
162        self.request.send().await
163    }
164}
165
166/// Request builder for [`Events::list_paginated`].
167pub struct ListPaginatedEvents {
168    request: Request<EventsPagination, GammaError>,
169}
170
171impl ListPaginatedEvents {
172    /// Limit the number of results.
173    pub fn limit(mut self, limit: u32) -> Self {
174        self.request = self.request.query("limit", limit);
175        self
176    }
177
178    /// Pagination offset.
179    pub fn offset(mut self, offset: u32) -> Self {
180        self.request = self.request.query("offset", offset);
181        self
182    }
183
184    /// Comma-separated list of fields to order by.
185    pub fn order(mut self, order: impl Into<String>) -> Self {
186        self.request = self.request.query("order", order.into());
187        self
188    }
189
190    /// Sort direction.
191    pub fn ascending(mut self, ascending: bool) -> Self {
192        self.request = self.request.query("ascending", ascending);
193        self
194    }
195
196    /// Include chat data in response.
197    pub fn include_chat(mut self, include: bool) -> Self {
198        self.request = self.request.query("include_chat", include);
199        self
200    }
201
202    /// Include template data in response.
203    pub fn include_template(mut self, include: bool) -> Self {
204        self.request = self.request.query("include_template", include);
205        self
206    }
207
208    /// Filter by recurrence pattern.
209    pub fn recurrence(mut self, recurrence: impl Into<String>) -> Self {
210        self.request = self.request.query("recurrence", recurrence.into());
211        self
212    }
213
214    /// Execute the request.
215    pub async fn send(self) -> Result<EventsPagination, GammaError> {
216        self.request.send().await
217    }
218}
219
220/// Request builder for [`Events::list_results`].
221pub struct ListEventResults {
222    request: Request<Vec<Event>, GammaError>,
223}
224
225impl ListEventResults {
226    /// Limit the number of results.
227    pub fn limit(mut self, limit: u32) -> Self {
228        self.request = self.request.query("limit", limit);
229        self
230    }
231
232    /// Pagination offset.
233    pub fn offset(mut self, offset: u32) -> Self {
234        self.request = self.request.query("offset", offset);
235        self
236    }
237
238    /// Comma-separated list of fields to order by.
239    pub fn order(mut self, order: impl Into<String>) -> Self {
240        self.request = self.request.query("order", order.into());
241        self
242    }
243
244    /// Sort direction.
245    pub fn ascending(mut self, ascending: bool) -> Self {
246        self.request = self.request.query("ascending", ascending);
247        self
248    }
249
250    /// Execute the request.
251    pub async fn send(self) -> Result<Vec<Event>, GammaError> {
252        self.request.send().await
253    }
254}
255
256/// Request builder for [`Events::list_keyset`].
257pub struct ListKeysetEvents {
258    request: Request<KeysetEventsResponse, GammaError>,
259}
260
261impl ListKeysetEvents {
262    /// Maximum number of results to return (upstream max 500).
263    pub fn limit(mut self, limit: u32) -> Self {
264        self.request = self.request.query("limit", limit);
265        self
266    }
267
268    /// Comma-separated list of JSON field names to order by.
269    pub fn order(mut self, order: impl Into<String>) -> Self {
270        self.request = self.request.query("order", order.into());
271        self
272    }
273
274    /// Sort direction (used only when `order` is set).
275    pub fn ascending(mut self, ascending: bool) -> Self {
276        self.request = self.request.query("ascending", ascending);
277        self
278    }
279
280    /// Opaque cursor token returned as `next_cursor` from a previous response.
281    pub fn after_cursor(mut self, cursor: impl Into<String>) -> Self {
282        self.request = self.request.query("after_cursor", cursor.into());
283        self
284    }
285
286    /// Filter by specific event IDs.
287    pub fn id(mut self, ids: impl IntoIterator<Item = i64>) -> Self {
288        self.request = self.request.query_many("id", ids);
289        self
290    }
291
292    /// Filter by event slugs.
293    pub fn slug(mut self, slugs: impl IntoIterator<Item = impl ToString>) -> Self {
294        self.request = self.request.query_many("slug", slugs);
295        self
296    }
297
298    /// Filter by closed status.
299    pub fn closed(mut self, closed: bool) -> Self {
300        self.request = self.request.query("closed", closed);
301        self
302    }
303
304    /// Filter live events only.
305    pub fn live(mut self, live: bool) -> Self {
306        self.request = self.request.query("live", live);
307        self
308    }
309
310    /// Filter featured events only.
311    pub fn featured(mut self, featured: bool) -> Self {
312        self.request = self.request.query("featured", featured);
313        self
314    }
315
316    /// Search by event title substring.
317    pub fn title_search(mut self, query: impl Into<String>) -> Self {
318        self.request = self.request.query("title_search", query.into());
319        self
320    }
321
322    /// Filter by tag IDs.
323    pub fn tag_id(mut self, tag_ids: impl IntoIterator<Item = i64>) -> Self {
324        self.request = self.request.query_many("tag_id", tag_ids);
325        self
326    }
327
328    /// Filter by tag slug.
329    pub fn tag_slug(mut self, slug: impl Into<String>) -> Self {
330        self.request = self.request.query("tag_slug", slug.into());
331        self
332    }
333
334    /// Set minimum liquidity threshold.
335    pub fn liquidity_min(mut self, min: f64) -> Self {
336        self.request = self.request.query("liquidity_min", min);
337        self
338    }
339
340    /// Set maximum liquidity threshold.
341    pub fn liquidity_max(mut self, max: f64) -> Self {
342        self.request = self.request.query("liquidity_max", max);
343        self
344    }
345
346    /// Set minimum trading volume.
347    pub fn volume_min(mut self, min: f64) -> Self {
348        self.request = self.request.query("volume_min", min);
349        self
350    }
351
352    /// Set maximum trading volume.
353    pub fn volume_max(mut self, max: f64) -> Self {
354        self.request = self.request.query("volume_max", max);
355        self
356    }
357
358    /// Execute the request.
359    pub async fn send(self) -> Result<KeysetEventsResponse, GammaError> {
360        self.request.send().await
361    }
362}
363
364/// Request builder for getting a single event
365pub struct GetEvent {
366    request: Request<Event, GammaError>,
367}
368
369impl GetEvent {
370    /// Include chat data in response
371    pub fn include_chat(mut self, include: bool) -> Self {
372        self.request = self.request.query("include_chat", include);
373        self
374    }
375
376    /// Include template data in response
377    pub fn include_template(mut self, include: bool) -> Self {
378        self.request = self.request.query("include_template", include);
379        self
380    }
381
382    /// Execute the request
383    pub async fn send(self) -> Result<Event, GammaError> {
384        self.request.send().await
385    }
386}
387
388/// Request builder for listing events
389pub struct ListEvents {
390    request: Request<Vec<Event>, GammaError>,
391}
392
393impl ListEvents {
394    /// Set maximum number of results (minimum: 0)
395    pub fn limit(mut self, limit: u32) -> Self {
396        self.request = self.request.query("limit", limit);
397        self
398    }
399
400    /// Set pagination offset (minimum: 0)
401    pub fn offset(mut self, offset: u32) -> Self {
402        self.request = self.request.query("offset", offset);
403        self
404    }
405
406    /// Set order fields (comma-separated list)
407    pub fn order(mut self, order: impl Into<String>) -> Self {
408        self.request = self.request.query("order", order.into());
409        self
410    }
411
412    /// Set sort direction
413    pub fn ascending(mut self, ascending: bool) -> Self {
414        self.request = self.request.query("ascending", ascending);
415        self
416    }
417
418    /// Filter by specific event IDs
419    ///
420    /// Safe batch size: ≤ 400 per request. URLs over ~8 KB are rejected
421    /// upstream with `414 URI Too Long`.
422    pub fn id(mut self, ids: impl IntoIterator<Item = i64>) -> Self {
423        self.request = self.request.query_many("id", ids);
424        self
425    }
426
427    /// Filter by tag identifier
428    pub fn tag_id(mut self, tag_id: i64) -> Self {
429        self.request = self.request.query("tag_id", tag_id);
430        self
431    }
432
433    /// Exclude events with specified tag IDs
434    ///
435    /// Safe batch size: ≤ 500 per request. Tag IDs are short integers
436    /// (~5 B/entry); URLs over ~8 KB are rejected upstream with `414`.
437    pub fn exclude_tag_id(mut self, tag_ids: impl IntoIterator<Item = i64>) -> Self {
438        self.request = self.request.query_many("exclude_tag_id", tag_ids);
439        self
440    }
441
442    /// Filter by event slugs
443    ///
444    /// Safe batch size: ≤ 100 per request. URL length is capped at ~8 KB
445    /// upstream; slug entries vary so pick a cap based on your longest slug.
446    pub fn slug(mut self, slugs: impl IntoIterator<Item = impl ToString>) -> Self {
447        self.request = self.request.query_many("slug", slugs);
448        self
449    }
450
451    /// Filter by tag slug
452    pub fn tag_slug(mut self, slug: impl Into<String>) -> Self {
453        self.request = self.request.query("tag_slug", slug.into());
454        self
455    }
456
457    /// Include related tags in response
458    pub fn related_tags(mut self, include: bool) -> Self {
459        self.request = self.request.query("related_tags", include);
460        self
461    }
462
463    /// Filter active events only
464    pub fn active(mut self, active: bool) -> Self {
465        self.request = self.request.query("active", active);
466        self
467    }
468
469    /// Filter archived events
470    pub fn archived(mut self, archived: bool) -> Self {
471        self.request = self.request.query("archived", archived);
472        self
473    }
474
475    /// Filter featured events
476    pub fn featured(mut self, featured: bool) -> Self {
477        self.request = self.request.query("featured", featured);
478        self
479    }
480
481    /// Filter create-your-own-market events
482    pub fn cyom(mut self, cyom: bool) -> Self {
483        self.request = self.request.query("cyom", cyom);
484        self
485    }
486
487    /// Include chat data in response
488    pub fn include_chat(mut self, include: bool) -> Self {
489        self.request = self.request.query("include_chat", include);
490        self
491    }
492
493    /// Include template data
494    pub fn include_template(mut self, include: bool) -> Self {
495        self.request = self.request.query("include_template", include);
496        self
497    }
498
499    /// Filter by recurrence pattern
500    pub fn recurrence(mut self, recurrence: impl Into<String>) -> Self {
501        self.request = self.request.query("recurrence", recurrence.into());
502        self
503    }
504
505    /// Filter closed events
506    pub fn closed(mut self, closed: bool) -> Self {
507        self.request = self.request.query("closed", closed);
508        self
509    }
510
511    /// Set minimum liquidity threshold
512    pub fn liquidity_min(mut self, min: f64) -> Self {
513        self.request = self.request.query("liquidity_min", min);
514        self
515    }
516
517    /// Set maximum liquidity threshold
518    pub fn liquidity_max(mut self, max: f64) -> Self {
519        self.request = self.request.query("liquidity_max", max);
520        self
521    }
522
523    /// Set minimum trading volume
524    pub fn volume_min(mut self, min: f64) -> Self {
525        self.request = self.request.query("volume_min", min);
526        self
527    }
528
529    /// Set maximum trading volume
530    pub fn volume_max(mut self, max: f64) -> Self {
531        self.request = self.request.query("volume_max", max);
532        self
533    }
534
535    /// Set earliest start date (ISO 8601 format)
536    pub fn start_date_min(mut self, date: impl Into<String>) -> Self {
537        self.request = self.request.query("start_date_min", date.into());
538        self
539    }
540
541    /// Set latest start date (ISO 8601 format)
542    pub fn start_date_max(mut self, date: impl Into<String>) -> Self {
543        self.request = self.request.query("start_date_max", date.into());
544        self
545    }
546
547    /// Set earliest end date (ISO 8601 format)
548    pub fn end_date_min(mut self, date: impl Into<String>) -> Self {
549        self.request = self.request.query("end_date_min", date.into());
550        self
551    }
552
553    /// Set latest end date (ISO 8601 format)
554    pub fn end_date_max(mut self, date: impl Into<String>) -> Self {
555        self.request = self.request.query("end_date_max", date.into());
556        self
557    }
558
559    /// Execute the request
560    pub async fn send(self) -> Result<Vec<Event>, GammaError> {
561        self.request.send().await
562    }
563}
564
565#[cfg(test)]
566mod tests {
567    use crate::Gamma;
568
569    fn gamma() -> Gamma {
570        Gamma::new().unwrap()
571    }
572
573    /// Verify that all event builder methods chain correctly
574    #[test]
575    fn test_list_events_full_chain() {
576        let _list = gamma()
577            .events()
578            .list()
579            .limit(10)
580            .offset(20)
581            .order("volume")
582            .ascending(true)
583            .id(vec![1i64, 2])
584            .tag_id(42)
585            .exclude_tag_id(vec![99i64])
586            .slug(vec!["slug-a"])
587            .tag_slug("politics")
588            .related_tags(true)
589            .active(true)
590            .archived(false)
591            .featured(true)
592            .cyom(false)
593            .include_chat(true)
594            .include_template(false)
595            .recurrence("daily")
596            .closed(false)
597            .liquidity_min(1000.0)
598            .liquidity_max(50000.0)
599            .volume_min(100.0)
600            .volume_max(10000.0)
601            .start_date_min("2024-01-01")
602            .start_date_max("2025-01-01")
603            .end_date_min("2024-06-01")
604            .end_date_max("2025-12-31");
605    }
606
607    #[test]
608    fn test_get_event_accepts_str_and_string() {
609        let _req1 = gamma().events().get("evt-123");
610        let _req2 = gamma().events().get(String::from("evt-123"));
611    }
612
613    #[test]
614    fn test_get_by_slug_accepts_str_and_string() {
615        let _req1 = gamma().events().get_by_slug("slug");
616        let _req2 = gamma().events().get_by_slug(String::from("slug"));
617    }
618
619    #[test]
620    fn test_get_event_with_query_params() {
621        let _req = gamma()
622            .events()
623            .get("evt-123")
624            .include_chat(true)
625            .include_template(false);
626    }
627
628    #[test]
629    fn test_event_tags_accepts_str_and_string() {
630        let _req1 = gamma().events().tags("evt-123");
631        let _req2 = gamma().events().tags(String::from("evt-123"));
632    }
633
634    #[test]
635    fn test_event_tweet_count() {
636        let _req = gamma().events().tweet_count("evt-123");
637    }
638
639    #[test]
640    fn test_event_comment_count() {
641        let _req = gamma().events().comment_count("evt-123");
642    }
643
644    #[test]
645    fn test_list_creators_full_chain() {
646        let _req = gamma()
647            .events()
648            .list_creators()
649            .limit(10)
650            .offset(0)
651            .order("createdAt")
652            .ascending(true)
653            .creator_name("poly")
654            .creator_handle("polymarket");
655    }
656
657    #[test]
658    fn test_get_creator_accepts_str_and_string() {
659        let _req1 = gamma().events().get_creator("c-1");
660        let _req2 = gamma().events().get_creator(String::from("c-1"));
661    }
662
663    #[test]
664    fn test_list_paginated_full_chain() {
665        let _req = gamma()
666            .events()
667            .list_paginated()
668            .limit(25)
669            .offset(50)
670            .order("startDate")
671            .ascending(false)
672            .include_chat(false)
673            .include_template(true)
674            .recurrence("daily");
675    }
676
677    #[test]
678    fn test_list_results_full_chain() {
679        let _req = gamma()
680            .events()
681            .list_results()
682            .limit(5)
683            .offset(0)
684            .order("endDate")
685            .ascending(true);
686    }
687
688    #[test]
689    fn test_list_keyset_full_chain() {
690        let _req = gamma()
691            .events()
692            .list_keyset()
693            .limit(50)
694            .order("volume_num")
695            .ascending(true)
696            .after_cursor("abc")
697            .id(vec![1i64, 2])
698            .slug(vec!["slug-a"])
699            .closed(false)
700            .live(true)
701            .featured(true)
702            .title_search("bitcoin")
703            .tag_id(vec![42i64])
704            .tag_slug("politics")
705            .liquidity_min(0.0)
706            .liquidity_max(1e6)
707            .volume_min(0.0)
708            .volume_max(1e6);
709    }
710}