Skip to main content

kellnr_settings/
proxy.rs

1use clap_serde_derive::ClapSerde;
2use serde::{Deserialize, Serialize};
3use url::Url;
4
5fn default_proxy_url() -> Url {
6    Url::parse("https://static.crates.io/crates/").unwrap()
7}
8
9fn default_index_url() -> Url {
10    Url::parse("https://index.crates.io/").unwrap()
11}
12
13fn default_api_url() -> Url {
14    Url::parse("https://crates.io/api/v1/crates/").unwrap()
15}
16
17#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone, ClapSerde)]
18#[serde(default)]
19pub struct Proxy {
20    /// Enable crates.io proxy
21    #[default(false)]
22    #[arg(id = "proxy-enabled", long = "proxy-enabled")]
23    pub enabled: bool,
24
25    /// Number of proxy threads
26    #[default(10)]
27    #[arg(id = "proxy-num-threads", long = "proxy-num-threads")]
28    pub num_threads: usize,
29
30    /// Download crates on index update
31    #[default(false)]
32    #[arg(id = "proxy-download-on-update", long = "proxy-download-on-update")]
33    pub download_on_update: bool,
34
35    /// Crates.io download URL
36    #[default(default_proxy_url())]
37    #[arg(id = "proxy-url", long = "proxy-url")]
38    pub url: Url,
39
40    /// Crates.io index URL
41    #[default(default_index_url())]
42    #[arg(id = "proxy-index", long = "proxy-index")]
43    pub index: Url,
44
45    /// Crates.io API URL
46    #[default(default_api_url())]
47    #[arg(id = "proxy-api", long = "proxy-api")]
48    pub api: Url,
49
50    /// Connect timeout in seconds for upstream requests
51    #[default(5)]
52    #[arg(id = "proxy-connect-timeout", long = "proxy-connect-timeout")]
53    pub connect_timeout_seconds: u64,
54
55    /// Request timeout in seconds for upstream downloads
56    #[default(30)]
57    #[arg(id = "proxy-request-timeout", long = "proxy-request-timeout")]
58    pub request_timeout_seconds: u64,
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn default_api_url_is_cratesio() {
67        let proxy = Proxy::default();
68        assert_eq!(
69            proxy.api,
70            Url::parse("https://crates.io/api/v1/crates/").unwrap()
71        );
72    }
73
74    #[test]
75    fn deserialize_custom_api_url_from_toml() {
76        let toml = r#"
77            api = "https://rsproxy.cn/api/v1/crates/"
78        "#;
79        let proxy: Proxy = toml::from_str(toml).unwrap();
80        assert_eq!(
81            proxy.api,
82            Url::parse("https://rsproxy.cn/api/v1/crates/").unwrap()
83        );
84    }
85
86    #[test]
87    fn missing_api_url_uses_default() {
88        let toml = r#"
89            enabled = true
90            url = "https://rsproxy.cn/api/v1/crates/"
91            index = "https://rsproxy.cn/index/"
92        "#;
93        let proxy: Proxy = toml::from_str(toml).unwrap();
94        assert_eq!(
95            proxy.api,
96            Url::parse("https://crates.io/api/v1/crates/").unwrap()
97        );
98    }
99
100    #[test]
101    fn api_url_join_produces_correct_crate_url() {
102        let proxy: Proxy = toml::from_str(r#"api = "https://rsproxy.cn/api/v1/crates/""#).unwrap();
103        let url = proxy.api.join("serde").unwrap();
104        assert_eq!(url.as_str(), "https://rsproxy.cn/api/v1/crates/serde");
105    }
106
107    #[test]
108    fn default_url_join_produces_correct_crate_url() {
109        let proxy = Proxy::default();
110        let url = proxy.api.join("tokio").unwrap();
111        assert_eq!(url.as_str(), "https://crates.io/api/v1/crates/tokio");
112    }
113}