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))]
456#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
457pub struct RelatedTag {
458 pub id: String,
460 #[serde(rename = "tagID", default)]
462 #[cfg_attr(feature = "specta", specta(type = Option<f64>))]
463 pub tag_id: Option<u64>,
464 #[serde(rename = "relatedTagID", default)]
466 #[cfg_attr(feature = "specta", specta(type = Option<f64>))]
467 pub related_tag_id: Option<u64>,
468 #[serde(default)]
470 #[cfg_attr(feature = "specta", specta(type = Option<f64>))]
471 pub rank: Option<i64>,
472}
473
474#[cfg_attr(feature = "specta", derive(specta::Type))]
476#[derive(Debug, Clone, Serialize, Deserialize)]
477#[serde(rename_all = "camelCase")]
478pub struct SportMetadata {
479 #[cfg_attr(feature = "specta", specta(type = f64))]
480 pub id: u64,
481 pub sport: String,
482 pub image: Option<String>,
483 pub resolution: Option<String>,
484 pub ordering: Option<String>,
485 pub tags: Option<String>,
486 pub series: Option<String>,
487 pub created_at: Option<String>,
488}
489
490#[cfg_attr(feature = "specta", derive(specta::Type))]
492#[derive(Debug, Clone, Serialize, Deserialize)]
493#[serde(rename_all = "camelCase")]
494pub struct Team {
495 #[cfg_attr(feature = "specta", specta(type = f64))]
496 pub id: i64,
497 pub name: Option<String>,
498 pub league: Option<String>,
499 pub record: Option<String>,
500 pub logo: Option<String>,
501 pub abbreviation: Option<String>,
502 pub alias: Option<String>,
503 pub created_at: Option<DateTime<Utc>>,
504 pub updated_at: Option<DateTime<Utc>>,
505}
506
507#[cfg_attr(feature = "specta", derive(specta::Type))]
509#[derive(Debug, Clone, Serialize, Deserialize)]
510#[serde(rename_all = "camelCase")]
511pub struct Comment {
512 pub id: String,
513 pub body: String,
514 pub created_at: DateTime<Utc>,
515 pub updated_at: DateTime<Utc>,
516 pub deleted_at: Option<DateTime<Utc>>,
517 pub user: CommentUser,
518 pub market_id: Option<String>,
519 pub event_id: Option<String>,
520 pub series_id: Option<String>,
521 pub parent_id: Option<String>,
522 #[serde(default)]
523 pub reactions: Vec<CommentReaction>,
524 #[serde(default)]
525 pub positions: Vec<CommentPosition>,
526 #[cfg_attr(feature = "specta", specta(type = f64))]
527 pub like_count: u32,
528 #[cfg_attr(feature = "specta", specta(type = f64))]
529 pub dislike_count: u32,
530 #[cfg_attr(feature = "specta", specta(type = f64))]
531 pub reply_count: u32,
532}
533
534#[cfg_attr(feature = "specta", derive(specta::Type))]
536#[derive(Debug, Clone, Serialize, Deserialize)]
537#[serde(rename_all = "camelCase")]
538pub struct CommentUser {
539 pub id: String,
540 pub name: String,
541 pub avatar: 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 CommentReaction {
549 pub user_id: String,
550 pub reaction_type: String,
551}
552
553#[cfg_attr(feature = "specta", derive(specta::Type))]
555#[derive(Debug, Clone, Serialize, Deserialize)]
556#[serde(rename_all = "camelCase")]
557pub struct CommentPosition {
558 pub token_id: String,
559 pub outcome: String,
560 pub shares: String,
561}
562
563#[cfg_attr(feature = "specta", derive(specta::Type))]
565#[derive(Debug, Clone, Serialize, Deserialize)]
566#[serde(rename_all = "camelCase")]
567pub struct CountResponse {
568 #[cfg_attr(feature = "specta", specta(type = f64))]
569 pub count: u64,
570}
571
572#[cfg_attr(feature = "specta", derive(specta::Type))]
574#[derive(Debug, Clone, Serialize, Deserialize)]
575#[serde(rename_all = "camelCase")]
576pub struct Cursor {
577 pub next_cursor: Option<String>,
578}
579
580#[cfg_attr(feature = "specta", derive(specta::Type))]
582#[derive(Debug, Clone, Serialize, Deserialize)]
583#[serde(rename_all = "camelCase")]
584pub struct PaginatedResponse<T> {
585 pub data: Vec<T>,
586 pub next_cursor: Option<String>,
587}
588
589#[cfg_attr(feature = "specta", derive(specta::Type))]
591#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
592#[serde(rename_all = "camelCase")]
593pub struct EventCreator {
594 pub id: String,
595 pub creator_name: Option<String>,
596 pub creator_handle: Option<String>,
597 pub creator_url: Option<String>,
598 pub creator_image: Option<String>,
599 pub created_at: Option<String>,
600 pub updated_at: Option<String>,
601}
602
603#[cfg_attr(feature = "specta", derive(specta::Type))]
605#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
606#[serde(rename_all = "camelCase")]
607pub struct Pagination {
608 pub has_more: Option<bool>,
609 #[cfg_attr(feature = "specta", specta(type = Option<f64>))]
610 pub total_results: Option<i64>,
611}
612
613#[cfg_attr(feature = "specta", derive(specta::Type))]
615#[derive(Debug, Clone, Serialize, Deserialize)]
616#[serde(rename_all = "camelCase")]
617pub struct EventsPagination {
618 #[serde(default)]
619 pub data: Vec<Event>,
620 pub pagination: Option<Pagination>,
621}
622
623#[cfg_attr(feature = "specta", derive(specta::Type))]
628#[derive(Debug, Clone, Serialize, Deserialize)]
629pub struct KeysetEventsResponse {
630 #[serde(default)]
631 pub events: Vec<Event>,
632 pub next_cursor: Option<String>,
633}
634
635#[cfg_attr(feature = "specta", derive(specta::Type))]
640#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
641#[serde(rename_all = "camelCase")]
642pub struct MarketDescription {
643 pub description: Option<String>,
644}
645
646#[cfg_attr(feature = "specta", derive(specta::Type))]
652#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
653#[serde(rename_all = "camelCase")]
654pub struct MarketsInformationBody {
655 #[cfg_attr(feature = "specta", specta(type = Vec<f64>))]
657 #[serde(default, skip_serializing_if = "Vec::is_empty")]
658 pub id: Vec<i64>,
659 #[serde(default, skip_serializing_if = "Vec::is_empty")]
661 pub slug: Vec<String>,
662 #[serde(skip_serializing_if = "Option::is_none")]
664 pub closed: Option<bool>,
665 #[serde(default, skip_serializing_if = "Vec::is_empty")]
667 pub clob_token_ids: Vec<String>,
668 #[serde(default, skip_serializing_if = "Vec::is_empty")]
670 pub condition_ids: Vec<String>,
671 #[serde(default, skip_serializing_if = "Vec::is_empty")]
673 pub market_maker_address: Vec<String>,
674 #[serde(skip_serializing_if = "Option::is_none")]
676 pub liquidity_num_min: Option<f64>,
677 #[serde(skip_serializing_if = "Option::is_none")]
679 pub liquidity_num_max: Option<f64>,
680 #[serde(skip_serializing_if = "Option::is_none")]
682 pub volume_num_min: Option<f64>,
683 #[serde(skip_serializing_if = "Option::is_none")]
685 pub volume_num_max: Option<f64>,
686 #[serde(skip_serializing_if = "Option::is_none")]
688 pub start_date_min: Option<String>,
689 #[serde(skip_serializing_if = "Option::is_none")]
691 pub start_date_max: Option<String>,
692 #[serde(skip_serializing_if = "Option::is_none")]
694 pub end_date_min: Option<String>,
695 #[serde(skip_serializing_if = "Option::is_none")]
697 pub end_date_max: Option<String>,
698 #[serde(skip_serializing_if = "Option::is_none")]
700 pub related_tags: Option<bool>,
701 #[cfg_attr(feature = "specta", specta(type = Option<f64>))]
703 #[serde(skip_serializing_if = "Option::is_none")]
704 pub tag_id: Option<i64>,
705 #[serde(skip_serializing_if = "Option::is_none")]
707 pub cyom: Option<bool>,
708 #[serde(skip_serializing_if = "Option::is_none")]
710 pub uma_resolution_status: Option<String>,
711 #[serde(skip_serializing_if = "Option::is_none")]
713 pub game_id: Option<String>,
714 #[serde(default, skip_serializing_if = "Vec::is_empty")]
716 pub sports_market_types: Vec<String>,
717 #[serde(skip_serializing_if = "Option::is_none")]
719 pub rewards_min_size: Option<f64>,
720 #[serde(default, skip_serializing_if = "Vec::is_empty")]
722 pub question_ids: Vec<String>,
723 #[serde(skip_serializing_if = "Option::is_none")]
725 pub include_tags: Option<bool>,
726}
727
728#[cfg_attr(feature = "specta", derive(specta::Type))]
733#[derive(Debug, Clone, Serialize, Deserialize)]
734pub struct KeysetMarketsResponse {
735 #[serde(default)]
736 pub markets: Vec<Market>,
737 pub next_cursor: Option<String>,
738}
739
740#[cfg(test)]
741mod tests {
742 use super::*;
743
744 #[test]
747 fn test_market_token_deserialization() {
748 let json = r#"{
749 "tokenId": "71321045679252212594626385532706912750332728571942532289631379312455583992563",
750 "outcome": "Yes",
751 "price": "0.55",
752 "winner": false
753 }"#;
754 let token: MarketToken = serde_json::from_str(json).unwrap();
755 assert_eq!(token.outcome, "Yes");
756 assert_eq!(token.price.as_deref(), Some("0.55"));
757 assert_eq!(token.winner, Some(false));
758 }
759
760 #[test]
761 fn test_market_token_optional_fields() {
762 let json = r#"{"tokenId": "123", "outcome": "No"}"#;
763 let token: MarketToken = serde_json::from_str(json).unwrap();
764 assert!(token.price.is_none());
765 assert!(token.winner.is_none());
766 }
767
768 #[test]
771 fn test_tag_deserialization() {
772 let json = r#"{
773 "id": "42",
774 "slug": "politics",
775 "label": "Politics",
776 "forceShow": true,
777 "publishedAt": "2024-01-01T00:00:00Z",
778 "createdBy": 1,
779 "updatedBy": 2,
780 "createdAt": "2024-01-01T00:00:00Z",
781 "updatedAt": "2024-06-01T00:00:00Z",
782 "forceHide": false,
783 "isCarousel": true
784 }"#;
785 let tag: Tag = serde_json::from_str(json).unwrap();
786 assert_eq!(tag.slug, "politics");
787 assert_eq!(tag.force_show, Some(true));
788 assert_eq!(tag.is_carousel, Some(true));
789 }
790
791 #[test]
792 fn test_tag_minimal() {
793 let json = r#"{"id": "1", "slug": "test", "label": "Test"}"#;
794 let tag: Tag = serde_json::from_str(json).unwrap();
795 assert_eq!(tag.label, "Test");
796 assert!(tag.force_show.is_none());
797 assert!(tag.created_by.is_none());
798 }
799
800 #[test]
803 fn test_market_minimal_deserialization() {
804 let json = r#"{
805 "id": "12345",
806 "conditionId": "0xabc",
807 "description": "Will X happen?",
808 "question": "Will X happen by end of 2025?",
809 "marketMakerAddress": "0x1234567890abcdef"
810 }"#;
811 let market: Market = serde_json::from_str(json).unwrap();
812 assert_eq!(market.id, "12345");
813 assert_eq!(market.condition_id, "0xabc");
814 assert!(market.tokens.is_empty()); assert!(market.tags.is_empty()); assert!(market.slug.is_none());
817 assert!(market.volume_24hr.is_none());
818 }
819
820 #[test]
821 fn test_market_abridged_without_description_deserializes() {
822 let json = r#"{
826 "id": "12345",
827 "conditionId": "0xabc",
828 "question": "Will X happen by end of 2025?",
829 "marketMakerAddress": "0x1234567890abcdef",
830 "slug": "will-x-happen",
831 "closed": false
832 }"#;
833 let market: Market = serde_json::from_str(json)
834 .expect("abridged market without description should deserialize");
835 assert_eq!(market.id, "12345");
836 assert_eq!(market.question, "Will X happen by end of 2025?");
837 assert!(market.description.is_none());
838 }
839
840 #[test]
841 fn test_market_with_tokens() {
842 let json = r#"{
843 "id": "1",
844 "conditionId": "0xcond",
845 "description": "Test",
846 "question": "Test?",
847 "marketMakerAddress": "0xaddr",
848 "tokens": [
849 {"tokenId": "t1", "outcome": "Yes", "price": "0.7", "winner": true},
850 {"tokenId": "t2", "outcome": "No", "price": "0.3", "winner": false}
851 ]
852 }"#;
853 let market: Market = serde_json::from_str(json).unwrap();
854 assert_eq!(market.tokens.len(), 2);
855 assert_eq!(market.tokens[0].outcome, "Yes");
856 assert_eq!(market.tokens[1].price.as_deref(), Some("0.3"));
857 }
858
859 #[test]
860 fn test_market_volume_fields() {
861 let json = r#"{
862 "id": "1",
863 "conditionId": "0xcond",
864 "description": "Test",
865 "question": "Test?",
866 "marketMakerAddress": "0xaddr",
867 "volume24hr": 1500.5,
868 "volume1wk": 10000.0,
869 "volume1mo": 50000.0,
870 "volume1yr": 200000.0,
871 "volume24hrAmm": 100.0,
872 "volume1wkClob": 9900.0
873 }"#;
874 let market: Market = serde_json::from_str(json).unwrap();
875 assert_eq!(market.volume_24hr, Some(1500.5));
876 assert_eq!(market.volume_1wk, Some(10000.0));
877 assert_eq!(market.volume_24hr_amm, Some(100.0));
878 assert_eq!(market.volume_1wk_clob, Some(9900.0));
879 }
880
881 #[test]
882 fn test_market_denomination_token_rename() {
883 let json = r#"{
885 "id": "1",
886 "conditionId": "0xcond",
887 "description": "Test",
888 "question": "Test?",
889 "marketMakerAddress": "0xaddr",
890 "denomationToken": "USDC"
891 }"#;
892 let market: Market = serde_json::from_str(json).unwrap();
893 assert_eq!(market.denomination_token.as_deref(), Some("USDC"));
894 }
895
896 #[test]
897 fn test_market_rewards_as_map() {
898 let json = r#"{
899 "id": "1",
900 "conditionId": "0xcond",
901 "description": "Test",
902 "question": "Test?",
903 "marketMakerAddress": "0xaddr",
904 "rewards": {"min_size": "100", "max_spread": "0.05"}
905 }"#;
906 let market: Market = serde_json::from_str(json).unwrap();
907 assert!(market.rewards.is_some());
908 let rewards = market.rewards.unwrap();
909 assert_eq!(rewards["min_size"], "100");
910 }
911
912 #[test]
913 fn test_market_null_rewards() {
914 let json = r#"{
915 "id": "1",
916 "conditionId": "0xcond",
917 "description": "Test",
918 "question": "Test?",
919 "marketMakerAddress": "0xaddr",
920 "rewards": null
921 }"#;
922 let market: Market = serde_json::from_str(json).unwrap();
923 assert!(market.rewards.is_none());
924 }
925
926 #[test]
929 fn test_event_minimal() {
930 let json = r#"{"id": "evt-1"}"#;
931 let event: Event = serde_json::from_str(json).unwrap();
932 assert_eq!(event.id, "evt-1");
933 assert!(event.markets.is_empty()); assert!(event.tags.is_empty());
935 assert!(event.series.is_empty());
936 assert!(event.sub_events.is_empty());
937 }
938
939 #[test]
940 fn test_event_with_nested_markets() {
941 let json = r#"{
942 "id": "evt-1",
943 "title": "2025 Election",
944 "markets": [
945 {
946 "id": "mkt-1",
947 "conditionId": "0xabc",
948 "description": "Who wins?",
949 "question": "Who wins the election?",
950 "marketMakerAddress": "0xaddr"
951 }
952 ]
953 }"#;
954 let event: Event = serde_json::from_str(json).unwrap();
955 assert_eq!(event.markets.len(), 1);
956 assert_eq!(event.markets[0].id, "mkt-1");
957 }
958
959 #[test]
960 fn test_event_volume_24h_rename() {
961 let json = r#"{
962 "id": "evt-1",
963 "volume24hr": 5000.0
964 }"#;
965 let event: Event = serde_json::from_str(json).unwrap();
966 assert_eq!(event.volume_24hr, Some(5000.0));
967 }
968
969 #[test]
970 fn test_event_volume_24h_old_key_ignored() {
971 let json = r#"{
973 "id": "evt-1",
974 "volume24h": 5000.0
975 }"#;
976 let event: Event = serde_json::from_str(json).unwrap();
977 assert_eq!(event.volume_24hr, None);
978 }
979
980 #[test]
981 fn test_event_enable_neg_risk_rename() {
982 let json = r#"{
983 "id": "evt-1",
984 "enableNegRisk": true
985 }"#;
986 let event: Event = serde_json::from_str(json).unwrap();
987 assert_eq!(event.enable_neg_risk, Some(true));
988 }
989
990 #[test]
991 fn test_event_published_at_snake_case() {
992 let json = r#"{
993 "id": "evt-1",
994 "published_at": "2024-01-01T00:00:00Z"
995 }"#;
996 let event: Event = serde_json::from_str(json).unwrap();
997 assert_eq!(event.published_at.as_deref(), Some("2024-01-01T00:00:00Z"));
998 }
999
1000 #[test]
1001 fn test_market_question_id_capital() {
1002 let json = r#"{
1003 "id": "1",
1004 "conditionId": "0xcond",
1005 "description": "Test",
1006 "question": "Test?",
1007 "marketMakerAddress": "0xaddr",
1008 "questionID": "0xabc123"
1009 }"#;
1010 let market: Market = serde_json::from_str(json).unwrap();
1011 assert_eq!(market.question_id.as_deref(), Some("0xabc123"));
1012 }
1013
1014 #[test]
1015 fn test_market_has_reviewed_dates() {
1016 let json = r#"{
1017 "id": "1",
1018 "conditionId": "0xcond",
1019 "description": "Test",
1020 "question": "Test?",
1021 "marketMakerAddress": "0xaddr",
1022 "hasReviewedDates": true
1023 }"#;
1024 let market: Market = serde_json::from_str(json).unwrap();
1025 assert_eq!(market.has_reviewed_dates, Some(true));
1026 }
1027
1028 #[test]
1029 fn test_market_rewards_max_spread_singular() {
1030 let json = r#"{
1031 "id": "1",
1032 "conditionId": "0xcond",
1033 "description": "Test",
1034 "question": "Test?",
1035 "marketMakerAddress": "0xaddr",
1036 "rewardsMaxSpread": 0.05
1037 }"#;
1038 let market: Market = serde_json::from_str(json).unwrap();
1039 assert_eq!(market.rewards_max_spread, Some(0.05));
1040 }
1041
1042 #[test]
1043 fn test_market_submitted_by_snake_case() {
1044 let json = r#"{
1045 "id": "1",
1046 "conditionId": "0xcond",
1047 "description": "Test",
1048 "question": "Test?",
1049 "marketMakerAddress": "0xaddr",
1050 "submitted_by": "0xdeadbeef"
1051 }"#;
1052 let market: Market = serde_json::from_str(json).unwrap();
1053 assert_eq!(market.submitted_by.as_deref(), Some("0xdeadbeef"));
1054 }
1055
1056 #[test]
1059 fn test_series_info_minimal() {
1060 let json = r#"{"id": "s1", "slug": "nfl-2025", "title": "NFL 2025"}"#;
1061 let si: SeriesInfo = serde_json::from_str(json).unwrap();
1062 assert_eq!(si.slug, "nfl-2025");
1063 assert_eq!(si.title, "NFL 2025");
1064 assert!(si.ticker.is_none());
1065 assert!(si.active.is_none());
1066 }
1067
1068 #[test]
1069 fn test_series_info_full() {
1070 let json = r#"{
1071 "id": "2",
1072 "ticker": "nba",
1073 "slug": "nba",
1074 "title": "NBA",
1075 "seriesType": "single",
1076 "recurrence": "daily",
1077 "image": "https://example.com/nba.png",
1078 "icon": "https://example.com/nba-icon.png",
1079 "layout": "default",
1080 "active": true,
1081 "closed": false,
1082 "archived": false,
1083 "new": false,
1084 "featured": false,
1085 "restricted": true,
1086 "publishedAt": "2023-01-30T17:13:39Z",
1087 "createdBy": "15",
1088 "updatedBy": "15",
1089 "createdAt": "2022-10-13T00:36:01Z",
1090 "updatedAt": "2026-03-04T12:03:42Z",
1091 "commentsEnabled": false,
1092 "competitive": "0",
1093 "volume24hr": 11.07,
1094 "startDate": "2021-01-01T17:00:00Z",
1095 "commentCount": 6274,
1096 "requiresTranslation": false
1097 }"#;
1098 let si: SeriesInfo = serde_json::from_str(json).unwrap();
1099 assert_eq!(si.ticker.as_deref(), Some("nba"));
1100 assert_eq!(si.series_type.as_deref(), Some("single"));
1101 assert_eq!(si.active, Some(true));
1102 assert_eq!(si.closed, Some(false));
1103 assert_eq!(si.volume_24hr, Some(11.07));
1104 assert_eq!(si.comment_count, Some(6274));
1105 assert_eq!(si.requires_translation, Some(false));
1106 }
1107
1108 #[test]
1111 fn test_market_old_question_id_ignored() {
1112 let json = r#"{
1114 "id": "1",
1115 "conditionId": "0xcond",
1116 "description": "Test",
1117 "question": "Test?",
1118 "marketMakerAddress": "0xaddr",
1119 "questionId": "0xwrong"
1120 }"#;
1121 let market: Market = serde_json::from_str(json).unwrap();
1122 assert!(market.question_id.is_none());
1123 }
1124
1125 #[test]
1126 fn test_market_old_has_review_dates_ignored() {
1127 let json = r#"{
1129 "id": "1",
1130 "conditionId": "0xcond",
1131 "description": "Test",
1132 "question": "Test?",
1133 "marketMakerAddress": "0xaddr",
1134 "hasReviewDates": true
1135 }"#;
1136 let market: Market = serde_json::from_str(json).unwrap();
1137 assert!(market.has_reviewed_dates.is_none());
1138 }
1139
1140 #[test]
1141 fn test_market_old_rewards_max_spreads_ignored() {
1142 let json = r#"{
1144 "id": "1",
1145 "conditionId": "0xcond",
1146 "description": "Test",
1147 "question": "Test?",
1148 "marketMakerAddress": "0xaddr",
1149 "rewardsMaxSpreads": 0.05
1150 }"#;
1151 let market: Market = serde_json::from_str(json).unwrap();
1152 assert!(market.rewards_max_spread.is_none());
1153 }
1154
1155 #[test]
1156 fn test_event_old_enalbe_neg_risk_ignored() {
1157 let json = r#"{
1159 "id": "evt-1",
1160 "enalbeNegRisk": true
1161 }"#;
1162 let event: Event = serde_json::from_str(json).unwrap();
1163 assert!(event.enable_neg_risk.is_none());
1164 }
1165
1166 #[test]
1167 fn test_event_camel_published_at_ignored() {
1168 let json = r#"{
1170 "id": "evt-1",
1171 "publishedAt": "2024-01-01T00:00:00Z"
1172 }"#;
1173 let event: Event = serde_json::from_str(json).unwrap();
1174 assert!(event.published_at.is_none());
1175 }
1176
1177 #[test]
1178 fn test_market_camel_submitted_by_ignored() {
1179 let json = r#"{
1181 "id": "1",
1182 "conditionId": "0xcond",
1183 "description": "Test",
1184 "question": "Test?",
1185 "marketMakerAddress": "0xaddr",
1186 "submittedBy": "0xwrong"
1187 }"#;
1188 let market: Market = serde_json::from_str(json).unwrap();
1189 assert!(market.submitted_by.is_none());
1190 }
1191
1192 #[test]
1195 fn test_series_data_minimal() {
1196 let json = r#"{
1197 "id": "s1",
1198 "slug": "nfl",
1199 "title": "NFL",
1200 "active": true,
1201 "closed": false,
1202 "archived": false
1203 }"#;
1204 let sd: SeriesData = serde_json::from_str(json).unwrap();
1205 assert!(sd.active);
1206 assert!(!sd.closed);
1207 assert!(sd.events.is_empty()); assert!(sd.tags.is_empty());
1209 }
1210
1211 #[test]
1214 fn test_sport_metadata() {
1215 let json = r#"{
1216 "id": 1,
1217 "sport": "Basketball",
1218 "image": "https://example.com/nba.png",
1219 "createdAt": "2024-01-01T00:00:00Z"
1220 }"#;
1221 let sm: SportMetadata = serde_json::from_str(json).unwrap();
1222 assert_eq!(sm.id, 1);
1223 assert_eq!(sm.sport, "Basketball");
1224 }
1225
1226 #[test]
1229 fn test_team() {
1230 let json = r#"{
1231 "id": 42,
1232 "name": "Lakers",
1233 "league": "NBA",
1234 "abbreviation": "LAL",
1235 "createdAt": "2024-01-01T00:00:00Z",
1236 "updatedAt": "2024-06-15T12:00:00Z"
1237 }"#;
1238 let team: Team = serde_json::from_str(json).unwrap();
1239 assert_eq!(team.id, 42);
1240 assert_eq!(team.name.as_deref(), Some("Lakers"));
1241 assert!(team.created_at.is_some());
1242 }
1243
1244 #[test]
1247 fn test_comment_deserialization() {
1248 let json = r#"{
1249 "id": "c1",
1250 "body": "I think this market will resolve yes.",
1251 "createdAt": "2024-06-01T10:00:00Z",
1252 "updatedAt": "2024-06-01T10:00:00Z",
1253 "deletedAt": null,
1254 "user": {"id": "u1", "name": "trader1", "avatar": null},
1255 "marketId": "mkt-1",
1256 "eventId": null,
1257 "seriesId": null,
1258 "parentId": null,
1259 "reactions": [],
1260 "positions": [
1261 {"tokenId": "t1", "outcome": "Yes", "shares": "100.5"}
1262 ],
1263 "likeCount": 5,
1264 "dislikeCount": 1,
1265 "replyCount": 3
1266 }"#;
1267 let comment: Comment = serde_json::from_str(json).unwrap();
1268 assert_eq!(comment.id, "c1");
1269 assert_eq!(comment.user.name, "trader1");
1270 assert_eq!(comment.like_count, 5);
1271 assert_eq!(comment.positions.len(), 1);
1272 assert_eq!(comment.positions[0].shares, "100.5");
1273 assert!(comment.deleted_at.is_none());
1274 }
1275
1276 #[test]
1279 fn test_user_response() {
1280 let json = r#"{
1281 "proxyWallet": "0xproxy",
1282 "address": "0xsigner",
1283 "id": "u1",
1284 "name": "polytrader"
1285 }"#;
1286 let user: crate::api::user::UserResponse = serde_json::from_str(json).unwrap();
1287 assert_eq!(user.proxy.as_deref(), Some("0xproxy"));
1288 assert_eq!(user.name.as_deref(), Some("polytrader"));
1289 }
1290
1291 #[test]
1292 fn test_user_response_all_null() {
1293 let json = r#"{}"#;
1294 let user: crate::api::user::UserResponse = serde_json::from_str(json).unwrap();
1295 assert!(user.proxy.is_none());
1296 assert!(user.address.is_none());
1297 assert!(user.id.is_none());
1298 assert!(user.name.is_none());
1299 assert!(user.created_at.is_none());
1300 assert!(user.profile_image.is_none());
1301 assert!(user.display_username_public.is_none());
1302 assert!(user.bio.is_none());
1303 assert!(user.pseudonym.is_none());
1304 assert!(user.x_username.is_none());
1305 assert!(user.verified_badge.is_none());
1306 assert!(user.users.is_empty());
1307 }
1308
1309 #[test]
1310 fn test_user_response_full_profile() {
1311 let json = r#"{
1312 "proxyWallet": "0xproxy",
1313 "address": "0xsigner",
1314 "id": "u1",
1315 "name": "polytrader",
1316 "createdAt": "2024-01-15T10:00:00Z",
1317 "profileImage": "https://example.com/avatar.png",
1318 "displayUsernamePublic": true,
1319 "bio": "DeFi enthusiast",
1320 "pseudonym": "poly_anon",
1321 "xUsername": "polytrader_x",
1322 "verifiedBadge": true,
1323 "users": [
1324 {"id": "uid-1", "creator": true, "mod": false},
1325 {"id": "uid-2", "creator": false, "mod": true}
1326 ]
1327 }"#;
1328 let user: crate::api::user::UserResponse = serde_json::from_str(json).unwrap();
1329 assert_eq!(user.proxy.as_deref(), Some("0xproxy"));
1330 assert_eq!(user.name.as_deref(), Some("polytrader"));
1331 assert_eq!(user.created_at.as_deref(), Some("2024-01-15T10:00:00Z"));
1332 assert_eq!(
1333 user.profile_image.as_deref(),
1334 Some("https://example.com/avatar.png")
1335 );
1336 assert_eq!(user.display_username_public, Some(true));
1337 assert_eq!(user.bio.as_deref(), Some("DeFi enthusiast"));
1338 assert_eq!(user.pseudonym.as_deref(), Some("poly_anon"));
1339 assert_eq!(user.x_username.as_deref(), Some("polytrader_x"));
1340 assert_eq!(user.verified_badge, Some(true));
1341 assert_eq!(user.users.len(), 2);
1342 assert!(user.users[0].creator);
1343 assert!(!user.users[0].moderator);
1344 assert!(!user.users[1].creator);
1345 assert!(user.users[1].moderator);
1346 }
1347
1348 #[test]
1349 fn test_user_info_deserialization() {
1350 let json = r#"{"id": "uid-1", "creator": true, "mod": false}"#;
1351 let info: crate::api::user::UserInfo = serde_json::from_str(json).unwrap();
1352 assert_eq!(info.id.as_deref(), Some("uid-1"));
1353 assert!(info.creator);
1354 assert!(!info.moderator);
1355 }
1356
1357 #[test]
1358 fn test_user_info_defaults() {
1359 let json = r#"{}"#;
1360 let info: crate::api::user::UserInfo = serde_json::from_str(json).unwrap();
1361 assert!(info.id.is_none());
1362 assert!(!info.creator);
1363 assert!(!info.moderator);
1364 }
1365
1366 #[test]
1369 fn test_count_response() {
1370 let json = r#"{"count": 42}"#;
1371 let resp: CountResponse = serde_json::from_str(json).unwrap();
1372 assert_eq!(resp.count, 42);
1373 }
1374
1375 #[test]
1378 fn test_cursor_with_next() {
1379 let json = r#"{"nextCursor": "abc123"}"#;
1380 let cursor: Cursor = serde_json::from_str(json).unwrap();
1381 assert_eq!(cursor.next_cursor.as_deref(), Some("abc123"));
1382 }
1383
1384 #[test]
1385 fn test_cursor_without_next() {
1386 let json = r#"{"nextCursor": null}"#;
1387 let cursor: Cursor = serde_json::from_str(json).unwrap();
1388 assert!(cursor.next_cursor.is_none());
1389 }
1390
1391 #[test]
1392 fn test_paginated_response() {
1393 let json = r#"{
1394 "data": [{"tokenId": "t1", "outcome": "Yes"}],
1395 "nextCursor": "page2"
1396 }"#;
1397 let resp: PaginatedResponse<MarketToken> = serde_json::from_str(json).unwrap();
1398 assert_eq!(resp.data.len(), 1);
1399 assert_eq!(resp.next_cursor.as_deref(), Some("page2"));
1400 }
1401
1402 #[test]
1403 fn test_paginated_response_empty() {
1404 let json = r#"{"data": [], "nextCursor": null}"#;
1405 let resp: PaginatedResponse<MarketToken> = serde_json::from_str(json).unwrap();
1406 assert!(resp.data.is_empty());
1407 assert!(resp.next_cursor.is_none());
1408 }
1409
1410 #[test]
1413 fn test_market_token_roundtrip() {
1414 let token = MarketToken {
1415 token_id: "123".into(),
1416 outcome: "Yes".into(),
1417 price: Some("0.75".into()),
1418 winner: Some(true),
1419 };
1420 let json = serde_json::to_string(&token).unwrap();
1421 let back: MarketToken = serde_json::from_str(&json).unwrap();
1422 assert_eq!(token, back);
1423 }
1424
1425 #[test]
1428 fn test_event_creator_full() {
1429 let json = r#"{
1430 "id": "7",
1431 "creatorName": "Polymarket Sports",
1432 "creatorHandle": "poly_sports",
1433 "creatorUrl": "https://example.com",
1434 "creatorImage": "https://example.com/a.png",
1435 "createdAt": "2024-01-01T00:00:00Z",
1436 "updatedAt": "2024-06-01T00:00:00Z"
1437 }"#;
1438 let c: EventCreator = serde_json::from_str(json).unwrap();
1439 assert_eq!(c.id, "7");
1440 assert_eq!(c.creator_name.as_deref(), Some("Polymarket Sports"));
1441 assert_eq!(c.creator_handle.as_deref(), Some("poly_sports"));
1442 assert_eq!(c.creator_url.as_deref(), Some("https://example.com"));
1443 }
1444
1445 #[test]
1446 fn test_event_creator_minimal() {
1447 let json = r#"{"id": "1"}"#;
1448 let c: EventCreator = serde_json::from_str(json).unwrap();
1449 assert_eq!(c.id, "1");
1450 assert!(c.creator_name.is_none());
1451 assert!(c.creator_handle.is_none());
1452 assert!(c.created_at.is_none());
1453 }
1454
1455 #[test]
1456 fn test_event_creator_roundtrip() {
1457 let c = EventCreator {
1458 id: "9".into(),
1459 creator_name: Some("Poly".into()),
1460 creator_handle: Some("poly".into()),
1461 creator_url: None,
1462 creator_image: None,
1463 created_at: None,
1464 updated_at: None,
1465 };
1466 let json = serde_json::to_string(&c).unwrap();
1467 let back: EventCreator = serde_json::from_str(&json).unwrap();
1468 assert_eq!(c, back);
1469 }
1470
1471 #[test]
1474 fn test_pagination_deserialization() {
1475 let json = r#"{"hasMore": true, "totalResults": 124}"#;
1476 let p: Pagination = serde_json::from_str(json).unwrap();
1477 assert_eq!(p.has_more, Some(true));
1478 assert_eq!(p.total_results, Some(124));
1479 }
1480
1481 #[test]
1482 fn test_events_pagination_deserialization() {
1483 let json = r#"{
1484 "data": [{"id": "e1"}, {"id": "e2"}],
1485 "pagination": {"hasMore": false, "totalResults": 2}
1486 }"#;
1487 let ep: EventsPagination = serde_json::from_str(json).unwrap();
1488 assert_eq!(ep.data.len(), 2);
1489 assert_eq!(ep.data[0].id, "e1");
1490 let p = ep.pagination.unwrap();
1491 assert_eq!(p.has_more, Some(false));
1492 assert_eq!(p.total_results, Some(2));
1493 }
1494
1495 #[test]
1496 fn test_events_pagination_empty_data() {
1497 let json = r#"{"pagination": {"hasMore": false, "totalResults": 0}}"#;
1498 let ep: EventsPagination = serde_json::from_str(json).unwrap();
1499 assert!(ep.data.is_empty());
1500 }
1501
1502 #[test]
1505 fn test_keyset_events_response_with_cursor() {
1506 let json = r#"{
1508 "events": [{"id": "e1", "title": "Test"}],
1509 "next_cursor": "cursor-123"
1510 }"#;
1511 let resp: KeysetEventsResponse = serde_json::from_str(json).unwrap();
1512 assert_eq!(resp.events.len(), 1);
1513 assert_eq!(resp.next_cursor.as_deref(), Some("cursor-123"));
1514 }
1515
1516 #[test]
1517 fn test_keyset_events_response_last_page() {
1518 let json = r#"{"events": []}"#;
1519 let resp: KeysetEventsResponse = serde_json::from_str(json).unwrap();
1520 assert!(resp.events.is_empty());
1521 assert!(resp.next_cursor.is_none());
1522 }
1523
1524 #[test]
1525 fn test_tag_roundtrip() {
1526 let tag = Tag {
1527 id: "1".into(),
1528 slug: "test".into(),
1529 label: "Test".into(),
1530 force_show: None,
1531 published_at: None,
1532 created_by: None,
1533 updated_by: None,
1534 created_at: None,
1535 updated_at: None,
1536 force_hide: None,
1537 is_carousel: None,
1538 };
1539 let json = serde_json::to_string(&tag).unwrap();
1540 let back: Tag = serde_json::from_str(&json).unwrap();
1541 assert_eq!(tag, back);
1542 }
1543
1544 #[test]
1547 fn test_market_description_deserialization() {
1548 let json = r#"{"description": "Will X happen?"}"#;
1549 let md: MarketDescription = serde_json::from_str(json).unwrap();
1550 assert_eq!(md.description.as_deref(), Some("Will X happen?"));
1551 }
1552
1553 #[test]
1554 fn test_market_description_null() {
1555 let json = r#"{"description": null}"#;
1556 let md: MarketDescription = serde_json::from_str(json).unwrap();
1557 assert!(md.description.is_none());
1558 }
1559
1560 #[test]
1563 fn test_markets_information_body_default_serializes_empty() {
1564 let body = MarketsInformationBody::default();
1565 let json = serde_json::to_string(&body).unwrap();
1566 assert_eq!(json, "{}");
1567 }
1568
1569 #[test]
1570 fn test_markets_information_body_populated_roundtrip() {
1571 let body = MarketsInformationBody {
1572 id: vec![1, 2, 3],
1573 slug: vec!["will-x".into(), "will-y".into()],
1574 closed: Some(true),
1575 clob_token_ids: vec!["tok-a".into()],
1576 condition_ids: vec!["0xcond".into()],
1577 market_maker_address: vec!["0xmm".into()],
1578 liquidity_num_min: Some(100.0),
1579 liquidity_num_max: Some(10_000.0),
1580 volume_num_min: Some(50.0),
1581 volume_num_max: None,
1582 start_date_min: Some("2025-01-01T00:00:00Z".into()),
1583 start_date_max: None,
1584 end_date_min: None,
1585 end_date_max: Some("2026-01-01T00:00:00Z".into()),
1586 related_tags: Some(true),
1587 tag_id: Some(42),
1588 cyom: Some(false),
1589 uma_resolution_status: Some("resolved".into()),
1590 game_id: Some("game-7".into()),
1591 sports_market_types: vec!["moneyline".into(), "spread".into()],
1592 rewards_min_size: Some(10.0),
1593 question_ids: vec!["q1".into()],
1594 include_tags: Some(true),
1595 };
1596 let json = serde_json::to_string(&body).unwrap();
1597 assert!(json.contains("\"clobTokenIds\""));
1599 assert!(json.contains("\"marketMakerAddress\""));
1600 assert!(json.contains("\"rewardsMinSize\""));
1601 let back: MarketsInformationBody = serde_json::from_str(&json).unwrap();
1602 assert_eq!(body, back);
1603 }
1604
1605 #[test]
1606 fn test_markets_information_body_partial_omits_empty() {
1607 let body = MarketsInformationBody {
1608 closed: Some(true),
1609 ..Default::default()
1610 };
1611 let json = serde_json::to_string(&body).unwrap();
1612 assert_eq!(json, r#"{"closed":true}"#);
1613 }
1614
1615 #[test]
1618 fn test_keyset_markets_response_full() {
1619 let json = r#"{
1620 "markets": [{
1621 "id": "1",
1622 "conditionId": "0xcond",
1623 "description": "desc",
1624 "question": "q?",
1625 "marketMakerAddress": "0xmm"
1626 }],
1627 "next_cursor": "abc"
1628 }"#;
1629 let resp: KeysetMarketsResponse = serde_json::from_str(json).unwrap();
1630 assert_eq!(resp.markets.len(), 1);
1631 assert_eq!(resp.markets[0].id, "1");
1632 assert_eq!(resp.next_cursor.as_deref(), Some("abc"));
1633 }
1634
1635 #[test]
1636 fn test_keyset_markets_response_last_page() {
1637 let json = r#"{"markets": []}"#;
1638 let resp: KeysetMarketsResponse = serde_json::from_str(json).unwrap();
1639 assert!(resp.markets.is_empty());
1640 assert!(resp.next_cursor.is_none());
1641 }
1642
1643 #[test]
1646 fn test_series_summary_full() {
1647 let json = r#"{
1648 "id": "s-1",
1649 "title": "NFL 2025",
1650 "slug": "nfl-2025",
1651 "eventDates": ["2025-09-01", "2025-09-08"],
1652 "eventWeeks": [1, 2],
1653 "earliest_open_week": 1,
1654 "earliest_open_date": "2025-09-01"
1655 }"#;
1656 let s: SeriesSummary = serde_json::from_str(json).unwrap();
1657 assert_eq!(s.id, "s-1");
1658 assert_eq!(s.title.as_deref(), Some("NFL 2025"));
1659 assert_eq!(s.event_dates.len(), 2);
1660 assert_eq!(s.event_weeks, vec![1, 2]);
1661 assert_eq!(s.earliest_open_week, Some(1));
1662 }
1663
1664 #[test]
1665 fn test_series_summary_minimal() {
1666 let json = r#"{"id": "s-1"}"#;
1667 let s: SeriesSummary = serde_json::from_str(json).unwrap();
1668 assert_eq!(s.id, "s-1");
1669 assert!(s.title.is_none());
1670 assert!(s.event_dates.is_empty());
1671 assert!(s.event_weeks.is_empty());
1672 }
1673
1674 #[test]
1675 fn test_series_summary_roundtrip_preserves_mixed_casing() {
1676 let s = SeriesSummary {
1677 id: "s-1".into(),
1678 title: Some("T".into()),
1679 slug: Some("slug".into()),
1680 event_dates: vec!["2025-09-01".into()],
1681 event_weeks: vec![1],
1682 earliest_open_week: Some(1),
1683 earliest_open_date: Some("2025-09-01".into()),
1684 };
1685 let json = serde_json::to_string(&s).unwrap();
1686 assert!(json.contains("\"eventDates\""));
1688 assert!(json.contains("\"eventWeeks\""));
1689 assert!(json.contains("\"earliest_open_week\""));
1690 assert!(json.contains("\"earliest_open_date\""));
1691 let back: SeriesSummary = serde_json::from_str(&json).unwrap();
1692 assert_eq!(s, back);
1693 }
1694
1695 #[test]
1698 fn test_profile_minimal() {
1699 let json = r#"{"id": "p-1"}"#;
1700 let p: Profile = serde_json::from_str(json).unwrap();
1701 assert_eq!(p.id, "p-1");
1702 assert!(p.name.is_none());
1703 assert!(p.proxy_wallet.is_none());
1704 }
1705
1706 #[test]
1707 fn test_profile_full() {
1708 let json = r#"{
1709 "id": "p-1",
1710 "name": "Alice",
1711 "user": 42,
1712 "proxyWallet": "0xdead",
1713 "utmSource": "direct",
1714 "walletActivated": true,
1715 "isCloseOnly": false,
1716 "profileImageOptimized": {"medium": "https://cdn/p/m.png"}
1717 }"#;
1718 let p: Profile = serde_json::from_str(json).unwrap();
1719 assert_eq!(p.name.as_deref(), Some("Alice"));
1720 assert_eq!(p.user, Some(42));
1721 assert_eq!(p.proxy_wallet.as_deref(), Some("0xdead"));
1722 assert_eq!(p.utm_source.as_deref(), Some("direct"));
1723 assert_eq!(p.wallet_activated, Some(true));
1724 assert_eq!(p.is_close_only, Some(false));
1725 assert!(p.profile_image_optimized.is_some());
1726 }
1727
1728 #[test]
1729 fn test_profile_roundtrip() {
1730 let p = Profile {
1731 id: "p-1".into(),
1732 name: Some("A".into()),
1733 user: Some(1),
1734 referral: None,
1735 created_by: None,
1736 updated_by: None,
1737 created_at: None,
1738 updated_at: None,
1739 utm_source: None,
1740 utm_medium: None,
1741 utm_campaign: None,
1742 utm_content: None,
1743 utm_term: None,
1744 wallet_activated: Some(true),
1745 pseudonym: None,
1746 display_username_public: None,
1747 profile_image: None,
1748 bio: None,
1749 proxy_wallet: Some("0xpw".into()),
1750 profile_image_optimized: None,
1751 is_close_only: None,
1752 is_cert_req: None,
1753 cert_req_date: None,
1754 };
1755 let json = serde_json::to_string(&p).unwrap();
1756 assert!(json.contains("\"proxyWallet\""));
1757 assert!(json.contains("\"walletActivated\""));
1758 let back: Profile = serde_json::from_str(&json).unwrap();
1759 assert_eq!(p, back);
1760 }
1761}