cargo_geiger_serde/
report.rs

1use crate::PackageId;
2use serde::{Deserialize, Serialize};
3use std::fmt::{Debug, Formatter};
4use std::{
5    collections::{HashMap, HashSet},
6    ops::{Add, AddAssign},
7    path::PathBuf,
8};
9
10fn debug_fmt_set(
11    f: &mut Formatter<'_>,
12    set: &HashSet<impl Debug>,
13) -> std::fmt::Result {
14    let mut strings: Vec<String> =
15        set.iter().map(|v| format!("{v:?}")).collect();
16    strings.sort();
17    write!(f, "{{ {} }}, ", strings.join(", "))
18}
19
20fn debug_fmt_map(
21    f: &mut Formatter<'_>,
22    set: &HashMap<impl Debug, impl Debug>,
23) -> std::fmt::Result {
24    let mut strings: Vec<String> =
25        set.iter().map(|(k, v)| format!("{k:?}: {v:?}")).collect();
26    strings.sort();
27    write!(f, "{{ {} }}, ", strings.join(", "))
28}
29
30/// Package dependency information
31#[derive(Clone, Deserialize, Eq, PartialEq, Serialize)]
32pub struct PackageInfo {
33    pub id: PackageId,
34    #[serde(serialize_with = "set_serde::serialize")]
35    pub dependencies: HashSet<PackageId>,
36    #[serde(serialize_with = "set_serde::serialize")]
37    pub dev_dependencies: HashSet<PackageId>,
38    #[serde(serialize_with = "set_serde::serialize")]
39    pub build_dependencies: HashSet<PackageId>,
40}
41impl Debug for PackageInfo {
42    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
43        write!(f, "PackageInfo {{ id: {:?}", self.id)?;
44        write!(f, ", dependencies: ")?;
45        debug_fmt_set(f, &self.dependencies)?;
46        write!(f, ", dev_dependencies: ")?;
47        debug_fmt_set(f, &self.dev_dependencies)?;
48        write!(f, ", build_dependencies: ")?;
49        debug_fmt_set(f, &self.build_dependencies)?;
50        write!(f, " }}")
51    }
52}
53
54impl PackageInfo {
55    pub fn new(id: PackageId) -> Self {
56        PackageInfo {
57            id,
58            dependencies: Default::default(),
59            dev_dependencies: Default::default(),
60            build_dependencies: Default::default(),
61        }
62    }
63
64    pub fn add_dependency(&mut self, dep: PackageId, kind: DependencyKind) {
65        match kind {
66            DependencyKind::Normal => self.dependencies.insert(dep),
67            DependencyKind::Development => self.dev_dependencies.insert(dep),
68            DependencyKind::Build => self.build_dependencies.insert(dep),
69        };
70    }
71}
72
73/// Entry of the report generated from scanning for packages that forbid the use of `unsafe`
74#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
75pub struct QuickReportEntry {
76    pub package: PackageInfo,
77    /// Whether this package forbids the use of `unsafe`
78    pub forbids_unsafe: bool,
79}
80
81/// Report generated from scanning for packages that forbid the use of `unsafe`
82#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
83pub struct QuickSafetyReport {
84    /// Packages that were scanned successfully
85    #[serde(with = "entry_serde")]
86    pub packages: HashMap<PackageId, QuickReportEntry>,
87    /// Packages that were not scanned successfully
88    #[serde(serialize_with = "set_serde::serialize")]
89    pub packages_without_metrics: HashSet<PackageId>,
90}
91
92/// Entry of the report generated from scanning for the use of `unsafe`
93#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
94pub struct ReportEntry {
95    pub package: PackageInfo,
96    /// Unsafety scan results
97    pub unsafety: UnsafeInfo,
98}
99
100/// Report generated from scanning for the use of `unsafe`
101#[derive(Clone, Default, Deserialize, Eq, PartialEq, Serialize)]
102pub struct SafetyReport {
103    #[serde(with = "entry_serde")]
104    pub packages: HashMap<PackageId, ReportEntry>,
105    #[serde(serialize_with = "set_serde::serialize")]
106    pub packages_without_metrics: HashSet<PackageId>,
107    #[serde(serialize_with = "set_serde::serialize")]
108    pub used_but_not_scanned_files: HashSet<PathBuf>,
109}
110impl Debug for SafetyReport {
111    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
112        write!(f, "SafetyReport {{ packages: ")?;
113        debug_fmt_map(f, &self.packages)?;
114        write!(f, ", packages_without_metrics: ")?;
115        debug_fmt_set(f, &self.packages_without_metrics)?;
116        write!(f, ", used_but_not_scanned_files: ")?;
117        debug_fmt_set(f, &self.used_but_not_scanned_files)?;
118        write!(f, " }}")
119    }
120}
121
122/// Unsafety usage in a package
123#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
124pub struct UnsafeInfo {
125    /// Unsafe usage statistics for code used by the project
126    pub used: CounterBlock,
127    /// Unsafe usage statistics for code not used by the project
128    pub unused: CounterBlock,
129    /// Whether this package forbids the use of `unsafe`
130    pub forbids_unsafe: bool,
131}
132
133/// Kind of dependency for a package
134#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
135pub enum DependencyKind {
136    /// Dependency in the `[dependencies]` section of `Cargo.toml`
137    Normal,
138    /// Dependency in the `[dev-dependencies]` section of `Cargo.toml`
139    Development,
140    /// Dependency in the `[build-dependencies]` section of `Cargo.toml`
141    Build,
142}
143
144/// Statistics about the use of `unsafe`
145#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
146pub struct Count {
147    /// Number of safe items
148    pub safe: u64,
149    /// Number of unsafe items
150    pub unsafe_: u64,
151}
152
153impl Count {
154    /// Increments the safe or unsafe counter by 1
155    pub fn count(&mut self, is_unsafe: bool) {
156        if is_unsafe {
157            self.unsafe_ += 1;
158        } else {
159            self.safe += 1;
160        }
161    }
162}
163
164impl Add for Count {
165    type Output = Count;
166
167    fn add(self, other: Count) -> Count {
168        Count {
169            safe: self.safe + other.safe,
170            unsafe_: self.unsafe_ + other.unsafe_,
171        }
172    }
173}
174
175impl AddAssign for Count {
176    fn add_assign(&mut self, rhs: Count) {
177        *self = self.clone() + rhs;
178    }
179}
180
181/// Unsafe usage metrics collection.
182#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
183pub struct CounterBlock {
184    pub functions: Count,
185    pub exprs: Count,
186    pub item_impls: Count,
187    pub item_traits: Count,
188    pub methods: Count,
189}
190
191impl CounterBlock {
192    pub fn has_unsafe(&self) -> bool {
193        self.functions.unsafe_ > 0
194            || self.exprs.unsafe_ > 0
195            || self.item_impls.unsafe_ > 0
196            || self.item_traits.unsafe_ > 0
197            || self.methods.unsafe_ > 0
198    }
199}
200
201impl Add for CounterBlock {
202    type Output = CounterBlock;
203
204    fn add(self, other: CounterBlock) -> CounterBlock {
205        CounterBlock {
206            functions: self.functions + other.functions,
207            exprs: self.exprs + other.exprs,
208            item_impls: self.item_impls + other.item_impls,
209            item_traits: self.item_traits + other.item_traits,
210            methods: self.methods + other.methods,
211        }
212    }
213}
214
215impl AddAssign for CounterBlock {
216    fn add_assign(&mut self, rhs: Self) {
217        *self = self.clone() + rhs;
218    }
219}
220
221trait Entry {
222    fn package_id(&self) -> &PackageId;
223}
224
225impl Entry for ReportEntry {
226    fn package_id(&self) -> &PackageId {
227        &self.package.id
228    }
229}
230
231impl Entry for QuickReportEntry {
232    fn package_id(&self) -> &PackageId {
233        &self.package.id
234    }
235}
236
237mod entry_serde {
238    use crate::PackageId;
239    use serde::{
240        ser::SerializeSeq, Deserialize, Deserializer, Serialize, Serializer,
241    };
242    use std::{collections::HashMap, fmt, marker::PhantomData};
243
244    pub(super) fn serialize<T, S>(
245        map: &HashMap<PackageId, T>,
246        serializer: S,
247    ) -> Result<S::Ok, S::Error>
248    where
249        T: Serialize + super::Entry,
250        S: Serializer,
251    {
252        let mut values = map.values().collect::<Vec<_>>();
253        values.sort_by(|a, b| a.package_id().cmp(b.package_id()));
254        let mut seq = serializer.serialize_seq(Some(values.len()))?;
255        for value in values {
256            seq.serialize_element(value)?;
257        }
258        seq.end()
259    }
260
261    pub(super) fn deserialize<'de, T, D>(
262        deserializer: D,
263    ) -> Result<HashMap<PackageId, T>, D::Error>
264    where
265        T: Deserialize<'de> + super::Entry,
266        D: Deserializer<'de>,
267    {
268        struct Visitor<U>(PhantomData<fn() -> U>);
269
270        impl<'d, U> serde::de::Visitor<'d> for Visitor<U>
271        where
272            U: Deserialize<'d> + super::Entry,
273        {
274            type Value = HashMap<PackageId, U>;
275
276            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277                f.write_str("a sequence")
278            }
279
280            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
281            where
282                A: serde::de::SeqAccess<'d>,
283            {
284                let mut map = HashMap::new();
285                while let Some(item) = seq.next_element::<U>()? {
286                    map.insert(item.package_id().clone(), item);
287                }
288                Ok(map)
289            }
290        }
291
292        deserializer.deserialize_seq(Visitor(PhantomData))
293    }
294}
295
296mod set_serde {
297    use serde::{ser::SerializeSeq, Serialize, Serializer};
298    use std::collections::HashSet;
299
300    pub(super) fn serialize<T, S>(
301        set: &HashSet<T>,
302        serializer: S,
303    ) -> Result<S::Ok, S::Error>
304    where
305        T: Serialize + Ord,
306        S: Serializer,
307    {
308        let mut values = set.iter().collect::<Vec<_>>();
309        values.sort();
310        let mut seq = serializer.serialize_seq(Some(values.len()))?;
311        for value in values {
312            seq.serialize_element(value)?;
313        }
314        seq.end()
315    }
316}