fallow_config/config/
similar_code.rs1use schemars::JsonSchema;
2use serde::{Deserialize, Deserializer, Serialize};
3
4const fn default_threshold() -> f64 {
5 0.80
6}
7
8const fn default_min_lines() -> usize {
9 3
10}
11
12fn deserialize_threshold<'de, D>(deserializer: D) -> Result<f64, D::Error>
13where
14 D: Deserializer<'de>,
15{
16 let value = f64::deserialize(deserializer)?;
17 if !value.is_finite() || !(0.0..=1.0).contains(&value) {
18 return Err(serde::de::Error::custom(format!(
19 "similarCode.threshold must be finite and between 0 and 1 (got {value})"
20 )));
21 }
22 Ok(value)
23}
24
25fn deserialize_min_lines<'de, D>(deserializer: D) -> Result<usize, D::Error>
26where
27 D: Deserializer<'de>,
28{
29 let value = usize::deserialize(deserializer)?;
30 if value == 0 {
31 return Err(serde::de::Error::custom(
32 "similarCode.minLines must be at least 1",
33 ));
34 }
35 Ok(value)
36}
37
38#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
44#[serde(rename_all = "camelCase", deny_unknown_fields)]
45pub struct SimilarCodeConfig {
46 #[serde(
49 default = "default_threshold",
50 deserialize_with = "deserialize_threshold"
51 )]
52 #[schemars(range(min = 0.0, max = 1.0))]
53 pub threshold: f64,
54
55 #[serde(
57 default = "default_min_lines",
58 deserialize_with = "deserialize_min_lines"
59 )]
60 #[schemars(range(min = 1))]
61 pub min_lines: usize,
62
63 #[serde(default)]
66 pub ignore: Vec<String>,
67}
68
69impl Default for SimilarCodeConfig {
70 fn default() -> Self {
71 Self {
72 threshold: default_threshold(),
73 min_lines: default_min_lines(),
74 ignore: Vec::new(),
75 }
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn defaults_are_candidate_only_calibration() {
85 let config = SimilarCodeConfig::default();
86 assert!((config.threshold - 0.80).abs() < f64::EPSILON);
87 assert_eq!(config.min_lines, 3);
88 assert!(config.ignore.is_empty());
89 }
90
91 #[test]
92 fn invalid_threshold_and_line_floor_fail_loud() {
93 let threshold = serde_json::from_str::<SimilarCodeConfig>(r#"{"threshold":1.1}"#)
94 .unwrap_err()
95 .to_string();
96 assert!(threshold.contains("between 0 and 1"));
97 let lines = serde_json::from_str::<SimilarCodeConfig>(r#"{"minLines":0}"#)
98 .unwrap_err()
99 .to_string();
100 assert!(lines.contains("at least 1"));
101 }
102}