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