lychee_lib/ratelimit/host/
key.rs1use serde::Deserialize;
2use std::fmt;
3use url::Url;
4
5use crate::ErrorKind;
6use crate::types::Result;
7
8#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)]
24pub struct HostKey(String);
25
26impl HostKey {
27 #[must_use]
29 pub fn as_str(&self) -> &str {
30 &self.0
31 }
32
33 #[must_use]
35 pub fn into_string(self) -> String {
36 self.0
37 }
38}
39
40impl TryFrom<&Url> for HostKey {
41 type Error = ErrorKind;
42
43 fn try_from(url: &Url) -> Result<Self> {
44 let host = url.host_str().ok_or_else(|| ErrorKind::InvalidUrlHost)?;
45
46 Ok(HostKey(host.to_lowercase()))
48 }
49}
50
51impl TryFrom<&crate::Uri> for HostKey {
52 type Error = ErrorKind;
53
54 fn try_from(uri: &crate::Uri) -> Result<Self> {
55 Self::try_from(&uri.url)
56 }
57}
58
59impl TryFrom<Url> for HostKey {
60 type Error = ErrorKind;
61
62 fn try_from(url: Url) -> Result<Self> {
63 HostKey::try_from(&url)
64 }
65}
66
67impl fmt::Display for HostKey {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 write!(f, "{}", self.0)
70 }
71}
72
73impl From<String> for HostKey {
74 fn from(host: String) -> Self {
75 HostKey(host.to_lowercase())
76 }
77}
78
79impl From<&str> for HostKey {
80 fn from(host: &str) -> Self {
81 HostKey(host.to_lowercase())
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn test_host_key_from_url() {
91 let url = Url::parse("https://api.github.com/repos/user/repo").unwrap();
92 let host_key = HostKey::try_from(&url).unwrap();
93 assert_eq!(host_key.as_str(), "api.github.com");
94 }
95
96 #[test]
97 fn test_host_key_normalization() {
98 let url = Url::parse("https://API.GITHUB.COM/repos/user/repo").unwrap();
99 let host_key = HostKey::try_from(&url).unwrap();
100 assert_eq!(host_key.as_str(), "api.github.com");
101 }
102
103 #[test]
104 fn test_host_key_subdomain_separation() {
105 let api_url = Url::parse("https://api.github.com/").unwrap();
106 let www_url = Url::parse("https://www.github.com/").unwrap();
107
108 let api_key = HostKey::try_from(&api_url).unwrap();
109 let www_key = HostKey::try_from(&www_url).unwrap();
110
111 assert_ne!(api_key, www_key);
112 assert_eq!(api_key.as_str(), "api.github.com");
113 assert_eq!(www_key.as_str(), "www.github.com");
114 }
115
116 #[test]
117 fn test_host_key_from_string() {
118 let host_key = HostKey::from("example.com");
119 assert_eq!(host_key.as_str(), "example.com");
120
121 let host_key = HostKey::from("EXAMPLE.COM");
122 assert_eq!(host_key.as_str(), "example.com");
123 }
124
125 #[test]
126 fn test_host_key_no_host() {
127 let url = Url::parse("file:///path/to/file").unwrap();
128 let result = HostKey::try_from(&url);
129 assert!(result.is_err());
130 }
131
132 #[test]
133 fn test_host_key_display() {
134 let host_key = HostKey::from("example.com");
135 assert_eq!(format!("{host_key}"), "example.com");
136 }
137
138 #[test]
139 fn test_host_key_hash_equality() {
140 use std::collections::HashMap;
141
142 let key1 = HostKey::from("example.com");
143 let key2 = HostKey::from("EXAMPLE.COM");
144
145 let mut map = HashMap::new();
146 map.insert(key1, "value");
147
148 assert_eq!(map.get(&key2), Some(&"value"));
150 }
151}