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