1use crate::error::TinifyError;
2use crate::sync::source::Source;
3use std::path::Path;
4
5pub struct Client {
7 source: Source,
8}
9
10impl Client {
11 pub(crate) fn new<K>(key: K) -> Self
12 where
13 K: AsRef<str>,
14 {
15 Self {
16 source: Source::new(Some(key.as_ref())),
17 }
18 }
19
20 pub fn from_file<P>(self, path: P) -> Result<Source, TinifyError>
22 where
23 P: AsRef<Path>,
24 {
25 self.source.from_file(path)
26 }
27
28 pub fn from_buffer(self, buffer: &[u8]) -> Result<Source, TinifyError> {
30 self.source.from_buffer(buffer)
31 }
32
33 pub fn from_url<P>(self, url: P) -> Result<Source, TinifyError>
35 where
36 P: AsRef<str> + Into<String>,
37 {
38 self.source.from_url(url)
39 }
40}
41
42#[cfg(test)]
43mod tests {
44 use super::*;
45 use crate::convert::Convert;
46 use crate::convert::Type;
47 use crate::resize::Method;
48 use crate::resize::Resize;
49 use assert_matches::assert_matches;
50 use dotenv::dotenv;
51 use imagesize::size;
52 use reqwest::blocking::Client as ReqwestClient;
53 use std::env;
54 use std::ffi::OsStr;
55 use std::fs;
56
57 fn get_key() -> String {
58 dotenv().ok();
59 match env::var("KEY") {
60 Ok(key) => key,
61 Err(_err) => panic!("No such file or directory."),
62 }
63 }
64
65 #[test]
66 fn test_invalid_key() {
67 let client = Client::new("invalid");
68 let request = client
69 .from_url("https://tinypng.com/images/panda-happy.png")
70 .unwrap_err();
71
72 assert_matches!(request, TinifyError::ClientError { .. });
73 }
74
75 #[test]
76 fn test_compress_from_file() -> Result<(), TinifyError> {
77 let key = get_key();
78 let output = Path::new("./optimized.jpg");
79 let tmp_image = Path::new("./tmp_image.jpg");
80 let _ = Client::new(key).from_file(tmp_image)?.to_file(output);
81 let actual = fs::metadata(tmp_image)?.len();
82 let expected = fs::metadata(output)?.len();
83
84 assert_eq!(actual, 124814);
85 assert_eq!(expected, 102051);
86
87 if output.exists() {
88 fs::remove_file(output)?;
89 }
90
91 Ok(())
92 }
93
94 #[test]
95 fn test_compress_from_buffer() -> Result<(), TinifyError> {
96 let key = get_key();
97 let output = Path::new("./optimized.jpg");
98 let tmp_image = Path::new("./tmp_image.jpg");
99 let buffer = fs::read(tmp_image).unwrap();
100 let _ = Client::new(key).from_buffer(&buffer)?.to_file(output);
101 let actual = fs::metadata(tmp_image)?.len();
102 let expected = fs::metadata(output)?.len();
103
104 assert_eq!(actual, 124814);
105 assert_eq!(expected, 102051);
106
107 if output.exists() {
108 fs::remove_file(output)?;
109 }
110
111 Ok(())
112 }
113
114 #[test]
115 fn test_compress_from_url() -> Result<(), TinifyError> {
116 let key = get_key();
117 let output = Path::new("./optimized.jpg");
118 let remote_image = "https://tinypng.com/images/panda-happy.png";
119 let _ = Client::new(key).from_url(remote_image)?.to_file(output);
120 let expected = fs::metadata(output)?.len();
121 let actual = ReqwestClient::new().get(remote_image).send()?;
122
123 if let Some(content_length) = actual.content_length() {
124 assert_eq!(content_length, 30734);
125 }
126
127 assert_eq!(expected, 26324);
128
129 if output.exists() {
130 fs::remove_file(output)?;
131 }
132
133 Ok(())
134 }
135
136 #[test]
137 fn test_save_to_file() -> Result<(), TinifyError> {
138 let key = get_key();
139 let output = Path::new("./optimized.jpg");
140 let tmp_image = Path::new("./tmp_image.jpg");
141 let _ = Client::new(key).from_file(tmp_image)?.to_file(output);
142
143 assert!(output.exists());
144
145 if output.exists() {
146 fs::remove_file(output)?;
147 }
148
149 Ok(())
150 }
151
152 #[test]
153 fn test_save_to_bufffer() -> Result<(), TinifyError> {
154 let key = get_key();
155 let output = Path::new("./optimized.jpg");
156 let tmp_image = Path::new("./tmp_image.jpg");
157 let client = Client::new(key);
158 let buffer = client.from_file(tmp_image)?.to_buffer()?;
159
160 assert_eq!(buffer.capacity(), 102051);
161
162 if output.exists() {
163 fs::remove_file(output)?;
164 }
165
166 Ok(())
167 }
168
169 #[test]
170 fn test_resize_scale_width() -> Result<(), TinifyError> {
171 let key = get_key();
172 let output = Path::new("./tmp_resized.jpg");
173 let _ = Client::new(key)
174 .from_file("./tmp_image.jpg")?
175 .resize(Resize {
176 method: Method::Scale,
177 width: Some(400),
178 height: None,
179 })?
180 .to_file(output);
181
182 let (width, height) = match size(output) {
183 Ok(dim) => (dim.width, dim.height),
184 Err(err) => panic!("Error getting dimensions: {:?}", err),
185 };
186
187 assert_eq!((width, height), (400, 200));
188
189 if output.exists() {
190 fs::remove_file(output)?;
191 }
192
193 Ok(())
194 }
195
196 #[test]
197 fn test_resize_scale_height() -> Result<(), TinifyError> {
198 let key = get_key();
199 let output = Path::new("./tmp_resized.jpg");
200 let _ = Client::new(key)
201 .from_file("./tmp_image.jpg")?
202 .resize(Resize {
203 method: Method::Scale,
204 width: None,
205 height: Some(400),
206 })?
207 .to_file(output);
208
209 let (width, height) = match size(output) {
210 Ok(dim) => (dim.width, dim.height),
211 Err(err) => panic!("Error getting dimensions: {:?}", err),
212 };
213
214 assert_eq!((width, height), (800, 400));
215
216 if output.exists() {
217 fs::remove_file(output)?;
218 }
219
220 Ok(())
221 }
222
223 #[test]
224 fn test_resize_fit() -> Result<(), TinifyError> {
225 let key = get_key();
226 let output = Path::new("./tmp_resized.jpg");
227 let _ = Client::new(key)
228 .from_file("./tmp_image.jpg")?
229 .resize(Resize {
230 method: Method::Fit,
231 width: Some(400),
232 height: Some(200),
233 })?
234 .to_file(output);
235
236 let (width, height) = match size(output) {
237 Ok(dim) => (dim.width, dim.height),
238 Err(err) => panic!("Error getting dimensions: {:?}", err),
239 };
240
241 assert_eq!((width, height), (400, 200));
242
243 if output.exists() {
244 fs::remove_file(output)?;
245 }
246
247 Ok(())
248 }
249
250 #[test]
251 fn test_resize_cover() -> Result<(), TinifyError> {
252 let key = get_key();
253 let output = Path::new("./tmp_resized.jpg");
254 let _ = Client::new(key)
255 .from_file("./tmp_image.jpg")?
256 .resize(Resize {
257 method: Method::Cover,
258 width: Some(400),
259 height: Some(200),
260 })?
261 .to_file(output);
262
263 let (width, height) = match size(output) {
264 Ok(dim) => (dim.width, dim.height),
265 Err(err) => panic!("Error getting dimensions: {:?}", err),
266 };
267
268 assert_eq!((width, height), (400, 200));
269
270 if output.exists() {
271 fs::remove_file(output)?;
272 }
273
274 Ok(())
275 }
276
277 #[test]
278 fn test_resize_thumb() -> Result<(), TinifyError> {
279 let key = get_key();
280 let output = Path::new("./tmp_resized.jpg");
281 let _ = Client::new(key)
282 .from_file("./tmp_image.jpg")?
283 .resize(Resize {
284 method: Method::Thumb,
285 width: Some(400),
286 height: Some(200),
287 })?
288 .to_file(output);
289
290 let (width, height) = match size(output) {
291 Ok(dim) => (dim.width, dim.height),
292 Err(err) => panic!("Error getting dimensions: {:?}", err),
293 };
294
295 assert_eq!((width, height), (400, 200));
296
297 if output.exists() {
298 fs::remove_file(output)?;
299 }
300
301 Ok(())
302 }
303
304 #[test]
305 fn test_error_transparent_png_to_jpeg() -> Result<(), TinifyError> {
306 let key = get_key();
307 let convert = Convert {
308 r#type: vec![Type::Jpeg],
309 };
310 let request = Client::new(key)
311 .from_url("https://tinypng.com/images/panda-happy.png")?
312 .convert(convert)?
313 .to_file(Path::new("./tmp_transparent.jpg"))
314 .unwrap_err();
315
316 assert_matches!(request, TinifyError::ClientError { .. });
317
318 Ok(())
319 }
320
321 #[test]
322 fn test_convert_from_jpg_to_png() -> Result<(), TinifyError> {
323 let key = get_key();
324 let output = Path::new("./panda-sticker.png");
325 let convert = Convert {
326 r#type: vec![Type::Png],
327 };
328 let _ = Client::new(key)
329 .from_file("./tmp_image.jpg")?
330 .convert(convert)?
331 .to_file(output);
332
333 let extension = output.extension().and_then(OsStr::to_str).unwrap();
334
335 assert_eq!(extension, "png");
336
337 if output.exists() {
338 fs::remove_file(output)?;
339 }
340
341 Ok(())
342 }
343
344 #[test]
345 fn test_convert_from_jpg_to_webp() -> Result<(), TinifyError> {
346 let key = get_key();
347 let output = Path::new("./panda-sticker.webp");
348 let convert = Convert {
349 r#type: vec![Type::Webp],
350 };
351 let _ = Client::new(key)
352 .from_file("./tmp_image.jpg")?
353 .convert(convert)?
354 .to_file(output);
355
356 let extension = output.extension().and_then(OsStr::to_str).unwrap();
357
358 assert_eq!(extension, "webp");
359
360 if output.exists() {
361 fs::remove_file(output)?;
362 }
363
364 Ok(())
365 }
366
367 #[test]
368 fn test_convert_smallest_type() -> Result<(), TinifyError> {
369 let key = get_key();
370 let output = Path::new("./panda-sticker.webp");
371 let convert = Convert {
372 r#type: vec![Type::Jpeg, Type::Png, Type::Webp],
373 };
374 let _ = Client::new(key)
375 .from_url("https://tinypng.com/images/panda-happy.png")?
376 .convert(convert)?
377 .to_file(output);
378
379 let extension = output.extension().and_then(OsStr::to_str).unwrap();
380
381 assert_eq!(extension, "webp");
382
383 if output.exists() {
384 fs::remove_file(output)?;
385 }
386
387 Ok(())
388 }
389
390 #[test]
391 fn test_convert_smallest_wildcard_type() -> Result<(), TinifyError> {
392 let key = get_key();
393 let output = Path::new("./panda-sticker.webp");
394 let convert = Convert {
395 r#type: vec![Type::WildCard],
396 };
397 let _ = Client::new(key)
398 .from_url("https://tinypng.com/images/panda-happy.png")?
399 .convert(convert)?
400 .to_file(output);
401
402 let extension = output.extension().and_then(OsStr::to_str).unwrap();
403
404 assert_eq!(extension, "webp");
405
406 if output.exists() {
407 fs::remove_file(output)?;
408 }
409
410 Ok(())
411 }
412}