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 {
65 self.request = self.request.query_many("events_tag", tag_ids);
66 self
67 }
68
69 pub fn keep_closed_markets(mut self, keep: i32) -> Self {
71 self.request = self.request.query("keep_closed_markets", keep);
72 self
73 }
74
75 pub fn sort(mut self, sort: impl Into<String>) -> Self {
77 self.request = self.request.query("sort", sort.into());
78 self
79 }
80
81 pub fn ascending(mut self, ascending: bool) -> Self {
83 self.request = self.request.query("ascending", ascending);
84 self
85 }
86
87 pub fn search_tags(mut self, include: bool) -> Self {
89 self.request = self.request.query("search_tags", include);
90 self
91 }
92
93 pub fn recurrence(mut self, recurrence: impl Into<String>) -> Self {
95 self.request = self.request.query("recurrence", recurrence.into());
96 self
97 }
98
99 pub fn exclude_tag_id(mut self, tag_ids: impl IntoIterator<Item = i64>) -> Self {
104 self.request = self.request.query_many("exclude_tag_id", tag_ids);
105 self
106 }
107
108 pub fn optimized(mut self, optimized: bool) -> Self {
110 self.request = self.request.query("optimized", optimized);
111 self
112 }
113
114 pub async fn send(self) -> Result<SearchResponse, GammaError> {
116 self.request.send().await
117 }
118}
119
120#[cfg_attr(feature = "specta", derive(specta::Type))]
122#[derive(Debug, Clone, Serialize, Deserialize)]
123#[serde(rename_all = "camelCase")]
124pub struct SearchResponse {
125 #[serde(default)]
127 pub profiles: Vec<SearchProfile>,
128 #[serde(default)]
130 pub events: Vec<Event>,
131 #[serde(default)]
133 pub tags: Vec<Tag>,
134}
135
136#[cfg_attr(feature = "specta", derive(specta::Type))]
138#[derive(Debug, Clone, Serialize, Deserialize)]
139#[serde(rename_all = "camelCase")]
140pub struct SearchProfile {
141 pub address: Option<String>,
143 pub name: Option<String>,
145 pub profile_image: Option<String>,
147 pub pseudonym: Option<String>,
149 pub bio: Option<String>,
151 pub proxy_wallet: Option<String>,
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158 use crate::Gamma;
159
160 fn gamma() -> Gamma {
161 Gamma::new().unwrap()
162 }
163
164 #[test]
165 fn test_public_search_full_chain() {
166 let _search = gamma()
167 .search()
168 .public_search("bitcoin")
169 .search_profiles(true)
170 .limit_per_type(10)
171 .page(1)
172 .cache(false)
173 .events_status("active")
174 .events_tag(vec![1i64, 2])
175 .keep_closed_markets(0)
176 .sort("volume")
177 .search_tags(true)
178 .recurrence("daily")
179 .exclude_tag_id(vec![99i64])
180 .optimized(true);
181 }
182
183 #[test]
184 fn test_search_response_deserialization() {
185 let json = r#"{
186 "profiles": [
187 {
188 "address": "0xabc",
189 "name": "trader1",
190 "profileImage": null,
191 "pseudonym": null,
192 "bio": null,
193 "proxyWallet": "0xproxy"
194 }
195 ],
196 "events": [],
197 "tags": []
198 }"#;
199 let resp: SearchResponse = serde_json::from_str(json).unwrap();
200 assert_eq!(resp.profiles.len(), 1);
201 assert_eq!(resp.profiles[0].address.as_deref(), Some("0xabc"));
202 assert!(resp.events.is_empty());
203 assert!(resp.tags.is_empty());
204 }
205
206 #[test]
207 fn test_search_response_empty() {
208 let json = r#"{"profiles": [], "events": [], "tags": []}"#;
209 let resp: SearchResponse = serde_json::from_str(json).unwrap();
210 assert!(resp.profiles.is_empty());
211 }
212
213 #[test]
214 fn test_search_response_missing_fields() {
215 let json = r#"{}"#;
216 let resp: SearchResponse = serde_json::from_str(json).unwrap();
217 assert!(resp.profiles.is_empty());
218 assert!(resp.events.is_empty());
219 assert!(resp.tags.is_empty());
220 }
221
222 #[test]
223 fn test_search_profile_deserialization() {
224 let json = r#"{
225 "address": "0x123",
226 "name": "Searcher",
227 "profileImage": "https://img.example.com/pic.png",
228 "pseudonym": "anon",
229 "bio": "A bio",
230 "proxyWallet": "0xproxy123"
231 }"#;
232 let profile: SearchProfile = serde_json::from_str(json).unwrap();
233 assert_eq!(profile.address.as_deref(), Some("0x123"));
234 assert_eq!(profile.name.as_deref(), Some("Searcher"));
235 assert_eq!(profile.bio.as_deref(), Some("A bio"));
236 assert_eq!(profile.proxy_wallet.as_deref(), Some("0xproxy123"));
237 }
238
239 #[test]
240 fn test_search_profile_all_null() {
241 let json = r#"{}"#;
242 let profile: SearchProfile = serde_json::from_str(json).unwrap();
243 assert!(profile.address.is_none());
244 assert!(profile.name.is_none());
245 assert!(profile.profile_image.is_none());
246 assert!(profile.pseudonym.is_none());
247 assert!(profile.bio.is_none());
248 assert!(profile.proxy_wallet.is_none());
249 }
250}