weavatrix_clone/
config.rs1use crate::error::{CloneError, Result};
2use crate::model::{DetectionMode, Similarity};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub struct CloneConfig {
6 pub mode: DetectionMode,
7 pub min_tokens: usize,
8 pub k_gram: usize,
9 pub winnowing_window: usize,
10 pub min_similarity: Similarity,
11 pub candidate_similarity: Similarity,
12 pub min_shared_fingerprints: usize,
13 pub max_bucket_size: usize,
14 pub max_fragments: usize,
15 pub max_tokens_per_fragment: usize,
16 pub max_candidates: usize,
17 pub compare_overlapping_fragments: bool,
18}
19
20impl Default for CloneConfig {
21 fn default() -> Self {
22 Self {
23 mode: DetectionMode::NearMiss,
24 min_tokens: 24,
25 k_gram: 8,
26 winnowing_window: 4,
27 min_similarity: Similarity::from_permille(800),
28 candidate_similarity: Similarity::from_permille(450),
29 min_shared_fingerprints: 2,
30 max_bucket_size: 128,
31 max_fragments: 1_000_000,
32 max_tokens_per_fragment: 100_000,
33 max_candidates: 5_000_000,
34 compare_overlapping_fragments: false,
35 }
36 }
37}
38
39impl CloneConfig {
40 pub fn validate(self) -> Result<Self> {
47 if self.k_gram == 0 {
48 return Err(invalid("k_gram", "must be greater than zero"));
49 }
50 if self.winnowing_window == 0 {
51 return Err(invalid("winnowing_window", "must be greater than zero"));
52 }
53 let guaranteed = self
54 .k_gram
55 .checked_add(self.winnowing_window)
56 .and_then(|value| value.checked_sub(1))
57 .ok_or(CloneError::CapacityExceeded {
58 resource: "winnowing guarantee",
59 limit: usize::MAX,
60 })?;
61 if self.min_tokens < guaranteed {
62 return Err(invalid(
63 "min_tokens",
64 "must cover at least one complete winnowing window",
65 ));
66 }
67 if self.candidate_similarity > self.min_similarity {
68 return Err(invalid(
69 "candidate_similarity",
70 "must not exceed min_similarity",
71 ));
72 }
73 for (field, value) in [
74 ("min_shared_fingerprints", self.min_shared_fingerprints),
75 ("max_bucket_size", self.max_bucket_size),
76 ("max_fragments", self.max_fragments),
77 ("max_tokens_per_fragment", self.max_tokens_per_fragment),
78 ("max_candidates", self.max_candidates),
79 ] {
80 if value == 0 {
81 return Err(invalid(field, "must be greater than zero"));
82 }
83 }
84 Ok(self)
85 }
86}
87
88const fn invalid(field: &'static str, reason: &'static str) -> CloneError {
89 CloneError::InvalidConfig { field, reason }
90}