polyoxide_gamma/api/
search.rs1use polyoxide_core::{HttpClient, QueryBuilder, Request};
2use serde::{Deserialize, Serialize};
3
4use crate::{
5 error::GammaError,
6 types::{Event, Tag},
7};
8
9#[derive(Clone)]
11pub struct Search {
12 pub(crate) http_client: HttpClient,
13}
14
15impl Search {
16 pub fn public_search(&self, query: impl Into<String>) -> PublicSearch {
18 let request =
19 Request::new(self.http_client.clone(), "/public-search").query("q", query.into());
20 PublicSearch { request }
21 }
22}
23
24pub struct PublicSearch {
26 request: Request<SearchResponse, GammaError>,
27}
28
29impl PublicSearch {
30 pub fn search_profiles(mut self, include: bool) -> Self {
32 self.request = self.request.query("search_profiles", include);
33 self
34 }
35
36 pub fn limit_per_type(mut self, limit: u32) -> Self {
38 self.request = self.request.query("limit_per_type", limit);
39 self
40 }
41
42 pub fn page(mut self, page: u32) -> Self {
44 self.request = self.request.query("page", page);
45 self
46 }
47
48 pub fn cache(mut self, cache: bool) -> Self {
50 self.request = self.request.query("cache", cache);
51 self
52 }
53
54 pub fn events_status(mut self, status: impl Into<String>) -> Self {
56 self.request = self.request.query("events_status", status.into());
57 self
58 }
59
60 pub fn events_tag(mut self, tag_ids: impl IntoIterator<Item = impl ToString>) -> Self {
62 self.request = self.request.query_many("events_tag", tag_ids);
63 self
64 }
65
66 pub fn keep_closed_markets(mut self, keep: bool) -> Self {
68 self.request = self.request.query("keep_closed_markets", keep);
69 self
70 }
71
72 pub fn sort(mut self, sort: impl Into<String>) -> Self {
74 self.request = self.request.query("sort", sort.into());
75 self
76 }
77
78 pub fn search_tags(mut self, include: bool) -> Self {
80 self.request = self.request.query("search_tags", include);
81 self
82 }
83
84 pub fn recurrence(mut self, recurrence: impl Into<String>) -> Self {
86 self.request = self.request.query("recurrence", recurrence.into());
87 self
88 }
89
90 pub fn exclude_tag_id(mut self, tag_ids: impl IntoIterator<Item = i64>) -> Self {
92 self.request = self.request.query_many("exclude_tag_id", tag_ids);
93 self
94 }
95
96 pub fn optimized(mut self, optimized: bool) -> Self {
98 self.request = self.request.query("optimized", optimized);
99 self
100 }
101
102 pub async fn send(self) -> Result<SearchResponse, GammaError> {
104 self.request.send().await
105 }
106}
107
108#[cfg_attr(feature = "specta", derive(specta::Type))]
110#[derive(Debug, Clone, Serialize, Deserialize)]
111#[serde(rename_all = "camelCase")]
112pub struct SearchResponse {
113 #[serde(default)]
115 pub profiles: Vec<SearchProfile>,
116 #[serde(default)]
118 pub events: Vec<Event>,
119 #[serde(default)]
121 pub tags: Vec<Tag>,
122}
123
124#[cfg_attr(feature = "specta", derive(specta::Type))]
126#[derive(Debug, Clone, Serialize, Deserialize)]
127#[serde(rename_all = "camelCase")]
128pub struct SearchProfile {
129 pub address: Option<String>,
131 pub name: Option<String>,
133 pub profile_image: Option<String>,
135 pub pseudonym: Option<String>,
137 pub bio: Option<String>,
139 pub proxy_wallet: Option<String>,
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146 use crate::Gamma;
147
148 fn gamma() -> Gamma {
149 Gamma::new().unwrap()
150 }
151
152 #[test]
153 fn test_public_search_full_chain() {
154 let _search = gamma()
155 .search()
156 .public_search("bitcoin")
157 .search_profiles(true)
158 .limit_per_type(10)
159 .page(1)
160 .cache(false)
161 .events_status("active")
162 .events_tag(vec![1i64, 2])
163 .keep_closed_markets(false)
164 .sort("volume")
165 .search_tags(true)
166 .recurrence("daily")
167 .exclude_tag_id(vec![99i64])
168 .optimized(true);
169 }
170
171 #[test]
172 fn test_search_response_deserialization() {
173 let json = r#"{
174 "profiles": [
175 {
176 "address": "0xabc",
177 "name": "trader1",
178 "profileImage": null,
179 "pseudonym": null,
180 "bio": null,
181 "proxyWallet": "0xproxy"
182 }
183 ],
184 "events": [],
185 "tags": []
186 }"#;
187 let resp: SearchResponse = serde_json::from_str(json).unwrap();
188 assert_eq!(resp.profiles.len(), 1);
189 assert_eq!(resp.profiles[0].address.as_deref(), Some("0xabc"));
190 assert!(resp.events.is_empty());
191 assert!(resp.tags.is_empty());
192 }
193
194 #[test]
195 fn test_search_response_empty() {
196 let json = r#"{"profiles": [], "events": [], "tags": []}"#;
197 let resp: SearchResponse = serde_json::from_str(json).unwrap();
198 assert!(resp.profiles.is_empty());
199 }
200
201 #[test]
202 fn test_search_response_missing_fields() {
203 let json = r#"{}"#;
204 let resp: SearchResponse = serde_json::from_str(json).unwrap();
205 assert!(resp.profiles.is_empty());
206 assert!(resp.events.is_empty());
207 assert!(resp.tags.is_empty());
208 }
209
210 #[test]
211 fn test_search_profile_deserialization() {
212 let json = r#"{
213 "address": "0x123",
214 "name": "Searcher",
215 "profileImage": "https://img.example.com/pic.png",
216 "pseudonym": "anon",
217 "bio": "A bio",
218 "proxyWallet": "0xproxy123"
219 }"#;
220 let profile: SearchProfile = serde_json::from_str(json).unwrap();
221 assert_eq!(profile.address.as_deref(), Some("0x123"));
222 assert_eq!(profile.name.as_deref(), Some("Searcher"));
223 assert_eq!(profile.bio.as_deref(), Some("A bio"));
224 assert_eq!(profile.proxy_wallet.as_deref(), Some("0xproxy123"));
225 }
226
227 #[test]
228 fn test_search_profile_all_null() {
229 let json = r#"{}"#;
230 let profile: SearchProfile = serde_json::from_str(json).unwrap();
231 assert!(profile.address.is_none());
232 assert!(profile.name.is_none());
233 assert!(profile.profile_image.is_none());
234 assert!(profile.pseudonym.is_none());
235 assert!(profile.bio.is_none());
236 assert!(profile.proxy_wallet.is_none());
237 }
238}