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}