Skip to main content

dry4rust/
file_config.rs

1// Copyright (c) 2026 Matjaz Domen Pecan
2// Copyright 2026 Umberto Gotti <umberto.gotti@umbertogotti.dev>
3// Licensed under the MIT License
4// SPDX-License-Identifier: MIT
5
6use std::path::PathBuf;
7
8use serde::Deserialize;
9
10use crate::config::Config;
11use crate::error::Result;
12use crate::threshold::Threshold;
13
14/// Configuration as `dry4rust.toml` and `[package.metadata.dry4rust]` spell it.
15///
16/// Every field is optional, because a file states what it wants changed and
17/// says nothing about the rest. Turning that into a [`Config`] is where the
18/// ranges are checked, so a value that cannot be honoured is refused at the
19/// point it is read rather than carried further.
20#[derive(Debug, Deserialize, Default)]
21#[serde(default)]
22pub struct FileConfig {
23    min_nodes: Option<usize>,
24    similarity_threshold: Option<f64>,
25    exclude: Option<Vec<String>>,
26    max_exact_duplicates: Option<usize>,
27    max_near_duplicates: Option<usize>,
28    max_exact_percent: Option<f64>,
29    max_near_percent: Option<f64>,
30    min_lines: Option<usize>,
31    exclude_tests: Option<bool>,
32    sub_function: Option<bool>,
33    min_sub_nodes: Option<usize>,
34    baseline: Option<PathBuf>,
35}
36
37impl FileConfig {
38    /// Apply what this file states to `config`, leaving the rest alone.
39    ///
40    /// # Errors
41    ///
42    /// Returns [`crate::error::Error::InvalidConfig`] naming the first field
43    /// whose value falls outside the range it allows.
44    pub fn apply_to(&self, config: Config) -> Result<Config> {
45        let mut config = config;
46        if let Some(v) = self.min_nodes {
47            config.min_nodes = v;
48        }
49        if let Some(v) = self.similarity_threshold {
50            config.similarity_threshold = Threshold::fraction("similarity_threshold", v)?;
51        }
52        if let Some(ref v) = self.exclude {
53            config.exclude.clone_from(v);
54        }
55        if let Some(v) = self.max_exact_duplicates {
56            config.max_exact_duplicates = Some(v);
57        }
58        if let Some(v) = self.max_near_duplicates {
59            config.max_near_duplicates = Some(v);
60        }
61        if let Some(v) = self.max_exact_percent {
62            config.max_exact_percent = Some(Threshold::percent("max_exact_percent", v)?);
63        }
64        if let Some(v) = self.max_near_percent {
65            config.max_near_percent = Some(Threshold::percent("max_near_percent", v)?);
66        }
67        if let Some(v) = self.min_lines {
68            config.min_lines = v;
69        }
70        if let Some(v) = self.exclude_tests {
71            config.exclude_tests = v;
72        }
73        if let Some(v) = self.sub_function {
74            config.sub_function = v;
75        }
76        if let Some(v) = self.min_sub_nodes {
77            config.min_sub_nodes = v;
78        }
79        if let Some(ref v) = self.baseline {
80            config.baseline = Some(v.clone());
81        }
82        Ok(config)
83    }
84}