1use imagesize::ImageError;
2pub use imagesize::ImageSize;
3use reqwest::{Client, IntoUrl, Response};
4use url::Url;
5
6use crate::Error;
7
8#[non_exhaustive]
12#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
13pub enum IconKind {
14 HardcodedURL,
16 LinkedInHTML,
18 LinkedInManifest,
20}
21
22#[derive(Debug, Clone, Hash, PartialEq, Eq)]
26pub struct Icon {
27 pub kind: IconKind,
29 pub url: Url,
31 pub size: ImageSize,
33}
34
35impl Icon {
36 async fn find_size(mut response: Response) -> Result<ImageSize, Error> {
39 let mut buffer = vec![];
40 while let Some(chunk) = response.chunk().await? {
41 buffer.extend_from_slice(&chunk);
42 match imagesize::blob_size(&buffer) {
43 Ok(size) => return Ok(size),
44 Err(ImageError::IoError(_)) => continue,
45 Err(_) => return Err(Error::UnsupportedImageFormat),
46 }
47 }
48 Err(Error::UnsupportedImageFormat)
49 }
50
51 pub(crate) async fn from_url(
54 client: &Client,
55 url: impl IntoUrl,
56 kind: IconKind,
57 ) -> Result<Self, Error> {
58 let response = client.get(url).send().await?;
59 let url = response.url().to_owned();
60 let size = Icon::find_size(response).await?;
61 Ok(Icon { kind, url, size })
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 #[tokio::test]
70 async fn test_google() {
71 let client = reqwest::Client::new();
72
73 let icon = Icon::from_url(
74 &client,
75 "https://google.com/favicon.ico",
76 IconKind::HardcodedURL,
77 )
78 .await
79 .unwrap();
80
81 let ImageSize { width, height } = icon.size;
82 println!("The size of Google's favicon is {width}x{height} pixels.");
83 }
84}