Skip to main content

alpm_lint/lint_rules/source_info/
long_values_aurweb.rs

1//! Ensures that values of [SRCINFO] keywords do not exceed their byte limits.
2//!
3//! [SRCINFO]: https://alpm.archlinux.page/specifications/SRCINFO.5.html
4
5use std::collections::BTreeMap;
6
7use alpm_lint_config::LintRuleConfiguration;
8use alpm_srcinfo::source_info::v1::package::Override;
9use documented::Documented;
10
11use crate::{
12    internal_prelude::*,
13    issue::SourceInfoIssue,
14    lint_rules::source_info::source_info_from_resource,
15};
16
17/// Grouping together field properties for validation and error messages.
18///
19/// `.0` - name-or-keyword
20/// `.1` - value
21/// `.2` - byte-limit
22type Field = (&'static str, String, usize);
23
24/// Byte limits for values of [SRCINFO] keywords.
25///
26/// [SRCINFO]: https://alpm.archlinux.page/specifications/SRCINFO.5.html
27pub mod limits {
28    /// Prints the docs.rs URL to this module.
29    ///
30    /// This is meant to inform users of the byte limits for values of SRCINFO keywords in
31    /// `<LongValuesAurweb as LintRule>::extra_links()`.
32    pub(super) fn docsrs_page() -> String {
33        const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME");
34        let module_path = const { module_path!() }.replace("::", "/");
35
36        format!("https://docs.rs/{CARGO_PKG_NAME}/latest/{module_path}/index.html")
37    }
38
39    // Writing byte limits in docs so that we don't have to open up each const in our browser just
40    // to see their value.
41
42    /// Limit for `pkgbase` (`255` bytes).
43    pub const PKGBASE: usize = 255;
44    /// Limit for `pkgname` (`255` bytes).
45    pub const PKGNAME: usize = 255;
46    /// Limit for `pkgdesc` (`255` bytes).
47    pub const PKGDESC: usize = 255;
48    /// Limit for `url` (`8000` bytes).
49    pub const URL: usize = 8000;
50}
51
52/// # What it does
53///
54/// Ensures that values of [SRCINFO] keywords do not exceed their byte [`limits`].
55///
56/// [SRCINFO]: https://alpm.archlinux.page/specifications/SRCINFO.5.html
57#[derive(Clone, Debug, Documented)]
58pub struct LongValuesAurweb;
59
60impl LongValuesAurweb {
61    /// Creates a new, boxed instance of [`LongValuesAurweb`].
62    pub fn new_boxed(_config: &LintRuleConfiguration) -> Box<dyn LintRule> {
63        Box::new(Self {})
64    }
65}
66
67impl LintRule for LongValuesAurweb {
68    fn name(&self) -> &'static str {
69        "long_values_aurweb"
70    }
71
72    fn scope(&self) -> LintScope {
73        LintScope::SourceInfo
74    }
75
76    fn level(&self) -> Level {
77        Level::Warn
78    }
79    fn documentation(&self) -> String {
80        Self::DOCS.into()
81    }
82
83    fn help_text(&self) -> String {
84        "Value for SRCINFO keywords exceed the byte length restrictions enforced by the aurweb application."
85            .to_string()
86    }
87
88    fn run(&self, resources: &Resources, issues: &mut Vec<LintIssue>) -> Result<(), Error> {
89        // Extract the SourceInfo from the given resources.
90        let source_info = source_info_from_resource(resources, self.scoped_name())?;
91
92        let fields: Vec<Field> = {
93            let base = &source_info.base;
94            let mut fields = vec![("pkgbase", base.name.to_string(), limits::PKGBASE)];
95
96            if let Some(desc) = base.description.as_ref() {
97                fields.push(("pkgdesc", desc.to_string(), limits::PKGDESC));
98            }
99
100            if let Some(url) = base.url.as_ref() {
101                fields.push(("url", url.to_string(), limits::URL));
102            }
103
104            fields
105        };
106
107        for (keyword, value, limit) in fields {
108            if value.len() > limit {
109                issues.push(LintIssue::from_rule(
110                    self,
111                    SourceInfoIssue::BaseField {
112                        field_name: keyword.into(),
113                        value,
114                        context: format!("`{keyword}` value exceeded {limit} bytes"),
115                        architecture: None,
116                    }
117                    .into(),
118                ));
119            }
120        }
121
122        for package in &source_info.packages {
123            let fields: Vec<Field> = {
124                let mut fields = vec![("pkgname", package.name.to_string(), limits::PKGNAME)];
125
126                if let Override::Yes { ref value } = package.description {
127                    fields.push(("pkgdesc", value.to_string(), limits::PKGDESC));
128                }
129
130                if let Override::Yes { ref value } = package.url {
131                    fields.push(("url", value.to_string(), limits::URL));
132                }
133
134                fields
135            };
136
137            for (keyword, value, limit) in fields {
138                if value.len() > limit {
139                    issues.push(LintIssue::from_rule(
140                        self,
141                        SourceInfoIssue::PackageField {
142                            field_name: keyword.into(),
143                            package_name: package.name.to_string(),
144                            value,
145                            context: format!("`{keyword}` value exceeded {limit} bytes"),
146                            architecture: None,
147                        }
148                        .into(),
149                    ));
150                }
151            }
152        }
153
154        Ok(())
155    }
156
157    fn extra_links(&self) -> Option<BTreeMap<String, String>> {
158        let mut links = BTreeMap::new();
159
160        links.insert("Byte limits".to_string(), limits::docsrs_page());
161        links.insert(
162            "SRCINFO".to_string(),
163            "https://alpm.archlinux.page/specifications/SRCINFO.5.html".to_string(),
164        );
165
166        Some(links)
167    }
168}