1#![allow(clippy::struct_field_names)]
3
4use std::fmt::Display;
5
6use clap::ValueEnum;
7use provcfg::{ClapArgs, Configurable};
8use serde::{Deserialize, Deserializer, Serialize};
9use utoipa::ToSchema;
10
11#[derive(
16 Debug, Default, Deserialize, Serialize, Eq, PartialEq, Clone, Configurable, ClapArgs, ToSchema,
17)]
18#[serde(default)]
19#[configurable(clap_prefix = "cratesio-filter")]
20pub struct CratesioFilter {
21 pub license_enabled: bool,
23
24 #[configurable(env_list)]
26 #[arg(value_delimiter = ',')]
27 pub license_allowed: Vec<String>,
28
29 #[arg(value_enum)]
32 pub license_unknown: LicenseUnknown,
33
34 #[configurable(env_list)]
36 #[arg(value_delimiter = ',')]
37 pub license_exempt_crates: Vec<String>,
38
39 #[arg(long = "cratesio-filter-min-release-age")]
46 pub min_release_age_seconds: u64,
47
48 #[configurable(env_list)]
51 #[arg(value_delimiter = ',')]
52 pub min_release_age_exempt_crates: Vec<String>,
53}
54
55#[derive(
59 Debug, Default, Deserialize, Serialize, Eq, PartialEq, Clone, Configurable, ClapArgs, ToSchema,
60)]
61#[serde(default)]
62#[configurable(clap_prefix = "registry-filter")]
63pub struct RegistryFilter {
64 pub license_enabled: bool,
66
67 #[configurable(env_list)]
69 #[arg(value_delimiter = ',')]
70 pub license_allowed: Vec<String>,
71
72 #[arg(value_enum)]
75 pub license_unknown: LicenseUnknown,
76
77 #[configurable(env_list)]
79 #[arg(value_delimiter = ',')]
80 pub license_exempt_crates: Vec<String>,
81}
82
83#[derive(Debug, PartialEq, Eq, Clone, Copy, ValueEnum, Default, ToSchema)]
86#[schema(rename_all = "lowercase")]
87pub enum LicenseUnknown {
88 #[default]
89 Deny,
90 Allow,
91}
92
93impl<'de> Deserialize<'de> for LicenseUnknown {
94 fn deserialize<D>(de: D) -> Result<Self, D::Error>
95 where
96 D: Deserializer<'de>,
97 {
98 match String::deserialize(de)?.to_lowercase().as_str() {
99 "deny" => Ok(Self::Deny),
100 "allow" => Ok(Self::Allow),
101 other => Err(serde::de::Error::custom(format!(
102 "unknown license_unknown value {other:?}; expected \"deny\" or \"allow\""
103 ))),
104 }
105 }
106}
107
108impl Serialize for LicenseUnknown {
109 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
110 where
111 S: serde::Serializer,
112 {
113 serializer.serialize_str(&self.to_string())
114 }
115}
116
117impl Display for LicenseUnknown {
118 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119 match self {
120 LicenseUnknown::Deny => write!(f, "deny"),
121 LicenseUnknown::Allow => write!(f, "allow"),
122 }
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use provcfg::Config;
129
130 use crate::settings::SettingsProv;
131 use crate::{CratesioFilter, LicenseUnknown, RegistryFilter, Settings};
132
133 fn from_toml(toml: &str) -> Result<Settings, provcfg::Error> {
134 Config::new()
135 .add_toml_str("test.toml", toml)
136 .build::<SettingsProv>()
137 .map(|prov| Settings::from(&prov))
138 }
139
140 #[test]
141 fn filters_are_disabled_by_default() {
142 let settings = Settings::default();
143
144 assert_eq!(CratesioFilter::default(), settings.cratesio_filter);
145 assert_eq!(RegistryFilter::default(), settings.registry_filter);
146 assert!(!settings.cratesio_filter.license_enabled);
147 assert!(settings.cratesio_filter.license_allowed.is_empty());
148 assert_eq!(
149 LicenseUnknown::Deny,
150 settings.cratesio_filter.license_unknown
151 );
152 assert!(settings.cratesio_filter.license_exempt_crates.is_empty());
153 assert_eq!(0, settings.cratesio_filter.min_release_age_seconds);
154 assert!(
155 settings
156 .cratesio_filter
157 .min_release_age_exempt_crates
158 .is_empty()
159 );
160 }
161
162 #[test]
163 fn both_sections_read_from_toml() {
164 let settings = from_toml(
165 r#"
166 [cratesio_filter]
167 license_enabled = true
168 license_allowed = ["MIT", "Apache-2.0"]
169 license_unknown = "allow"
170 license_exempt_crates = ["ring"]
171 min_release_age_seconds = 259200
172 min_release_age_exempt_crates = ["openssl", "tokio-util"]
173
174 [registry_filter]
175 license_enabled = true
176 license_allowed = ["MIT"]
177 "#,
178 )
179 .unwrap();
180
181 assert_eq!(
182 CratesioFilter {
183 license_enabled: true,
184 license_allowed: vec!["MIT".to_string(), "Apache-2.0".to_string()],
185 license_unknown: LicenseUnknown::Allow,
186 license_exempt_crates: vec!["ring".to_string()],
187 min_release_age_seconds: 259_200,
188 min_release_age_exempt_crates: vec![
189 "openssl".to_string(),
190 "tokio-util".to_string()
191 ],
192 },
193 settings.cratesio_filter
194 );
195 assert_eq!(
196 RegistryFilter {
197 license_enabled: true,
198 license_allowed: vec!["MIT".to_string()],
199 license_unknown: LicenseUnknown::Deny,
200 license_exempt_crates: vec![],
201 },
202 settings.registry_filter
203 );
204 }
205
206 #[test]
207 fn invalid_license_unknown_fails() {
208 let result = from_toml("[cratesio_filter]\nlicense_unknown = \"deneay\"\n");
209
210 assert!(result.is_err());
211 }
212
213 #[test]
214 fn list_settings_are_env_list_keys() {
215 let keys = crate::settings::env_list_keys();
216
217 for key in [
218 "cratesio_filter.license_allowed",
219 "cratesio_filter.license_exempt_crates",
220 "cratesio_filter.min_release_age_exempt_crates",
221 "registry_filter.license_allowed",
222 "registry_filter.license_exempt_crates",
223 ] {
224 assert!(keys.contains(&key), "{key} must be an env list key");
225 }
226 }
227}