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)]
67 pub ignore: Vec<String>,
68}
69
70impl Default for SimilarCodeConfig {
71 fn default() -> Self {
72 Self {
73 threshold: default_threshold(),
74 min_lines: default_min_lines(),
75 ignore: Vec::new(),
76 }
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 #[test]
85 fn defaults_are_candidate_only_calibration() {
86 let config = SimilarCodeConfig::default();
87 assert!((config.threshold - 0.80).abs() < f64::EPSILON);
88 assert_eq!(config.min_lines, 3);
89 assert!(config.ignore.is_empty());
90 }
91
92 #[test]
93 fn invalid_threshold_and_line_floor_fail_loud() {
94 let threshold = serde_json::from_str::<SimilarCodeConfig>(r#"{"threshold":1.1}"#)
95 .unwrap_err()
96 .to_string();
97 assert!(threshold.contains("between 0 and 1"));
98 let lines = serde_json::from_str::<SimilarCodeConfig>(r#"{"minLines":0}"#)
99 .unwrap_err()
100 .to_string();
101 assert!(lines.contains("at least 1"));
102 }
103}