1use crate::error::{PeasyError, Result};
2use crate::types::*;
3
4const DEFAULT_BASE_URL: &str = "https://peasyimage.com";
5
6pub struct Client {
7 http: reqwest::Client,
8 base_url: String,
9}
10
11impl Client {
12 pub fn new() -> Self {
13 Self {
14 http: reqwest::Client::new(),
15 base_url: DEFAULT_BASE_URL.to_string(),
16 }
17 }
18
19 pub fn with_base_url(base_url: &str) -> Self {
20 Self {
21 http: reqwest::Client::new(),
22 base_url: base_url.trim_end_matches('/').to_string(),
23 }
24 }
25
26 async fn get(&self, path: &str, params: &[(&str, String)]) -> Result<Vec<u8>> {
27 let url = format!("{}{}", self.base_url, path);
28 let filtered: Vec<(&str, &str)> = params
29 .iter()
30 .filter(|(_, v)| !v.is_empty())
31 .map(|(k, v)| (*k, v.as_str()))
32 .collect();
33
34 let resp = self.http.get(&url).query(&filtered).send().await?;
35 let status = resp.status().as_u16();
36
37 if status == 404 {
38 return Err(PeasyError::NotFound {
39 resource: "resource".into(),
40 identifier: path.into(),
41 });
42 }
43 if status != 200 {
44 let body = resp.text().await.unwrap_or_default();
45 return Err(PeasyError::Api { status, body });
46 }
47 let bytes = resp.bytes().await?;
48 Ok(bytes.to_vec())
49 }
50
51 fn list_params(opts: &ListOptions) -> Vec<(&'static str, String)> {
52 let mut p = Vec::new();
53 if let Some(v) = opts.page {
54 p.push(("page", v.to_string()));
55 }
56 if let Some(v) = opts.limit {
57 p.push(("limit", v.to_string()));
58 }
59 if let Some(ref v) = opts.category {
60 p.push(("category", v.clone()));
61 }
62 if let Some(ref v) = opts.search {
63 p.push(("search", v.clone()));
64 }
65 p
66 }
67
68 pub async fn list_tools(&self, opts: &ListOptions) -> Result<PaginatedResponse<Tool>> {
69 let body = self.get("/api/v1/tools/", &Self::list_params(opts)).await?;
70 Ok(serde_json::from_slice(&body)?)
71 }
72
73 pub async fn get_tool(&self, slug: &str) -> Result<Tool> {
74 let body = self.get(&format!("/api/v1/tools/{}/", slug), &[]).await?;
75 Ok(serde_json::from_slice(&body)?)
76 }
77
78 pub async fn list_categories(
79 &self,
80 opts: &ListOptions,
81 ) -> Result<PaginatedResponse<Category>> {
82 let mut params = Vec::new();
83 if let Some(v) = opts.page {
84 params.push(("page", v.to_string()));
85 }
86 if let Some(v) = opts.limit {
87 params.push(("limit", v.to_string()));
88 }
89 let body = self.get("/api/v1/categories/", ¶ms).await?;
90 Ok(serde_json::from_slice(&body)?)
91 }
92
93 pub async fn list_formats(&self, opts: &ListOptions) -> Result<PaginatedResponse<Format>> {
94 let body = self
95 .get("/api/v1/formats/", &Self::list_params(opts))
96 .await?;
97 Ok(serde_json::from_slice(&body)?)
98 }
99
100 pub async fn get_format(&self, slug: &str) -> Result<Format> {
101 let body = self
102 .get(&format!("/api/v1/formats/{}/", slug), &[])
103 .await?;
104 Ok(serde_json::from_slice(&body)?)
105 }
106
107 pub async fn list_conversions(
108 &self,
109 opts: &ListConversionsOptions,
110 ) -> Result<PaginatedResponse<Conversion>> {
111 let mut params = Vec::new();
112 if let Some(v) = opts.page {
113 params.push(("page", v.to_string()));
114 }
115 if let Some(v) = opts.limit {
116 params.push(("limit", v.to_string()));
117 }
118 if let Some(ref v) = opts.source {
119 params.push(("source", v.clone()));
120 }
121 if let Some(ref v) = opts.target {
122 params.push(("target", v.clone()));
123 }
124 let body = self.get("/api/v1/conversions/", ¶ms).await?;
125 Ok(serde_json::from_slice(&body)?)
126 }
127
128 pub async fn list_glossary(
129 &self,
130 opts: &ListOptions,
131 ) -> Result<PaginatedResponse<GlossaryTerm>> {
132 let body = self
133 .get("/api/v1/glossary/", &Self::list_params(opts))
134 .await?;
135 Ok(serde_json::from_slice(&body)?)
136 }
137
138 pub async fn get_glossary_term(&self, slug: &str) -> Result<GlossaryTerm> {
139 let body = self
140 .get(&format!("/api/v1/glossary/{}/", slug), &[])
141 .await?;
142 Ok(serde_json::from_slice(&body)?)
143 }
144
145 pub async fn list_guides(
146 &self,
147 opts: &ListGuidesOptions,
148 ) -> Result<PaginatedResponse<Guide>> {
149 let mut params = Vec::new();
150 if let Some(v) = opts.page {
151 params.push(("page", v.to_string()));
152 }
153 if let Some(v) = opts.limit {
154 params.push(("limit", v.to_string()));
155 }
156 if let Some(ref v) = opts.category {
157 params.push(("category", v.clone()));
158 }
159 if let Some(ref v) = opts.audience_level {
160 params.push(("audience_level", v.clone()));
161 }
162 if let Some(ref v) = opts.search {
163 params.push(("search", v.clone()));
164 }
165 let body = self.get("/api/v1/guides/", ¶ms).await?;
166 Ok(serde_json::from_slice(&body)?)
167 }
168
169 pub async fn get_guide(&self, slug: &str) -> Result<Guide> {
170 let body = self
171 .get(&format!("/api/v1/guides/{}/", slug), &[])
172 .await?;
173 Ok(serde_json::from_slice(&body)?)
174 }
175
176 pub async fn list_use_cases(
177 &self,
178 opts: &ListOptions,
179 ) -> Result<PaginatedResponse<UseCase>> {
180 let mut params = Vec::new();
181 if let Some(v) = opts.page {
182 params.push(("page", v.to_string()));
183 }
184 if let Some(v) = opts.limit {
185 params.push(("limit", v.to_string()));
186 }
187 if let Some(ref v) = opts.category {
188 params.push(("industry", v.clone()));
189 }
190 if let Some(ref v) = opts.search {
191 params.push(("search", v.clone()));
192 }
193 let body = self.get("/api/v1/use-cases/", ¶ms).await?;
194 Ok(serde_json::from_slice(&body)?)
195 }
196
197 pub async fn search(&self, query: &str, limit: Option<u32>) -> Result<SearchResult> {
198 let mut params = vec![("q", query.to_string())];
199 if let Some(v) = limit {
200 params.push(("limit", v.to_string()));
201 }
202 let body = self.get("/api/v1/search/", ¶ms).await?;
203 Ok(serde_json::from_slice(&body)?)
204 }
205
206 pub async fn list_sites(&self) -> Result<PaginatedResponse<Site>> {
207 let body = self.get("/api/v1/sites/", &[]).await?;
208 Ok(serde_json::from_slice(&body)?)
209 }
210
211 pub async fn openapi_spec(&self) -> Result<serde_json::Value> {
212 let body = self.get("/api/openapi.json", &[]).await?;
213 Ok(serde_json::from_slice(&body)?)
214 }
215}
216
217impl Default for Client {
218 fn default() -> Self {
219 Self::new()
220 }
221}