tinify/sync/
client.rs

1use crate::error::TinifyError;
2use crate::sync::source::Source;
3use std::path::Path;
4
5/// The Tinify Client.
6pub 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  /// Choose a file to compress.
21  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  /// Choose a buffer to compress.
29  pub fn from_buffer(self, buffer: &[u8]) -> Result<Source, TinifyError> {
30    self.source.from_buffer(buffer)
31  }
32
33  /// Choose an url image to compress.
34  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}