resourcespace_client/api/search.rs
1use serde::{Serialize, Serializer};
2use serde_with::skip_serializing_none;
3
4use crate::client::Client;
5use crate::error::RsError;
6
7use super::{List, SortOrder};
8
9/// Sub-API for search endpoints.
10#[derive(Debug)]
11pub struct SearchApi<'a> {
12 client: &'a Client,
13}
14
15impl<'a> SearchApi<'a> {
16 pub(crate) fn new(client: &'a Client) -> Self {
17 Self { client }
18 }
19
20 /// Performs a search and returns matching resources.
21 ///
22 /// ## Arguments
23 /// * `request` - Parameters built via [`DoSearchRequest`]
24 ///
25 /// ## TODO: Errors
26 /// Returns [`RsError::OperationFailed`] if the search returns no results
27 /// or the user lacks search permissions.
28 ///
29 /// ## Examples
30 /// ```no_run
31 /// # use resourcespace_client::{Client, api::search::{DoSearchRequest}};
32 /// # use resourcespace_client::api::SortOrder;
33 /// # async fn example(client: Client) -> Result<(), Box<dyn std::error::Error>> {
34 /// let results = client.search()
35 /// .do_search(DoSearchRequest::new("cat").sort(SortOrder::Desc))
36 /// .await?;
37 ///
38 /// let specific_results = client.search()
39 /// .do_search(
40 /// DoSearchRequest::new("cat")
41 /// .fetchrows("100")
42 /// .offset(50)
43 /// .archive(0)
44 /// )
45 /// .await?;
46 /// # Ok(())
47 /// # }
48 /// ```
49 pub async fn do_search(&self, request: DoSearchRequest) -> Result<serde_json::Value, RsError> {
50 self.client
51 .send_request("do_search", reqwest::Method::GET, request)
52 .await
53 }
54
55 /// Performs a search and returns matching resources including URLs for requested preview sizes.
56 ///
57 /// ## Arguments
58 /// * `request` - Parameters built via [`SearchGetPreviewsRequest`]
59 ///
60 /// ## TODO: Errors
61 /// Returns [`RsError::OperationFailed`] if the search returns no results
62 /// or the user lacks search permissions.
63 ///
64 /// ## Examples
65 /// ```no_run
66 /// # use resourcespace_client::{Client, api::search::SearchGetPreviewsRequest};
67 /// # use resourcespace_client::api::SortOrder;
68 /// # async fn example(client: Client) -> Result<(), Box<dyn std::error::Error>> {
69 /// let results = client.search()
70 /// .search_get_previews(SearchGetPreviewsRequest::new("cat").getsizes("thm,scr"))
71 /// .await?;
72 ///
73 /// let specific_results = client.search()
74 /// .search_get_previews(
75 /// SearchGetPreviewsRequest::new("cat")
76 /// .getsizes("thm,scr,pre")
77 /// .previewext("jpg")
78 /// .sort(SortOrder::Desc)
79 /// .fetchrows("0,50")
80 /// )
81 /// .await?;
82 /// # Ok(())
83 /// # }
84 /// ```
85 pub async fn search_get_previews(
86 &self,
87 request: SearchGetPreviewsRequest,
88 ) -> Result<serde_json::Value, RsError> {
89 self.client
90 .send_request("search_get_previews", reqwest::Method::GET, request)
91 .await
92 }
93}
94
95/// The row fetch mode for a search request.
96///
97/// Use [`FetchRows::limit`] to cap the number of results, or
98/// [`FetchRows::page`] to fetch a specific window with offset and limit.
99/// Note that these two modes return different response shapes from
100/// ResourceSpace — `page` returns a structured response with a `total`
101/// count alongside the results.
102///
103/// ```no_run
104/// FetchRows::limit(100) // return up to 100 results
105/// FetchRows::page(0, 50) // return results 0–50
106/// ```
107#[derive(Clone, Debug, PartialEq)]
108pub enum FetchRows {
109 /// Return up to N rows
110 Limit(u32),
111 /// Return rows with explicit offset and limit, enables paginated response
112 Page { offset: u32, limit: u32 },
113}
114
115impl FetchRows {
116 pub fn limit(n: u32) -> Self {
117 Self::Limit(n)
118 }
119
120 pub fn page(offset: u32, limit: u32) -> Self {
121 Self::Page { offset, limit }
122 }
123}
124
125impl Serialize for FetchRows {
126 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
127 match self {
128 Self::Limit(n) => n.serialize(serializer),
129 Self::Page { offset, limit } => format!("{},{}", offset, limit).serialize(serializer),
130 }
131 }
132}
133
134#[skip_serializing_none]
135#[derive(Clone, Debug, PartialEq, Serialize)]
136pub struct DoSearchRequest {
137 /// The search string to match resources against.
138 pub search: String,
139 /// Comma-separated list of resource type IDs to restrict results to.
140 pub restypes: Option<List<u32>>,
141 /// Field name to order results by.
142 pub order_by: Option<String>,
143 /// Archive status filter: 0 = live, 1 = archived, 2 = deleted.
144 pub archive: Option<i8>,
145 /// Number of rows to return, or `"offset,rows"` for paginated fetching.
146 pub fetchrows: Option<FetchRows>,
147 /// Sort direction for the results.
148 pub sort: Option<SortOrder>,
149 /// Number of results to skip, used for pagination.
150 pub offset: Option<u32>,
151}
152
153impl DoSearchRequest {
154 pub fn new(search: impl Into<String>) -> Self {
155 Self {
156 search: search.into(),
157 restypes: None,
158 order_by: None,
159 archive: None,
160 fetchrows: None,
161 sort: None,
162 offset: None,
163 }
164 }
165
166 pub fn restypes(mut self, restypes: impl Into<List<u32>>) -> Self {
167 self.restypes = Some(restypes.into());
168 self
169 }
170
171 pub fn order_by(mut self, order_by: impl Into<String>) -> Self {
172 self.order_by = Some(order_by.into());
173 self
174 }
175
176 pub fn archive(mut self, archive: i8) -> Self {
177 self.archive = Some(archive);
178 self
179 }
180
181 pub fn fetchrows(mut self, fetchrows: FetchRows) -> Self {
182 self.fetchrows = Some(fetchrows);
183 self
184 }
185
186 pub fn sort(mut self, sort: SortOrder) -> Self {
187 self.sort = Some(sort);
188 self
189 }
190
191 pub fn offset(mut self, offset: u32) -> Self {
192 self.offset = Some(offset);
193 self
194 }
195}
196
197#[skip_serializing_none]
198#[derive(Clone, Debug, PartialEq, Serialize)]
199pub struct SearchGetPreviewsRequest {
200 /// The search string to match resources against.
201 pub search: String,
202 /// Comma-separated list of resource type IDs to restrict results to.
203 pub restypes: Option<List<u32>>,
204 /// Field name to order results by.
205 pub order_by: Option<String>,
206 /// Archive status filter: 0 = live, 1 = archived, 2 = deleted.
207 pub archive: Option<i8>,
208 /// Number of rows to return, or `"offset,rows"` for paginated fetching.
209 pub fetchrows: Option<FetchRows>,
210 /// Sort direction for the results.
211 pub sort: Option<SortOrder>,
212 /// Only return resources modified within this many days.
213 pub recent_search_daylimit: Option<String>,
214 /// Comma-separated list of preview sizes to include URLs for (e.g. `"thm,scr,pre"`).
215 pub getsizes: Option<List<u32>>,
216 /// Override the preview file extension returned (e.g. `"jpg"`).
217 pub previewext: Option<String>,
218}
219
220impl SearchGetPreviewsRequest {
221 pub fn new(search: impl Into<String>) -> Self {
222 Self {
223 search: search.into(),
224 restypes: None,
225 order_by: None,
226 archive: None,
227 fetchrows: None,
228 sort: None,
229 recent_search_daylimit: None,
230 getsizes: None,
231 previewext: None,
232 }
233 }
234
235 pub fn restypes(mut self, restypes: impl Into<List<u32>>) -> Self {
236 self.restypes = Some(restypes.into());
237 self
238 }
239
240 pub fn order_by(mut self, order_by: impl Into<String>) -> Self {
241 self.order_by = Some(order_by.into());
242 self
243 }
244
245 pub fn archive(mut self, archive: i8) -> Self {
246 self.archive = Some(archive);
247 self
248 }
249
250 pub fn fetchrows(mut self, fetchrows: FetchRows) -> Self {
251 self.fetchrows = Some(fetchrows);
252 self
253 }
254
255 pub fn sort(mut self, sort: SortOrder) -> Self {
256 self.sort = Some(sort);
257 self
258 }
259
260 pub fn recent_search_daylimit(mut self, recent_search_daylimit: impl Into<String>) -> Self {
261 self.recent_search_daylimit = Some(recent_search_daylimit.into());
262 self
263 }
264
265 pub fn getsizes(mut self, getsizes: impl Into<List<u32>>) -> Self {
266 self.getsizes = Some(getsizes.into());
267 self
268 }
269
270 pub fn previewext(mut self, previewext: impl Into<String>) -> Self {
271 self.previewext = Some(previewext.into());
272 self
273 }
274}