Skip to main content

licensebat_core/
licrc.rs

1//! Exposes a struct to manage the `.licrc` file information and validate the dependencies accordingly.
2//!
3//! When using the `licrc-from-file` feature, a [`LicRc::from_relative_path`] associated function will be available for you to load the information from a file.
4use crate::{Dependency, RetrievedDependency};
5use serde::{Deserialize, Serialize};
6use tracing::instrument;
7
8/// Represents the `.licrc` configuration file.
9/// This file is the one used in your project to define which licenses are accepted/unaccepted
10/// and which dependencies should be ignored.
11#[derive(Serialize, Deserialize, Debug, Default, Clone)]
12pub struct LicRc {
13    /// List of accepted and unaccepted licenses.
14    pub licenses: LicRcLicenses,
15    /// List of ignored dependencies and dependency settings.
16    pub dependencies: LicRcDependencies,
17    /// Properties that affect the behavior of the validation.
18    pub behavior: LicRcBehavior,
19}
20
21/// Error raised by a collector while parsing/getting the dependencies.
22#[cfg(feature = "licrc-from-file")]
23#[derive(Debug, thiserror::Error)]
24pub enum Error {
25    /// Error trying to open or read the .licrc file
26    #[error("Error trying to open and read the .licrc file: {0}")]
27    Io(#[from] std::io::Error),
28    /// Error parsing the .licrc file
29    #[error("Error trying to parse the .licrc file: {0}")]
30    Toml(#[from] toml::de::Error),
31}
32
33#[cfg(feature = "licrc-from-file")]
34impl LicRc {
35    /// Loads a .licrc from a relative path.
36    /// You must compile this crate with the `licrc-from-file` feature for this to be available.
37    #[instrument(skip(relative_path))]
38    pub fn from_relative_path(relative_path: impl AsRef<std::path::Path>) -> Result<Self, Error> {
39        let licrc_path = std::env::current_dir()?.join(relative_path);
40        let licrc_content = std::fs::read_to_string(licrc_path)?;
41        let licrc = toml::from_str(&licrc_content)?;
42        Ok(licrc)
43    }
44}
45
46impl LicRc {
47    /// Checks if a dependency should be ignored or not.
48    /// Note that this function will set the dependency's `is_ignored` property to `true` if it's ignored.
49    pub fn is_ignored(&self, dependency: &mut RetrievedDependency) -> bool {
50        // is it explicitly ignored?
51        if self
52            .dependencies
53            .ignored
54            .as_ref()
55            .unwrap_or(&vec![])
56            .contains(&dependency.name)
57        {
58            dependency.is_ignored = true;
59            return true;
60        }
61
62        // are dev dependencies ignored?
63        if self.dependencies.ignore_dev_dependencies && dependency.is_dev.unwrap_or(false) {
64            dependency.is_ignored = true;
65            return true;
66        }
67
68        // are optional dependencies ignored?
69        if self.dependencies.ignore_optional_dependencies && dependency.is_optional.unwrap_or(false)
70        {
71            dependency.is_ignored = true;
72            return true;
73        }
74        false
75    }
76
77    /// Checks if a dependency should be retrieved or not.
78    pub fn filter_dependencies_before_retrieval(&self, dependency: &Dependency) -> bool {
79        let is_dev = dependency.is_dev.unwrap_or_default();
80        let is_optional = dependency.is_optional.unwrap_or_default();
81
82        if self.behavior.do_not_show_dev_dependencies && is_dev {
83            return false;
84        }
85        if self.behavior.do_not_show_optional_dependencies && is_optional {
86            return false;
87        }
88
89        let is_ignored = self
90            .dependencies
91            .ignored
92            .as_ref()
93            .unwrap_or(&vec![])
94            .contains(&dependency.name);
95
96        if self.behavior.do_not_show_ignored_dependencies {
97            if is_ignored {
98                return false;
99            }
100            if self.dependencies.ignore_dev_dependencies && is_dev {
101                return false;
102            }
103            if self.dependencies.ignore_optional_dependencies && is_optional {
104                return false;
105            }
106        }
107
108        true
109    }
110
111    /// Validates a specific [`RetrievedDependency`].
112    /// Note that it will set the dependency's `validated` property to `true`.
113    /// While checking it's validaty against what's been declared in the `.licrc` file it can also modify `is_ignored` and `is_valid` properties.
114    #[instrument(skip(self))]
115    pub fn validate(&self, dependency: &mut RetrievedDependency) {
116        dependency.validated = true;
117        // is it ignored?
118        if self.is_ignored(dependency) {
119            tracing::debug!(dependency = ?dependency, "Dependency has been ignored");
120            return;
121        }
122        // is it compliant with the policy?
123        if !dependency.is_valid {
124            tracing::debug!(dependency = ?dependency, "Dependency is invalid");
125            return;
126        }
127
128        dependency.licenses.clone().map_or_else(
129            || {
130                tracing::error!("Licenses are None!! At this point, this shouldn't happen. Check out the dependency validation logic");
131            },
132            |licenses| {
133                for lic in &licenses {
134                    if let Some(accepted) = self.licenses.accepted.as_ref() {
135                        if !accepted.contains(lic) {
136                            make_invalid(dependency, lic);
137                        }
138                    } else if let Some(unaccepted) = self.licenses.unaccepted.as_ref() {
139                        if unaccepted.contains(lic) {
140                            make_invalid(dependency, lic);
141                        }
142                    }
143                }
144            },
145        );
146    }
147}
148
149/// Marks a dependency as invalid and if it doesnt' have any error it adds one saying `Not compliant`.
150#[instrument]
151fn make_invalid(dependency: &mut RetrievedDependency, license: &str) {
152    tracing::debug!(
153        ?dependency,
154        license,
155        "No compliant dependency marked as invalid"
156    );
157    dependency.is_valid = false;
158    // preserve pre-existing error
159    if dependency.error.is_none() {
160        dependency.error = Some("Not compliant".to_string());
161    }
162}
163
164/// Holds information about the accepted or unaccepted licenses.
165#[derive(Serialize, Deserialize, Debug, Default, Clone)]
166pub struct LicRcLicenses {
167    /// List of accepted licenses (see <https://spdx.org/licenses/>)
168    pub accepted: Option<Vec<String>>,
169    /// List of unaccepted licenses (see <https://spdx.org/licenses/>)
170    pub unaccepted: Option<Vec<String>>,
171}
172
173/// Holds information about dependency specifics.
174#[derive(Serialize, Deserialize, Debug, Default, Clone)]
175pub struct LicRcDependencies {
176    /// List of ignored dependencies.
177    /// These dependencies won't be validated.
178    /// You must use the name of the dependency here.
179    pub ignored: Option<Vec<String>>,
180    /// If set to true, dev dependencies will be ignored.
181    #[serde(default)]
182    pub ignore_dev_dependencies: bool,
183    /// If set to true, optional dependencies will be ignored.
184    #[serde(default)]
185    pub ignore_optional_dependencies: bool,
186}
187
188/// Holds information about the behavior of the validation process.
189/// **This only applies for the [GITHUB API integrated project](https://github.com/marketplace/licensebat)**.
190#[allow(clippy::struct_excessive_bools)]
191#[derive(Serialize, Deserialize, Debug, Default, Clone)]
192pub struct LicRcBehavior {
193    /// If set to false Licensebat will validate the dependencies no matter what file has been modified.
194    /// If set to true, validation will only happen when one of the dependency files or the .licrc files has been modified in the commit.
195    pub run_only_on_dependency_modification: Option<bool>,
196    /// If set to true, Licensebat will execute the check but it won't block the PR.
197    #[serde(default)]
198    pub do_not_block_pr: bool,
199    /// This will define the size of the buffer used to retrieve the dependencies.
200    /// It's set to 100 by default.
201    /// If you have a lot of dependencies, you might want to increase this value, but be careful, if the size is too big, the API might return an error.
202    pub retriever_buffer_size: Option<usize>,
203    /// If set to true, Licensebat will not show the ignored dependencies in the final report.
204    #[serde(default)]
205    pub do_not_show_ignored_dependencies: bool,
206    /// If set to true, Licensebat will not show the dev dependencies in the final report.
207    #[serde(default)]
208    pub do_not_show_dev_dependencies: bool,
209    /// If set to true, Licensebat will not show the optional dependencies in the final report.
210    #[serde(default)]
211    pub do_not_show_optional_dependencies: bool,
212}