1use std::collections::HashMap;
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6#[cfg_attr(feature = "specta", derive(specta::Type))]
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
9#[serde(rename_all = "camelCase")]
10pub struct Market {
11 pub id: String,
12 pub condition_id: String,
13 #[serde(rename = "questionID")]
14 pub question_id: Option<String>,
15 pub slug: Option<String>,
16 #[serde(default)]
17 pub tokens: Vec<MarketToken>,
18 #[cfg_attr(feature = "specta", specta(type = Option<HashMap<String, String>>))]
19 pub rewards: Option<HashMap<String, serde_json::Value>>,
20 pub minimum_order_size: Option<String>,
21 pub minimum_tick_size: Option<String>,
22 pub description: String,
23 pub category: Option<String>,
24 pub end_date_iso: Option<String>,
25 pub start_date_iso: Option<String>,
26 pub question: String,
27 pub min_incentive_size: Option<String>,
28 pub max_incentive_spread: Option<String>,
29 #[serde(rename = "submitted_by")]
30 pub submitted_by: Option<String>,
31 #[serde(rename = "volume24hr")] pub volume_24hr: Option<f64>,
33 #[serde(rename = "volume1wk")] pub volume_1wk: Option<f64>,
35 #[serde(rename = "volume1mo")] pub volume_1mo: Option<f64>,
37 #[serde(rename = "volume1yr")] pub volume_1yr: Option<f64>,
39 pub liquidity: Option<String>,
40 #[serde(default)]
41 pub tags: Vec<Tag>,
42 pub neg_risk: Option<bool>,
43 pub neg_risk_market_id: Option<String>,
44 pub neg_risk_request_id: Option<String>,
45 #[cfg_attr(feature = "specta", specta(type = Option<f64>))]
47 pub comment_count: Option<i64>,
48 pub twitter_card_image: Option<String>,
49 pub resolution_source: Option<String>,
50 pub amm_type: Option<String>,
51 pub sponsor_name: Option<String>,
52 pub sponsor_image: Option<String>,
53 pub x_axis_value: Option<String>,
54 pub y_axis_value: Option<String>,
55 #[serde(rename = "denomationToken")]
56 pub denomination_token: Option<String>,
57 pub fee: Option<String>,
58 pub image: Option<String>,
59 pub icon: Option<String>,
60 pub lower_bound: Option<String>,
61 pub upper_bound: Option<String>,
62 pub outcomes: Option<String>,
63 pub outcome_prices: Option<String>,
64 pub volume: Option<String>,
65 pub active: Option<bool>,
66 pub market_type: Option<String>,
67 pub format_type: Option<String>,
68 pub lower_bound_date: Option<String>,
69 pub upper_bound_date: Option<String>,
70 pub closed: Option<bool>,
71 pub market_maker_address: String,
72 #[cfg_attr(feature = "specta", specta(type = Option<f64>))]
73 pub created_by: Option<i64>,
74 #[cfg_attr(feature = "specta", specta(type = Option<f64>))]
75 pub updated_by: Option<i64>,
76 pub created_at: Option<String>,
77 pub updated_at: Option<String>,
78 pub closed_time: Option<String>,
79 pub wide_format: Option<bool>,
80 pub new: Option<bool>,
81 pub mailchimp_tag: Option<String>,
82 pub featured: Option<bool>,
83 pub archived: Option<bool>,
84 pub resolved_by: Option<String>,
85 pub restricted: Option<bool>,
86 #[cfg_attr(feature = "specta", specta(type = Option<f64>))]
87 pub market_group: Option<i64>,
88 pub group_item_title: Option<String>,
89 pub group_item_threshold: Option<String>,
90 pub uma_end_date: Option<String>,
91 pub uma_resolution_status: Option<String>,
92 pub uma_end_date_iso: Option<String>,
93 pub uma_resolution_statuses: Option<String>,
94 pub enable_order_book: Option<bool>,
95 pub order_price_min_tick_size: Option<f64>,
96 pub order_min_size: Option<f64>,
97 #[cfg_attr(feature = "specta", specta(type = Option<f64>))]
98 pub curation_order: Option<i64>,
99 pub volume_num: Option<f64>,
100 pub liquidity_num: Option<f64>,
101 pub has_reviewed_dates: Option<bool>,
102 pub ready_for_cron: Option<bool>,
103 pub comments_enabled: Option<bool>,
104 pub game_start_time: Option<String>,
105 #[cfg_attr(feature = "specta", specta(type = Option<f64>))]
106 pub seconds_delay: Option<i64>,
107 pub clob_token_ids: Option<String>,
108 pub disqus_thread: Option<String>,
109 pub short_outcomes: Option<String>,
110 pub team_aid: Option<String>,
111 pub team_bid: Option<String>,
112 pub uma_bond: Option<String>,
113 pub uma_reward: Option<String>,
114 pub fpmm_live: Option<bool>,
115 #[serde(rename = "volume24hrAmm")] pub volume_24hr_amm: Option<f64>,
117 #[serde(rename = "volume1wkAmm")]
118 pub volume_1wk_amm: Option<f64>,
119 #[serde(rename = "volume1moAmm")]
120 pub volume_1mo_amm: Option<f64>,
121 #[serde(rename = "volume1yrAmm")]
122 pub volume_1yr_amm: Option<f64>,
123 #[serde(rename = "volume24hrClob")]
124 pub volume_24hr_clob: Option<f64>,
125 #[serde(rename = "volume1wkClob")]
126 pub volume_1wk_clob: Option<f64>,
127 #[serde(rename = "volume1moClob")]
128 pub volume_1mo_clob: Option<f64>,
129 #[serde(rename = "volume1yrClob")]
130 pub volume_1yr_clob: Option<f64>,
131 pub volume_amm: Option<f64>,
132 pub volume_clob: Option<f64>,
133 pub liquidity_amm: Option<f64>,
134 pub liquidity_clob: Option<f64>,
135 #[cfg_attr(feature = "specta", specta(type = Option<f64>))]
136 pub maker_base_fee: Option<i64>,
137 #[cfg_attr(feature = "specta", specta(type = Option<f64>))]
138 pub taker_base_fee: Option<i64>,
139 #[cfg_attr(feature = "specta", specta(type = Option<f64>))]
140 pub custom_liveness: Option<i64>,
141 pub accepting_orders: Option<bool>,
142 pub notifications_enabled: Option<bool>,
143 #[cfg_attr(feature = "specta", specta(type = Option<f64>))]
144 pub score: Option<i64>,
145 pub creator: Option<String>,
146 pub ready: Option<bool>,
147 pub funded: Option<bool>,
148 pub past_slugs: Option<String>,
149 pub ready_timestamp: Option<String>,
150 pub funded_timestamp: Option<String>,
151 pub accepting_orders_timestamp: Option<String>,
152 pub competitive: Option<f64>,
153 pub rewards_min_size: Option<f64>,
154 pub rewards_max_spread: Option<f64>,
155 pub spread: Option<f64>,
156 pub automatically_resolved: Option<bool>,
157 pub automatically_active: Option<bool>,
158 pub one_day_price_change: Option<f64>,
159 pub one_hour_price_change: Option<f64>,
160 pub one_week_price_change: Option<f64>,
161 pub one_month_price_change: Option<f64>,
162 pub one_year_price_change: Option<f64>,
163 pub last_trade_price: Option<f64>,
164 pub best_bid: Option<f64>,
165 pub best_ask: Option<f64>,
166 pub clear_book_on_start: Option<bool>,
167 pub chart_color: Option<String>,
168 pub series_color: Option<String>,
169 pub show_gmp_series: Option<bool>,
170 pub show_gmp_outcome: Option<bool>,
171 pub manual_activation: Option<bool>,
172 pub neg_risk_other: Option<bool>,
173 pub game_id: Option<String>,
174 pub group_item_range: Option<String>,
175 pub sports_market_type: Option<String>,
176 pub line: Option<f64>,
177 pub pending_deployment: Option<bool>,
178 pub deploying: Option<bool>,
179 pub deploying_timestamp: Option<String>,
180 pub schedule_deployment_timestamp: Option<String>,
181 pub rfq_enabled: Option<bool>,
182 pub event_start_time: Option<String>,
183}
184
185#[cfg_attr(feature = "specta", derive(specta::Type))]
187#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
188#[serde(rename_all = "camelCase")]
189pub struct MarketToken {
190 pub token_id: String,
191 pub outcome: String,
192 pub price: Option<String>,
193 pub winner: Option<bool>,
194}
195
196#[cfg_attr(feature = "specta", derive(specta::Type))]
197#[derive(Debug, Clone, Serialize, Deserialize)]
198#[serde(rename_all = "camelCase")]
199pub struct Event {
200 pub id: String,
201 pub ticker: Option<String>,
202 pub slug: Option<String>,
203 pub title: Option<String>,
204 pub subtitle: Option<String>,
205 pub description: Option<String>,
206 pub resolution_source: Option<String>,
207 pub start_date: Option<String>,
208 pub creation_date: Option<String>,
209 pub end_date: Option<String>,
210 pub image: Option<String>,
211 pub icon: Option<String>,
212 pub start_date_iso: Option<String>,
213 pub end_date_iso: Option<String>,
214 pub active: Option<bool>,
215 pub closed: Option<bool>,
216 pub archived: Option<bool>,
217 pub new: Option<bool>,
218 pub featured: Option<bool>,
219 pub restricted: Option<bool>,
220 pub liquidity: Option<f64>,
221 pub open_interest: Option<f64>,
222 pub sort_by: Option<String>,
223 pub category: Option<String>,
224 pub subcategory: Option<String>,
225 pub is_template: Option<bool>,
226 pub template_variables: Option<String>,
227 #[serde(rename = "published_at")]
228 pub published_at: Option<String>,
229 pub created_by: Option<String>,
230 pub updated_by: Option<String>,
231 pub created_at: Option<String>,
232 pub updated_at: Option<String>,
233 pub comments_enabled: Option<bool>,
234 pub competitive: Option<f64>,
235 #[serde(rename = "volume24hr")]
236 pub volume_24hr: Option<f64>,
237 #[serde(rename = "volume1wk")]
238 pub volume_1wk: Option<f64>,
239 #[serde(rename = "volume1mo")]
240 pub volume_1mo: Option<f64>,
241 #[serde(rename = "volume1yr")]
242 pub volume_1yr: Option<f64>,
243 pub featured_image: Option<String>,
244 pub disqus_thread: Option<String>,
245 pub parent_event: Option<String>,
246 pub enable_order_book: Option<bool>,
247 pub liquidity_amm: Option<f64>,
248 pub liquidity_clob: Option<f64>,
249 pub neg_risk: Option<bool>,
250 pub neg_risk_market_id: Option<String>,
251 #[cfg_attr(feature = "specta", specta(type = Option<f64>))]
252 pub neg_risk_fee_bips: Option<i64>,
253 #[serde(default)]
254 pub sub_events: Vec<String>,
255 #[serde(default)]
256 pub markets: Vec<Market>,
257 #[serde(default)]
258 pub tags: Vec<Tag>,
259 #[serde(default)]
260 pub series: Vec<SeriesInfo>,
261 pub cyom: Option<bool>,
262 pub closed_time: Option<String>,
263 pub show_all_outcomes: Option<bool>,
264 pub show_market_images: Option<bool>,
265 pub automatically_resolved: Option<bool>,
266 #[serde(rename = "enableNegRisk")]
267 pub enable_neg_risk: Option<bool>,
268 pub automatically_active: Option<bool>,
269 pub event_date: Option<String>,
270 pub start_time: Option<String>,
271 #[cfg_attr(feature = "specta", specta(type = Option<f64>))]
272 pub event_week: Option<i64>,
273 pub series_slug: Option<String>,
274 pub score: Option<String>,
275 pub elapsed: Option<String>,
276 pub period: Option<String>,
277 pub live: Option<bool>,
278 pub ended: Option<bool>,
279 pub finished_timestamp: Option<String>,
280 pub gmp_chart_mode: Option<String>,
281 #[cfg_attr(feature = "specta", specta(type = Option<f64>))]
282 pub tweet_count: Option<i64>,
283 #[cfg_attr(feature = "specta", specta(type = Option<f64>))]
284 pub featured_order: Option<i64>,
285 pub estimate_value: Option<bool>,
286 pub cant_estimate: Option<bool>,
287 pub spreads_main_line: Option<f64>,
288 pub totals_main_line: Option<f64>,
289 pub carousel_map: Option<String>,
290 pub pending_deployment: Option<bool>,
291 pub deploying: Option<bool>,
292 pub deploying_timestamp: Option<String>,
293 pub schedule_deployment_timestamp: Option<String>,
294 pub game_status: Option<String>,
295}
296
297#[cfg_attr(feature = "specta", derive(specta::Type))]
299#[derive(Debug, Clone, Serialize, Deserialize)]
300#[serde(rename_all = "camelCase")]
301pub struct SeriesInfo {
302 pub id: String,
303 pub slug: String,
304 pub title: String,
305 pub ticker: Option<String>,
306 pub series_type: Option<String>,
307 pub recurrence: Option<String>,
308 pub image: Option<String>,
309 pub icon: Option<String>,
310 pub layout: Option<String>,
311 pub active: Option<bool>,
312 pub closed: Option<bool>,
313 pub archived: Option<bool>,
314 pub new: Option<bool>,
315 pub featured: Option<bool>,
316 pub restricted: Option<bool>,
317 pub published_at: Option<String>,
318 pub created_by: Option<String>,
319 pub updated_by: Option<String>,
320 pub created_at: Option<String>,
321 pub updated_at: Option<String>,
322 pub comments_enabled: Option<bool>,
323 pub competitive: Option<String>,
324 #[serde(rename = "volume24hr")]
325 pub volume_24hr: Option<f64>,
326 pub start_date: Option<String>,
327 #[cfg_attr(feature = "specta", specta(type = Option<f64>))]
328 pub comment_count: Option<i64>,
329 pub requires_translation: Option<bool>,
330}
331
332#[cfg_attr(feature = "specta", derive(specta::Type))]
334#[derive(Debug, Clone, Serialize, Deserialize)]
335#[serde(rename_all = "camelCase")]
336pub struct SeriesData {
337 pub id: String,
338 pub slug: String,
339 pub title: String,
340 pub description: Option<String>,
341 pub image: Option<String>,
342 pub icon: Option<String>,
343 pub active: bool,
344 pub closed: bool,
345 pub archived: bool,
346 #[serde(default)]
347 pub tags: Vec<String>,
348 pub volume: Option<f64>,
349 pub liquidity: Option<f64>,
350 #[serde(default)]
351 pub events: Vec<Event>,
352 pub competitive: Option<String>,
353}
354
355#[cfg_attr(feature = "specta", derive(specta::Type))]
362#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
363pub struct SeriesSummary {
364 pub id: String,
365 pub title: Option<String>,
366 pub slug: Option<String>,
367 #[serde(rename = "eventDates", default)]
368 pub event_dates: Vec<String>,
369 #[cfg_attr(feature = "specta", specta(type = Vec<f64>))]
370 #[serde(rename = "eventWeeks", default)]
371 pub event_weeks: Vec<i64>,
372 #[cfg_attr(feature = "specta", specta(type = Option<f64>))]
373 pub earliest_open_week: Option<i64>,
374 pub earliest_open_date: Option<String>,
375}
376
377#[cfg_attr(feature = "specta", derive(specta::Type))]
382#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
383#[serde(rename_all = "camelCase")]
384pub struct Profile {
385 pub id: String,
386 pub name: Option<String>,
387 #[cfg_attr(feature = "specta", specta(type = Option<f64>))]
388 pub user: Option<i64>,
389 pub referral: Option<String>,
390 #[cfg_attr(feature = "specta", specta(type = Option<f64>))]
391 pub created_by: Option<i64>,
392 #[cfg_attr(feature = "specta", specta(type = Option<f64>))]
393 pub updated_by: Option<i64>,
394 pub created_at: Option<String>,
395 pub updated_at: Option<String>,
396 pub utm_source: Option<String>,
397 pub utm_medium: Option<String>,
398 pub utm_campaign: Option<String>,
399 pub utm_content: Option<String>,
400 pub utm_term: Option<String>,
401 pub wallet_activated: Option<bool>,
402 pub pseudonym: Option<String>,
403 pub display_username_public: Option<bool>,
404 pub profile_image: Option<String>,
405 pub bio: Option<String>,
406 pub proxy_wallet: Option<String>,
407 #[cfg_attr(feature = "specta", specta(skip))]
410 pub profile_image_optimized: Option<serde_json::Value>,
411 pub is_close_only: Option<bool>,
412 pub is_cert_req: Option<bool>,
413 pub cert_req_date: Option<String>,
414}
415
416#[cfg_attr(feature = "specta", derive(specta::Type))]
418#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
419#[serde(rename_all = "camelCase")]
420pub struct Tag {
421 pub id: String,
422 pub slug: String,
423 pub label: String,
424 pub force_show: Option<bool>,
425 pub published_at: Option<String>,
426 #[cfg_attr(feature = "specta", specta(type = Option<f64>))]
427 pub created_by: Option<u64>,
428 #[cfg_attr(feature = "specta", specta(type = Option<f64>))]
429 pub updated_by: Option<u64>,
430 pub created_at: Option<String>,
431 pub updated_at: Option<String>,
432 pub force_hide: Option<bool>,
433 pub is_carousel: Option<bool>,
434}
435
436#[cfg_attr(feature = "specta", derive(specta::Type))]
438#[derive(Debug, Clone, Serialize, Deserialize)]
439#[serde(rename_all = "camelCase")]
440pub struct SportMetadata {
441 #[cfg_attr(feature = "specta", specta(type = f64))]
442 pub id: u64,
443 pub sport: String,
444 pub image: Option<String>,
445 pub resolution: Option<String>,
446 pub ordering: Option<String>,
447 pub tags: Option<String>,
448 pub series: Option<String>,
449 pub created_at: Option<String>,
450}
451
452#[cfg_attr(feature = "specta", derive(specta::Type))]
454#[derive(Debug, Clone, Serialize, Deserialize)]
455#[serde(rename_all = "camelCase")]
456pub struct Team {
457 #[cfg_attr(feature = "specta", specta(type = f64))]
458 pub id: i64,
459 pub name: Option<String>,
460 pub league: Option<String>,
461 pub record: Option<String>,
462 pub logo: Option<String>,
463 pub abbreviation: Option<String>,
464 pub alias: Option<String>,
465 pub created_at: Option<DateTime<Utc>>,
466 pub updated_at: Option<DateTime<Utc>>,
467}
468
469#[cfg_attr(feature = "specta", derive(specta::Type))]
471#[derive(Debug, Clone, Serialize, Deserialize)]
472#[serde(rename_all = "camelCase")]
473pub struct Comment {
474 pub id: String,
475 pub body: String,
476 pub created_at: DateTime<Utc>,
477 pub updated_at: DateTime<Utc>,
478 pub deleted_at: Option<DateTime<Utc>>,
479 pub user: CommentUser,
480 pub market_id: Option<String>,
481 pub event_id: Option<String>,
482 pub series_id: Option<String>,
483 pub parent_id: Option<String>,
484 #[serde(default)]
485 pub reactions: Vec<CommentReaction>,
486 #[serde(default)]
487 pub positions: Vec<CommentPosition>,
488 #[cfg_attr(feature = "specta", specta(type = f64))]
489 pub like_count: u32,
490 #[cfg_attr(feature = "specta", specta(type = f64))]
491 pub dislike_count: u32,
492 #[cfg_attr(feature = "specta", specta(type = f64))]
493 pub reply_count: u32,
494}
495
496#[cfg_attr(feature = "specta", derive(specta::Type))]
498#[derive(Debug, Clone, Serialize, Deserialize)]
499#[serde(rename_all = "camelCase")]
500pub struct CommentUser {
501 pub id: String,
502 pub name: String,
503 pub avatar: Option<String>,
504}
505
506#[cfg_attr(feature = "specta", derive(specta::Type))]
508#[derive(Debug, Clone, Serialize, Deserialize)]
509#[serde(rename_all = "camelCase")]
510pub struct CommentReaction {
511 pub user_id: String,
512 pub reaction_type: String,
513}
514
515#[cfg_attr(feature = "specta", derive(specta::Type))]
517#[derive(Debug, Clone, Serialize, Deserialize)]
518#[serde(rename_all = "camelCase")]
519pub struct CommentPosition {
520 pub token_id: String,
521 pub outcome: String,
522 pub shares: String,
523}
524
525#[cfg_attr(feature = "specta", derive(specta::Type))]
527#[derive(Debug, Clone, Serialize, Deserialize)]
528#[serde(rename_all = "camelCase")]
529pub struct CountResponse {
530 #[cfg_attr(feature = "specta", specta(type = f64))]
531 pub count: u64,
532}
533
534#[cfg_attr(feature = "specta", derive(specta::Type))]
536#[derive(Debug, Clone, Serialize, Deserialize)]
537#[serde(rename_all = "camelCase")]
538pub struct Cursor {
539 pub next_cursor: Option<String>,
540}
541
542#[cfg_attr(feature = "specta", derive(specta::Type))]
544#[derive(Debug, Clone, Serialize, Deserialize)]
545#[serde(rename_all = "camelCase")]
546pub struct PaginatedResponse<T> {
547 pub data: Vec<T>,
548 pub next_cursor: Option<String>,
549}
550
551#[cfg_attr(feature = "specta", derive(specta::Type))]
553#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
554#[serde(rename_all = "camelCase")]
555pub struct EventCreator {
556 pub id: String,
557 pub creator_name: Option<String>,
558 pub creator_handle: Option<String>,
559 pub creator_url: Option<String>,
560 pub creator_image: Option<String>,
561 pub created_at: Option<String>,
562 pub updated_at: Option<String>,
563}
564
565#[cfg_attr(feature = "specta", derive(specta::Type))]
567#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
568#[serde(rename_all = "camelCase")]
569pub struct Pagination {
570 pub has_more: Option<bool>,
571 #[cfg_attr(feature = "specta", specta(type = Option<f64>))]
572 pub total_results: Option<i64>,
573}
574
575#[cfg_attr(feature = "specta", derive(specta::Type))]
577#[derive(Debug, Clone, Serialize, Deserialize)]
578#[serde(rename_all = "camelCase")]
579pub struct EventsPagination {
580 #[serde(default)]
581 pub data: Vec<Event>,
582 pub pagination: Option<Pagination>,
583}
584
585#[cfg_attr(feature = "specta", derive(specta::Type))]
590#[derive(Debug, Clone, Serialize, Deserialize)]
591pub struct KeysetEventsResponse {
592 #[serde(default)]
593 pub events: Vec<Event>,
594 pub next_cursor: Option<String>,
595}
596
597#[cfg_attr(feature = "specta", derive(specta::Type))]
602#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
603#[serde(rename_all = "camelCase")]
604pub struct MarketDescription {
605 pub description: Option<String>,
606}
607
608#[cfg_attr(feature = "specta", derive(specta::Type))]
614#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
615#[serde(rename_all = "camelCase")]
616pub struct MarketsInformationBody {
617 #[cfg_attr(feature = "specta", specta(type = Vec<f64>))]
619 #[serde(default, skip_serializing_if = "Vec::is_empty")]
620 pub id: Vec<i64>,
621 #[serde(default, skip_serializing_if = "Vec::is_empty")]
623 pub slug: Vec<String>,
624 #[serde(skip_serializing_if = "Option::is_none")]
626 pub closed: Option<bool>,
627 #[serde(default, skip_serializing_if = "Vec::is_empty")]
629 pub clob_token_ids: Vec<String>,
630 #[serde(default, skip_serializing_if = "Vec::is_empty")]
632 pub condition_ids: Vec<String>,
633 #[serde(default, skip_serializing_if = "Vec::is_empty")]
635 pub market_maker_address: Vec<String>,
636 #[serde(skip_serializing_if = "Option::is_none")]
638 pub liquidity_num_min: Option<f64>,
639 #[serde(skip_serializing_if = "Option::is_none")]
641 pub liquidity_num_max: Option<f64>,
642 #[serde(skip_serializing_if = "Option::is_none")]
644 pub volume_num_min: Option<f64>,
645 #[serde(skip_serializing_if = "Option::is_none")]
647 pub volume_num_max: Option<f64>,
648 #[serde(skip_serializing_if = "Option::is_none")]
650 pub start_date_min: Option<String>,
651 #[serde(skip_serializing_if = "Option::is_none")]
653 pub start_date_max: Option<String>,
654 #[serde(skip_serializing_if = "Option::is_none")]
656 pub end_date_min: Option<String>,
657 #[serde(skip_serializing_if = "Option::is_none")]
659 pub end_date_max: Option<String>,
660 #[serde(skip_serializing_if = "Option::is_none")]
662 pub related_tags: Option<bool>,
663 #[cfg_attr(feature = "specta", specta(type = Option<f64>))]
665 #[serde(skip_serializing_if = "Option::is_none")]
666 pub tag_id: Option<i64>,
667 #[serde(skip_serializing_if = "Option::is_none")]
669 pub cyom: Option<bool>,
670 #[serde(skip_serializing_if = "Option::is_none")]
672 pub uma_resolution_status: Option<String>,
673 #[serde(skip_serializing_if = "Option::is_none")]
675 pub game_id: Option<String>,
676 #[serde(default, skip_serializing_if = "Vec::is_empty")]
678 pub sports_market_types: Vec<String>,
679 #[serde(skip_serializing_if = "Option::is_none")]
681 pub rewards_min_size: Option<f64>,
682 #[serde(default, skip_serializing_if = "Vec::is_empty")]
684 pub question_ids: Vec<String>,
685 #[serde(skip_serializing_if = "Option::is_none")]
687 pub include_tags: Option<bool>,
688}
689
690#[cfg_attr(feature = "specta", derive(specta::Type))]
695#[derive(Debug, Clone, Serialize, Deserialize)]
696pub struct KeysetMarketsResponse {
697 #[serde(default)]
698 pub markets: Vec<Market>,
699 pub next_cursor: Option<String>,
700}
701
702#[cfg(test)]
703mod tests {
704 use super::*;
705
706 #[test]
709 fn test_market_token_deserialization() {
710 let json = r#"{
711 "tokenId": "71321045679252212594626385532706912750332728571942532289631379312455583992563",
712 "outcome": "Yes",
713 "price": "0.55",
714 "winner": false
715 }"#;
716 let token: MarketToken = serde_json::from_str(json).unwrap();
717 assert_eq!(token.outcome, "Yes");
718 assert_eq!(token.price.as_deref(), Some("0.55"));
719 assert_eq!(token.winner, Some(false));
720 }
721
722 #[test]
723 fn test_market_token_optional_fields() {
724 let json = r#"{"tokenId": "123", "outcome": "No"}"#;
725 let token: MarketToken = serde_json::from_str(json).unwrap();
726 assert!(token.price.is_none());
727 assert!(token.winner.is_none());
728 }
729
730 #[test]
733 fn test_tag_deserialization() {
734 let json = r#"{
735 "id": "42",
736 "slug": "politics",
737 "label": "Politics",
738 "forceShow": true,
739 "publishedAt": "2024-01-01T00:00:00Z",
740 "createdBy": 1,
741 "updatedBy": 2,
742 "createdAt": "2024-01-01T00:00:00Z",
743 "updatedAt": "2024-06-01T00:00:00Z",
744 "forceHide": false,
745 "isCarousel": true
746 }"#;
747 let tag: Tag = serde_json::from_str(json).unwrap();
748 assert_eq!(tag.slug, "politics");
749 assert_eq!(tag.force_show, Some(true));
750 assert_eq!(tag.is_carousel, Some(true));
751 }
752
753 #[test]
754 fn test_tag_minimal() {
755 let json = r#"{"id": "1", "slug": "test", "label": "Test"}"#;
756 let tag: Tag = serde_json::from_str(json).unwrap();
757 assert_eq!(tag.label, "Test");
758 assert!(tag.force_show.is_none());
759 assert!(tag.created_by.is_none());
760 }
761
762 #[test]
765 fn test_market_minimal_deserialization() {
766 let json = r#"{
767 "id": "12345",
768 "conditionId": "0xabc",
769 "description": "Will X happen?",
770 "question": "Will X happen by end of 2025?",
771 "marketMakerAddress": "0x1234567890abcdef"
772 }"#;
773 let market: Market = serde_json::from_str(json).unwrap();
774 assert_eq!(market.id, "12345");
775 assert_eq!(market.condition_id, "0xabc");
776 assert!(market.tokens.is_empty()); assert!(market.tags.is_empty()); assert!(market.slug.is_none());
779 assert!(market.volume_24hr.is_none());
780 }
781
782 #[test]
783 fn test_market_with_tokens() {
784 let json = r#"{
785 "id": "1",
786 "conditionId": "0xcond",
787 "description": "Test",
788 "question": "Test?",
789 "marketMakerAddress": "0xaddr",
790 "tokens": [
791 {"tokenId": "t1", "outcome": "Yes", "price": "0.7", "winner": true},
792 {"tokenId": "t2", "outcome": "No", "price": "0.3", "winner": false}
793 ]
794 }"#;
795 let market: Market = serde_json::from_str(json).unwrap();
796 assert_eq!(market.tokens.len(), 2);
797 assert_eq!(market.tokens[0].outcome, "Yes");
798 assert_eq!(market.tokens[1].price.as_deref(), Some("0.3"));
799 }
800
801 #[test]
802 fn test_market_volume_fields() {
803 let json = r#"{
804 "id": "1",
805 "conditionId": "0xcond",
806 "description": "Test",
807 "question": "Test?",
808 "marketMakerAddress": "0xaddr",
809 "volume24hr": 1500.5,
810 "volume1wk": 10000.0,
811 "volume1mo": 50000.0,
812 "volume1yr": 200000.0,
813 "volume24hrAmm": 100.0,
814 "volume1wkClob": 9900.0
815 }"#;
816 let market: Market = serde_json::from_str(json).unwrap();
817 assert_eq!(market.volume_24hr, Some(1500.5));
818 assert_eq!(market.volume_1wk, Some(10000.0));
819 assert_eq!(market.volume_24hr_amm, Some(100.0));
820 assert_eq!(market.volume_1wk_clob, Some(9900.0));
821 }
822
823 #[test]
824 fn test_market_denomination_token_rename() {
825 let json = r#"{
827 "id": "1",
828 "conditionId": "0xcond",
829 "description": "Test",
830 "question": "Test?",
831 "marketMakerAddress": "0xaddr",
832 "denomationToken": "USDC"
833 }"#;
834 let market: Market = serde_json::from_str(json).unwrap();
835 assert_eq!(market.denomination_token.as_deref(), Some("USDC"));
836 }
837
838 #[test]
839 fn test_market_rewards_as_map() {
840 let json = r#"{
841 "id": "1",
842 "conditionId": "0xcond",
843 "description": "Test",
844 "question": "Test?",
845 "marketMakerAddress": "0xaddr",
846 "rewards": {"min_size": "100", "max_spread": "0.05"}
847 }"#;
848 let market: Market = serde_json::from_str(json).unwrap();
849 assert!(market.rewards.is_some());
850 let rewards = market.rewards.unwrap();
851 assert_eq!(rewards["min_size"], "100");
852 }
853
854 #[test]
855 fn test_market_null_rewards() {
856 let json = r#"{
857 "id": "1",
858 "conditionId": "0xcond",
859 "description": "Test",
860 "question": "Test?",
861 "marketMakerAddress": "0xaddr",
862 "rewards": null
863 }"#;
864 let market: Market = serde_json::from_str(json).unwrap();
865 assert!(market.rewards.is_none());
866 }
867
868 #[test]
871 fn test_event_minimal() {
872 let json = r#"{"id": "evt-1"}"#;
873 let event: Event = serde_json::from_str(json).unwrap();
874 assert_eq!(event.id, "evt-1");
875 assert!(event.markets.is_empty()); assert!(event.tags.is_empty());
877 assert!(event.series.is_empty());
878 assert!(event.sub_events.is_empty());
879 }
880
881 #[test]
882 fn test_event_with_nested_markets() {
883 let json = r#"{
884 "id": "evt-1",
885 "title": "2025 Election",
886 "markets": [
887 {
888 "id": "mkt-1",
889 "conditionId": "0xabc",
890 "description": "Who wins?",
891 "question": "Who wins the election?",
892 "marketMakerAddress": "0xaddr"
893 }
894 ]
895 }"#;
896 let event: Event = serde_json::from_str(json).unwrap();
897 assert_eq!(event.markets.len(), 1);
898 assert_eq!(event.markets[0].id, "mkt-1");
899 }
900
901 #[test]
902 fn test_event_volume_24h_rename() {
903 let json = r#"{
904 "id": "evt-1",
905 "volume24hr": 5000.0
906 }"#;
907 let event: Event = serde_json::from_str(json).unwrap();
908 assert_eq!(event.volume_24hr, Some(5000.0));
909 }
910
911 #[test]
912 fn test_event_volume_24h_old_key_ignored() {
913 let json = r#"{
915 "id": "evt-1",
916 "volume24h": 5000.0
917 }"#;
918 let event: Event = serde_json::from_str(json).unwrap();
919 assert_eq!(event.volume_24hr, None);
920 }
921
922 #[test]
923 fn test_event_enable_neg_risk_rename() {
924 let json = r#"{
925 "id": "evt-1",
926 "enableNegRisk": true
927 }"#;
928 let event: Event = serde_json::from_str(json).unwrap();
929 assert_eq!(event.enable_neg_risk, Some(true));
930 }
931
932 #[test]
933 fn test_event_published_at_snake_case() {
934 let json = r#"{
935 "id": "evt-1",
936 "published_at": "2024-01-01T00:00:00Z"
937 }"#;
938 let event: Event = serde_json::from_str(json).unwrap();
939 assert_eq!(event.published_at.as_deref(), Some("2024-01-01T00:00:00Z"));
940 }
941
942 #[test]
943 fn test_market_question_id_capital() {
944 let json = r#"{
945 "id": "1",
946 "conditionId": "0xcond",
947 "description": "Test",
948 "question": "Test?",
949 "marketMakerAddress": "0xaddr",
950 "questionID": "0xabc123"
951 }"#;
952 let market: Market = serde_json::from_str(json).unwrap();
953 assert_eq!(market.question_id.as_deref(), Some("0xabc123"));
954 }
955
956 #[test]
957 fn test_market_has_reviewed_dates() {
958 let json = r#"{
959 "id": "1",
960 "conditionId": "0xcond",
961 "description": "Test",
962 "question": "Test?",
963 "marketMakerAddress": "0xaddr",
964 "hasReviewedDates": true
965 }"#;
966 let market: Market = serde_json::from_str(json).unwrap();
967 assert_eq!(market.has_reviewed_dates, Some(true));
968 }
969
970 #[test]
971 fn test_market_rewards_max_spread_singular() {
972 let json = r#"{
973 "id": "1",
974 "conditionId": "0xcond",
975 "description": "Test",
976 "question": "Test?",
977 "marketMakerAddress": "0xaddr",
978 "rewardsMaxSpread": 0.05
979 }"#;
980 let market: Market = serde_json::from_str(json).unwrap();
981 assert_eq!(market.rewards_max_spread, Some(0.05));
982 }
983
984 #[test]
985 fn test_market_submitted_by_snake_case() {
986 let json = r#"{
987 "id": "1",
988 "conditionId": "0xcond",
989 "description": "Test",
990 "question": "Test?",
991 "marketMakerAddress": "0xaddr",
992 "submitted_by": "0xdeadbeef"
993 }"#;
994 let market: Market = serde_json::from_str(json).unwrap();
995 assert_eq!(market.submitted_by.as_deref(), Some("0xdeadbeef"));
996 }
997
998 #[test]
1001 fn test_series_info_minimal() {
1002 let json = r#"{"id": "s1", "slug": "nfl-2025", "title": "NFL 2025"}"#;
1003 let si: SeriesInfo = serde_json::from_str(json).unwrap();
1004 assert_eq!(si.slug, "nfl-2025");
1005 assert_eq!(si.title, "NFL 2025");
1006 assert!(si.ticker.is_none());
1007 assert!(si.active.is_none());
1008 }
1009
1010 #[test]
1011 fn test_series_info_full() {
1012 let json = r#"{
1013 "id": "2",
1014 "ticker": "nba",
1015 "slug": "nba",
1016 "title": "NBA",
1017 "seriesType": "single",
1018 "recurrence": "daily",
1019 "image": "https://example.com/nba.png",
1020 "icon": "https://example.com/nba-icon.png",
1021 "layout": "default",
1022 "active": true,
1023 "closed": false,
1024 "archived": false,
1025 "new": false,
1026 "featured": false,
1027 "restricted": true,
1028 "publishedAt": "2023-01-30T17:13:39Z",
1029 "createdBy": "15",
1030 "updatedBy": "15",
1031 "createdAt": "2022-10-13T00:36:01Z",
1032 "updatedAt": "2026-03-04T12:03:42Z",
1033 "commentsEnabled": false,
1034 "competitive": "0",
1035 "volume24hr": 11.07,
1036 "startDate": "2021-01-01T17:00:00Z",
1037 "commentCount": 6274,
1038 "requiresTranslation": false
1039 }"#;
1040 let si: SeriesInfo = serde_json::from_str(json).unwrap();
1041 assert_eq!(si.ticker.as_deref(), Some("nba"));
1042 assert_eq!(si.series_type.as_deref(), Some("single"));
1043 assert_eq!(si.active, Some(true));
1044 assert_eq!(si.closed, Some(false));
1045 assert_eq!(si.volume_24hr, Some(11.07));
1046 assert_eq!(si.comment_count, Some(6274));
1047 assert_eq!(si.requires_translation, Some(false));
1048 }
1049
1050 #[test]
1053 fn test_market_old_question_id_ignored() {
1054 let json = r#"{
1056 "id": "1",
1057 "conditionId": "0xcond",
1058 "description": "Test",
1059 "question": "Test?",
1060 "marketMakerAddress": "0xaddr",
1061 "questionId": "0xwrong"
1062 }"#;
1063 let market: Market = serde_json::from_str(json).unwrap();
1064 assert!(market.question_id.is_none());
1065 }
1066
1067 #[test]
1068 fn test_market_old_has_review_dates_ignored() {
1069 let json = r#"{
1071 "id": "1",
1072 "conditionId": "0xcond",
1073 "description": "Test",
1074 "question": "Test?",
1075 "marketMakerAddress": "0xaddr",
1076 "hasReviewDates": true
1077 }"#;
1078 let market: Market = serde_json::from_str(json).unwrap();
1079 assert!(market.has_reviewed_dates.is_none());
1080 }
1081
1082 #[test]
1083 fn test_market_old_rewards_max_spreads_ignored() {
1084 let json = r#"{
1086 "id": "1",
1087 "conditionId": "0xcond",
1088 "description": "Test",
1089 "question": "Test?",
1090 "marketMakerAddress": "0xaddr",
1091 "rewardsMaxSpreads": 0.05
1092 }"#;
1093 let market: Market = serde_json::from_str(json).unwrap();
1094 assert!(market.rewards_max_spread.is_none());
1095 }
1096
1097 #[test]
1098 fn test_event_old_enalbe_neg_risk_ignored() {
1099 let json = r#"{
1101 "id": "evt-1",
1102 "enalbeNegRisk": true
1103 }"#;
1104 let event: Event = serde_json::from_str(json).unwrap();
1105 assert!(event.enable_neg_risk.is_none());
1106 }
1107
1108 #[test]
1109 fn test_event_camel_published_at_ignored() {
1110 let json = r#"{
1112 "id": "evt-1",
1113 "publishedAt": "2024-01-01T00:00:00Z"
1114 }"#;
1115 let event: Event = serde_json::from_str(json).unwrap();
1116 assert!(event.published_at.is_none());
1117 }
1118
1119 #[test]
1120 fn test_market_camel_submitted_by_ignored() {
1121 let json = r#"{
1123 "id": "1",
1124 "conditionId": "0xcond",
1125 "description": "Test",
1126 "question": "Test?",
1127 "marketMakerAddress": "0xaddr",
1128 "submittedBy": "0xwrong"
1129 }"#;
1130 let market: Market = serde_json::from_str(json).unwrap();
1131 assert!(market.submitted_by.is_none());
1132 }
1133
1134 #[test]
1137 fn test_series_data_minimal() {
1138 let json = r#"{
1139 "id": "s1",
1140 "slug": "nfl",
1141 "title": "NFL",
1142 "active": true,
1143 "closed": false,
1144 "archived": false
1145 }"#;
1146 let sd: SeriesData = serde_json::from_str(json).unwrap();
1147 assert!(sd.active);
1148 assert!(!sd.closed);
1149 assert!(sd.events.is_empty()); assert!(sd.tags.is_empty());
1151 }
1152
1153 #[test]
1156 fn test_sport_metadata() {
1157 let json = r#"{
1158 "id": 1,
1159 "sport": "Basketball",
1160 "image": "https://example.com/nba.png",
1161 "createdAt": "2024-01-01T00:00:00Z"
1162 }"#;
1163 let sm: SportMetadata = serde_json::from_str(json).unwrap();
1164 assert_eq!(sm.id, 1);
1165 assert_eq!(sm.sport, "Basketball");
1166 }
1167
1168 #[test]
1171 fn test_team() {
1172 let json = r#"{
1173 "id": 42,
1174 "name": "Lakers",
1175 "league": "NBA",
1176 "abbreviation": "LAL",
1177 "createdAt": "2024-01-01T00:00:00Z",
1178 "updatedAt": "2024-06-15T12:00:00Z"
1179 }"#;
1180 let team: Team = serde_json::from_str(json).unwrap();
1181 assert_eq!(team.id, 42);
1182 assert_eq!(team.name.as_deref(), Some("Lakers"));
1183 assert!(team.created_at.is_some());
1184 }
1185
1186 #[test]
1189 fn test_comment_deserialization() {
1190 let json = r#"{
1191 "id": "c1",
1192 "body": "I think this market will resolve yes.",
1193 "createdAt": "2024-06-01T10:00:00Z",
1194 "updatedAt": "2024-06-01T10:00:00Z",
1195 "deletedAt": null,
1196 "user": {"id": "u1", "name": "trader1", "avatar": null},
1197 "marketId": "mkt-1",
1198 "eventId": null,
1199 "seriesId": null,
1200 "parentId": null,
1201 "reactions": [],
1202 "positions": [
1203 {"tokenId": "t1", "outcome": "Yes", "shares": "100.5"}
1204 ],
1205 "likeCount": 5,
1206 "dislikeCount": 1,
1207 "replyCount": 3
1208 }"#;
1209 let comment: Comment = serde_json::from_str(json).unwrap();
1210 assert_eq!(comment.id, "c1");
1211 assert_eq!(comment.user.name, "trader1");
1212 assert_eq!(comment.like_count, 5);
1213 assert_eq!(comment.positions.len(), 1);
1214 assert_eq!(comment.positions[0].shares, "100.5");
1215 assert!(comment.deleted_at.is_none());
1216 }
1217
1218 #[test]
1221 fn test_user_response() {
1222 let json = r#"{
1223 "proxyWallet": "0xproxy",
1224 "address": "0xsigner",
1225 "id": "u1",
1226 "name": "polytrader"
1227 }"#;
1228 let user: crate::api::user::UserResponse = serde_json::from_str(json).unwrap();
1229 assert_eq!(user.proxy.as_deref(), Some("0xproxy"));
1230 assert_eq!(user.name.as_deref(), Some("polytrader"));
1231 }
1232
1233 #[test]
1234 fn test_user_response_all_null() {
1235 let json = r#"{}"#;
1236 let user: crate::api::user::UserResponse = serde_json::from_str(json).unwrap();
1237 assert!(user.proxy.is_none());
1238 assert!(user.address.is_none());
1239 assert!(user.id.is_none());
1240 assert!(user.name.is_none());
1241 assert!(user.created_at.is_none());
1242 assert!(user.profile_image.is_none());
1243 assert!(user.display_username_public.is_none());
1244 assert!(user.bio.is_none());
1245 assert!(user.pseudonym.is_none());
1246 assert!(user.x_username.is_none());
1247 assert!(user.verified_badge.is_none());
1248 assert!(user.users.is_empty());
1249 }
1250
1251 #[test]
1252 fn test_user_response_full_profile() {
1253 let json = r#"{
1254 "proxyWallet": "0xproxy",
1255 "address": "0xsigner",
1256 "id": "u1",
1257 "name": "polytrader",
1258 "createdAt": "2024-01-15T10:00:00Z",
1259 "profileImage": "https://example.com/avatar.png",
1260 "displayUsernamePublic": true,
1261 "bio": "DeFi enthusiast",
1262 "pseudonym": "poly_anon",
1263 "xUsername": "polytrader_x",
1264 "verifiedBadge": true,
1265 "users": [
1266 {"id": "uid-1", "creator": true, "mod": false},
1267 {"id": "uid-2", "creator": false, "mod": true}
1268 ]
1269 }"#;
1270 let user: crate::api::user::UserResponse = serde_json::from_str(json).unwrap();
1271 assert_eq!(user.proxy.as_deref(), Some("0xproxy"));
1272 assert_eq!(user.name.as_deref(), Some("polytrader"));
1273 assert_eq!(user.created_at.as_deref(), Some("2024-01-15T10:00:00Z"));
1274 assert_eq!(
1275 user.profile_image.as_deref(),
1276 Some("https://example.com/avatar.png")
1277 );
1278 assert_eq!(user.display_username_public, Some(true));
1279 assert_eq!(user.bio.as_deref(), Some("DeFi enthusiast"));
1280 assert_eq!(user.pseudonym.as_deref(), Some("poly_anon"));
1281 assert_eq!(user.x_username.as_deref(), Some("polytrader_x"));
1282 assert_eq!(user.verified_badge, Some(true));
1283 assert_eq!(user.users.len(), 2);
1284 assert!(user.users[0].creator);
1285 assert!(!user.users[0].moderator);
1286 assert!(!user.users[1].creator);
1287 assert!(user.users[1].moderator);
1288 }
1289
1290 #[test]
1291 fn test_user_info_deserialization() {
1292 let json = r#"{"id": "uid-1", "creator": true, "mod": false}"#;
1293 let info: crate::api::user::UserInfo = serde_json::from_str(json).unwrap();
1294 assert_eq!(info.id.as_deref(), Some("uid-1"));
1295 assert!(info.creator);
1296 assert!(!info.moderator);
1297 }
1298
1299 #[test]
1300 fn test_user_info_defaults() {
1301 let json = r#"{}"#;
1302 let info: crate::api::user::UserInfo = serde_json::from_str(json).unwrap();
1303 assert!(info.id.is_none());
1304 assert!(!info.creator);
1305 assert!(!info.moderator);
1306 }
1307
1308 #[test]
1311 fn test_count_response() {
1312 let json = r#"{"count": 42}"#;
1313 let resp: CountResponse = serde_json::from_str(json).unwrap();
1314 assert_eq!(resp.count, 42);
1315 }
1316
1317 #[test]
1320 fn test_cursor_with_next() {
1321 let json = r#"{"nextCursor": "abc123"}"#;
1322 let cursor: Cursor = serde_json::from_str(json).unwrap();
1323 assert_eq!(cursor.next_cursor.as_deref(), Some("abc123"));
1324 }
1325
1326 #[test]
1327 fn test_cursor_without_next() {
1328 let json = r#"{"nextCursor": null}"#;
1329 let cursor: Cursor = serde_json::from_str(json).unwrap();
1330 assert!(cursor.next_cursor.is_none());
1331 }
1332
1333 #[test]
1334 fn test_paginated_response() {
1335 let json = r#"{
1336 "data": [{"tokenId": "t1", "outcome": "Yes"}],
1337 "nextCursor": "page2"
1338 }"#;
1339 let resp: PaginatedResponse<MarketToken> = serde_json::from_str(json).unwrap();
1340 assert_eq!(resp.data.len(), 1);
1341 assert_eq!(resp.next_cursor.as_deref(), Some("page2"));
1342 }
1343
1344 #[test]
1345 fn test_paginated_response_empty() {
1346 let json = r#"{"data": [], "nextCursor": null}"#;
1347 let resp: PaginatedResponse<MarketToken> = serde_json::from_str(json).unwrap();
1348 assert!(resp.data.is_empty());
1349 assert!(resp.next_cursor.is_none());
1350 }
1351
1352 #[test]
1355 fn test_market_token_roundtrip() {
1356 let token = MarketToken {
1357 token_id: "123".into(),
1358 outcome: "Yes".into(),
1359 price: Some("0.75".into()),
1360 winner: Some(true),
1361 };
1362 let json = serde_json::to_string(&token).unwrap();
1363 let back: MarketToken = serde_json::from_str(&json).unwrap();
1364 assert_eq!(token, back);
1365 }
1366
1367 #[test]
1370 fn test_event_creator_full() {
1371 let json = r#"{
1372 "id": "7",
1373 "creatorName": "Polymarket Sports",
1374 "creatorHandle": "poly_sports",
1375 "creatorUrl": "https://example.com",
1376 "creatorImage": "https://example.com/a.png",
1377 "createdAt": "2024-01-01T00:00:00Z",
1378 "updatedAt": "2024-06-01T00:00:00Z"
1379 }"#;
1380 let c: EventCreator = serde_json::from_str(json).unwrap();
1381 assert_eq!(c.id, "7");
1382 assert_eq!(c.creator_name.as_deref(), Some("Polymarket Sports"));
1383 assert_eq!(c.creator_handle.as_deref(), Some("poly_sports"));
1384 assert_eq!(c.creator_url.as_deref(), Some("https://example.com"));
1385 }
1386
1387 #[test]
1388 fn test_event_creator_minimal() {
1389 let json = r#"{"id": "1"}"#;
1390 let c: EventCreator = serde_json::from_str(json).unwrap();
1391 assert_eq!(c.id, "1");
1392 assert!(c.creator_name.is_none());
1393 assert!(c.creator_handle.is_none());
1394 assert!(c.created_at.is_none());
1395 }
1396
1397 #[test]
1398 fn test_event_creator_roundtrip() {
1399 let c = EventCreator {
1400 id: "9".into(),
1401 creator_name: Some("Poly".into()),
1402 creator_handle: Some("poly".into()),
1403 creator_url: None,
1404 creator_image: None,
1405 created_at: None,
1406 updated_at: None,
1407 };
1408 let json = serde_json::to_string(&c).unwrap();
1409 let back: EventCreator = serde_json::from_str(&json).unwrap();
1410 assert_eq!(c, back);
1411 }
1412
1413 #[test]
1416 fn test_pagination_deserialization() {
1417 let json = r#"{"hasMore": true, "totalResults": 124}"#;
1418 let p: Pagination = serde_json::from_str(json).unwrap();
1419 assert_eq!(p.has_more, Some(true));
1420 assert_eq!(p.total_results, Some(124));
1421 }
1422
1423 #[test]
1424 fn test_events_pagination_deserialization() {
1425 let json = r#"{
1426 "data": [{"id": "e1"}, {"id": "e2"}],
1427 "pagination": {"hasMore": false, "totalResults": 2}
1428 }"#;
1429 let ep: EventsPagination = serde_json::from_str(json).unwrap();
1430 assert_eq!(ep.data.len(), 2);
1431 assert_eq!(ep.data[0].id, "e1");
1432 let p = ep.pagination.unwrap();
1433 assert_eq!(p.has_more, Some(false));
1434 assert_eq!(p.total_results, Some(2));
1435 }
1436
1437 #[test]
1438 fn test_events_pagination_empty_data() {
1439 let json = r#"{"pagination": {"hasMore": false, "totalResults": 0}}"#;
1440 let ep: EventsPagination = serde_json::from_str(json).unwrap();
1441 assert!(ep.data.is_empty());
1442 }
1443
1444 #[test]
1447 fn test_keyset_events_response_with_cursor() {
1448 let json = r#"{
1450 "events": [{"id": "e1", "title": "Test"}],
1451 "next_cursor": "cursor-123"
1452 }"#;
1453 let resp: KeysetEventsResponse = serde_json::from_str(json).unwrap();
1454 assert_eq!(resp.events.len(), 1);
1455 assert_eq!(resp.next_cursor.as_deref(), Some("cursor-123"));
1456 }
1457
1458 #[test]
1459 fn test_keyset_events_response_last_page() {
1460 let json = r#"{"events": []}"#;
1461 let resp: KeysetEventsResponse = serde_json::from_str(json).unwrap();
1462 assert!(resp.events.is_empty());
1463 assert!(resp.next_cursor.is_none());
1464 }
1465
1466 #[test]
1467 fn test_tag_roundtrip() {
1468 let tag = Tag {
1469 id: "1".into(),
1470 slug: "test".into(),
1471 label: "Test".into(),
1472 force_show: None,
1473 published_at: None,
1474 created_by: None,
1475 updated_by: None,
1476 created_at: None,
1477 updated_at: None,
1478 force_hide: None,
1479 is_carousel: None,
1480 };
1481 let json = serde_json::to_string(&tag).unwrap();
1482 let back: Tag = serde_json::from_str(&json).unwrap();
1483 assert_eq!(tag, back);
1484 }
1485
1486 #[test]
1489 fn test_market_description_deserialization() {
1490 let json = r#"{"description": "Will X happen?"}"#;
1491 let md: MarketDescription = serde_json::from_str(json).unwrap();
1492 assert_eq!(md.description.as_deref(), Some("Will X happen?"));
1493 }
1494
1495 #[test]
1496 fn test_market_description_null() {
1497 let json = r#"{"description": null}"#;
1498 let md: MarketDescription = serde_json::from_str(json).unwrap();
1499 assert!(md.description.is_none());
1500 }
1501
1502 #[test]
1505 fn test_markets_information_body_default_serializes_empty() {
1506 let body = MarketsInformationBody::default();
1507 let json = serde_json::to_string(&body).unwrap();
1508 assert_eq!(json, "{}");
1509 }
1510
1511 #[test]
1512 fn test_markets_information_body_populated_roundtrip() {
1513 let body = MarketsInformationBody {
1514 id: vec![1, 2, 3],
1515 slug: vec!["will-x".into(), "will-y".into()],
1516 closed: Some(true),
1517 clob_token_ids: vec!["tok-a".into()],
1518 condition_ids: vec!["0xcond".into()],
1519 market_maker_address: vec!["0xmm".into()],
1520 liquidity_num_min: Some(100.0),
1521 liquidity_num_max: Some(10_000.0),
1522 volume_num_min: Some(50.0),
1523 volume_num_max: None,
1524 start_date_min: Some("2025-01-01T00:00:00Z".into()),
1525 start_date_max: None,
1526 end_date_min: None,
1527 end_date_max: Some("2026-01-01T00:00:00Z".into()),
1528 related_tags: Some(true),
1529 tag_id: Some(42),
1530 cyom: Some(false),
1531 uma_resolution_status: Some("resolved".into()),
1532 game_id: Some("game-7".into()),
1533 sports_market_types: vec!["moneyline".into(), "spread".into()],
1534 rewards_min_size: Some(10.0),
1535 question_ids: vec!["q1".into()],
1536 include_tags: Some(true),
1537 };
1538 let json = serde_json::to_string(&body).unwrap();
1539 assert!(json.contains("\"clobTokenIds\""));
1541 assert!(json.contains("\"marketMakerAddress\""));
1542 assert!(json.contains("\"rewardsMinSize\""));
1543 let back: MarketsInformationBody = serde_json::from_str(&json).unwrap();
1544 assert_eq!(body, back);
1545 }
1546
1547 #[test]
1548 fn test_markets_information_body_partial_omits_empty() {
1549 let body = MarketsInformationBody {
1550 closed: Some(true),
1551 ..Default::default()
1552 };
1553 let json = serde_json::to_string(&body).unwrap();
1554 assert_eq!(json, r#"{"closed":true}"#);
1555 }
1556
1557 #[test]
1560 fn test_keyset_markets_response_full() {
1561 let json = r#"{
1562 "markets": [{
1563 "id": "1",
1564 "conditionId": "0xcond",
1565 "description": "desc",
1566 "question": "q?",
1567 "marketMakerAddress": "0xmm"
1568 }],
1569 "next_cursor": "abc"
1570 }"#;
1571 let resp: KeysetMarketsResponse = serde_json::from_str(json).unwrap();
1572 assert_eq!(resp.markets.len(), 1);
1573 assert_eq!(resp.markets[0].id, "1");
1574 assert_eq!(resp.next_cursor.as_deref(), Some("abc"));
1575 }
1576
1577 #[test]
1578 fn test_keyset_markets_response_last_page() {
1579 let json = r#"{"markets": []}"#;
1580 let resp: KeysetMarketsResponse = serde_json::from_str(json).unwrap();
1581 assert!(resp.markets.is_empty());
1582 assert!(resp.next_cursor.is_none());
1583 }
1584
1585 #[test]
1588 fn test_series_summary_full() {
1589 let json = r#"{
1590 "id": "s-1",
1591 "title": "NFL 2025",
1592 "slug": "nfl-2025",
1593 "eventDates": ["2025-09-01", "2025-09-08"],
1594 "eventWeeks": [1, 2],
1595 "earliest_open_week": 1,
1596 "earliest_open_date": "2025-09-01"
1597 }"#;
1598 let s: SeriesSummary = serde_json::from_str(json).unwrap();
1599 assert_eq!(s.id, "s-1");
1600 assert_eq!(s.title.as_deref(), Some("NFL 2025"));
1601 assert_eq!(s.event_dates.len(), 2);
1602 assert_eq!(s.event_weeks, vec![1, 2]);
1603 assert_eq!(s.earliest_open_week, Some(1));
1604 }
1605
1606 #[test]
1607 fn test_series_summary_minimal() {
1608 let json = r#"{"id": "s-1"}"#;
1609 let s: SeriesSummary = serde_json::from_str(json).unwrap();
1610 assert_eq!(s.id, "s-1");
1611 assert!(s.title.is_none());
1612 assert!(s.event_dates.is_empty());
1613 assert!(s.event_weeks.is_empty());
1614 }
1615
1616 #[test]
1617 fn test_series_summary_roundtrip_preserves_mixed_casing() {
1618 let s = SeriesSummary {
1619 id: "s-1".into(),
1620 title: Some("T".into()),
1621 slug: Some("slug".into()),
1622 event_dates: vec!["2025-09-01".into()],
1623 event_weeks: vec![1],
1624 earliest_open_week: Some(1),
1625 earliest_open_date: Some("2025-09-01".into()),
1626 };
1627 let json = serde_json::to_string(&s).unwrap();
1628 assert!(json.contains("\"eventDates\""));
1630 assert!(json.contains("\"eventWeeks\""));
1631 assert!(json.contains("\"earliest_open_week\""));
1632 assert!(json.contains("\"earliest_open_date\""));
1633 let back: SeriesSummary = serde_json::from_str(&json).unwrap();
1634 assert_eq!(s, back);
1635 }
1636
1637 #[test]
1640 fn test_profile_minimal() {
1641 let json = r#"{"id": "p-1"}"#;
1642 let p: Profile = serde_json::from_str(json).unwrap();
1643 assert_eq!(p.id, "p-1");
1644 assert!(p.name.is_none());
1645 assert!(p.proxy_wallet.is_none());
1646 }
1647
1648 #[test]
1649 fn test_profile_full() {
1650 let json = r#"{
1651 "id": "p-1",
1652 "name": "Alice",
1653 "user": 42,
1654 "proxyWallet": "0xdead",
1655 "utmSource": "direct",
1656 "walletActivated": true,
1657 "isCloseOnly": false,
1658 "profileImageOptimized": {"medium": "https://cdn/p/m.png"}
1659 }"#;
1660 let p: Profile = serde_json::from_str(json).unwrap();
1661 assert_eq!(p.name.as_deref(), Some("Alice"));
1662 assert_eq!(p.user, Some(42));
1663 assert_eq!(p.proxy_wallet.as_deref(), Some("0xdead"));
1664 assert_eq!(p.utm_source.as_deref(), Some("direct"));
1665 assert_eq!(p.wallet_activated, Some(true));
1666 assert_eq!(p.is_close_only, Some(false));
1667 assert!(p.profile_image_optimized.is_some());
1668 }
1669
1670 #[test]
1671 fn test_profile_roundtrip() {
1672 let p = Profile {
1673 id: "p-1".into(),
1674 name: Some("A".into()),
1675 user: Some(1),
1676 referral: None,
1677 created_by: None,
1678 updated_by: None,
1679 created_at: None,
1680 updated_at: None,
1681 utm_source: None,
1682 utm_medium: None,
1683 utm_campaign: None,
1684 utm_content: None,
1685 utm_term: None,
1686 wallet_activated: Some(true),
1687 pseudonym: None,
1688 display_username_public: None,
1689 profile_image: None,
1690 bio: None,
1691 proxy_wallet: Some("0xpw".into()),
1692 profile_image_optimized: None,
1693 is_close_only: None,
1694 is_cert_req: None,
1695 cert_req_date: None,
1696 };
1697 let json = serde_json::to_string(&p).unwrap();
1698 assert!(json.contains("\"proxyWallet\""));
1699 assert!(json.contains("\"walletActivated\""));
1700 let back: Profile = serde_json::from_str(&json).unwrap();
1701 assert_eq!(p, back);
1702 }
1703}