tinify/result.rs
1use crate::error::Result;
2use reqwest::Response;
3use std::path::Path;
4
5/// Represents the result of Tinify API operations
6///
7/// `TinifyResult` contains response data and metadata after API operations,
8/// and can be used to retrieve processed image data, metadata information, etc.
9#[derive(Debug)]
10pub struct TinifyResult {
11 response: Option<Response>,
12}
13
14impl TinifyResult {
15 /// Create a new TinifyResult object
16 ///
17 /// # Arguments
18 ///
19 /// * `response` - HTTP response object
20 pub fn new(response: Response) -> Self {
21 Self {
22 response: Some(response),
23 }
24 }
25
26 /// Get image data to memory buffer
27 ///
28 /// Read the image data from the response into a byte array.
29 /// Note: This method consumes the response data and can only be called once.
30 ///
31 /// # Examples
32 ///
33 /// ```no_run
34 /// # tokio_test::block_on(async {
35 /// use tinify::Tinify;
36 ///
37 /// let client = Tinify::new("your-api-key".to_string())?;
38 /// let source = client.source_from_file("input.png").await?;
39 /// let mut result = source.resize(Default::default()).await?;
40 ///
41 /// let image_data = result.to_buffer().await?;
42 /// println!("Image size: {} bytes", image_data.len());
43 /// # Ok::<(), tinify::TinifyError>(())
44 /// # });
45 /// ```
46 pub async fn to_buffer(&mut self) -> Result<Vec<u8>> {
47 // Since reqwest::Response can only be consumed once, we use take() to move out the response
48 let response = self.response.take().expect("Response has been consumed");
49 let bytes = response.bytes().await?;
50 Ok(bytes.to_vec())
51 }
52
53 /// Save image to local file
54 ///
55 /// Save the image data from the response to the specified local file path.
56 /// Note: This method consumes the response data and can only be called once.
57 ///
58 /// # Arguments
59 ///
60 /// * `path` - Local file path to save the image
61 ///
62 /// # Examples
63 ///
64 /// ```no_run
65 /// # tokio_test::block_on(async {
66 /// use tinify::Tinify;
67 ///
68 /// let client = Tinify::new("your-api-key".to_string())?;
69 /// let source = client.source_from_file("input.png").await?;
70 /// let mut result = source.resize(Default::default()).await?;
71 ///
72 /// result.to_file("output.png").await?;
73 /// println!("Image saved to output.png");
74 /// # Ok::<(), tinify::TinifyError>(())
75 /// # });
76 /// ```
77 pub async fn to_file<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
78 let bytes = self.to_buffer().await?;
79 tokio::fs::write(path, bytes).await?;
80 Ok(())
81 }
82
83 /// Get compression count
84 ///
85 /// Returns the compression count statistics for the current API key this month.
86 ///
87 /// # Returns
88 ///
89 /// Returns `Some(count)` if the response header contains compression count information, otherwise returns `None`.
90 pub fn compression_count(&self) -> Option<u32> {
91 self.response
92 .as_ref()?
93 .headers()
94 .get("Compression-Count")
95 .and_then(|v| v.to_str().ok())
96 .and_then(|s| s.parse().ok())
97 }
98
99 /// Get image width
100 ///
101 /// Returns the width (in pixels) of the processed image.
102 ///
103 /// # Returns
104 ///
105 /// Returns `Some(width)` if the response header contains image width information, otherwise returns `None`.
106 pub fn image_width(&self) -> Option<u32> {
107 self.response
108 .as_ref()?
109 .headers()
110 .get("Image-Width")
111 .and_then(|v| v.to_str().ok())
112 .and_then(|s| s.parse().ok())
113 }
114
115 /// Get image height
116 ///
117 /// Returns the height (in pixels) of the processed image.
118 ///
119 /// # Returns
120 ///
121 /// Returns `Some(height)` if the response header contains image height information, otherwise returns `None`.
122 pub fn image_height(&self) -> Option<u32> {
123 self.response
124 .as_ref()?
125 .headers()
126 .get("Image-Height")
127 .and_then(|v| v.to_str().ok())
128 .and_then(|s| s.parse().ok())
129 }
130
131 /// Get content type
132 ///
133 /// Returns the MIME type of the response (such as "image/jpeg", "image/png", etc.).
134 ///
135 /// # Returns
136 ///
137 /// Returns `Some(content_type)` if the response header contains content type information, otherwise returns `None`.
138 pub fn content_type(&self) -> Option<String> {
139 self.response
140 .as_ref()?
141 .headers()
142 .get("Content-Type")
143 .and_then(|v| v.to_str().ok())
144 .map(String::from)
145 }
146
147 /// Get content length
148 ///
149 /// Returns the byte size of the response content.
150 ///
151 /// # Returns
152 ///
153 /// Returns `Some(length)` if the response header contains content length information, otherwise returns `None`.
154 pub fn content_length(&self) -> Option<u64> {
155 self.response
156 .as_ref()?
157 .headers()
158 .get("Content-Length")
159 .and_then(|v| v.to_str().ok())
160 .and_then(|s| s.parse().ok())
161 }
162}
163
164impl From<TinifyResult> for Vec<u8> {
165 fn from(mut result: TinifyResult) -> Self {
166 // This is a blocking conversion - in real usage, you should use to_buffer().await
167 // This implementation is primarily for examples where we need synchronous conversion
168 tokio::task::block_in_place(|| {
169 tokio::runtime::Handle::current()
170 .block_on(async { result.to_buffer().await.unwrap_or_default() })
171 })
172 }
173}