Skip to main content

pedant_types/
finding.rs

1use std::sync::Arc;
2
3use serde::de::Error;
4use serde::{Deserialize, Deserializer, Serialize};
5
6use crate::{Capability, ExecutionContext, FindingOrigin, Language};
7
8/// File, line, and column of a capability finding.
9#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
10pub struct SourceLocation {
11    /// Absolute path; `Arc` because many findings share the same file.
12    pub file: Arc<str>,
13    /// 1-based line number.
14    pub line: usize,
15    /// 1-based column number.
16    pub column: usize,
17}
18
19/// Evidence that a specific capability is exercised at a source location.
20#[derive(Serialize, Clone, Debug, PartialEq, Eq, Hash)]
21pub struct CapabilityFinding {
22    /// Which capability this finding represents.
23    pub capability: Capability,
24    /// Source position of the triggering expression or import.
25    pub location: SourceLocation,
26    /// Snippet of the triggering code (e.g., the import path or literal).
27    pub evidence: Arc<str>,
28    /// How the capability was detected (import, string literal, attribute, etc.).
29    #[serde(default, skip_serializing_if = "Option::is_none")]
30    pub origin: Option<FindingOrigin>,
31    /// Source language of the analyzed file, if known.
32    #[serde(default, skip_serializing_if = "Option::is_none")]
33    pub language: Option<Language>,
34    /// When during the lifecycle this code executes.
35    #[serde(default, skip_serializing_if = "Option::is_none")]
36    pub execution_context: Option<ExecutionContext>,
37    /// Reachability from a public entry point.
38    ///
39    /// `None` when DataFlow analysis is unavailable. `Some(true)` when
40    /// reachable, `Some(false)` when dead code.
41    #[serde(default, skip_serializing_if = "Option::is_none")]
42    pub reachable: Option<bool>,
43}
44
45impl<'de> Deserialize<'de> for CapabilityFinding {
46    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
47        #[derive(Deserialize)]
48        struct Raw {
49            capability: Capability,
50            location: SourceLocation,
51            evidence: Arc<str>,
52            #[serde(default)]
53            origin: Option<FindingOrigin>,
54            #[serde(default)]
55            language: Option<Language>,
56            #[serde(default)]
57            execution_context: Option<ExecutionContext>,
58            #[serde(default)]
59            build_script: Option<bool>,
60            #[serde(default)]
61            reachable: Option<bool>,
62        }
63
64        let raw = Raw::deserialize(deserializer)?;
65        let execution_context = match (raw.execution_context, raw.build_script) {
66            (Some(ExecutionContext::BuildHook), Some(true)) => Some(ExecutionContext::BuildHook),
67            (Some(ExecutionContext::BuildHook), Some(false)) => {
68                return Err(D::Error::custom(
69                    "execution_context=build_hook contradicts legacy build_script=false",
70                ));
71            }
72            (Some(ctx), Some(true)) => {
73                return Err(D::Error::custom(format!(
74                    "execution_context={ctx:?} contradicts legacy build_script=true"
75                )));
76            }
77            (Some(ctx), Some(false) | None) => Some(ctx),
78            (None, Some(true)) => Some(ExecutionContext::BuildHook),
79            (None, Some(false) | None) => None,
80        };
81        Ok(Self {
82            capability: raw.capability,
83            location: raw.location,
84            evidence: raw.evidence,
85            origin: raw.origin,
86            language: raw.language,
87            execution_context,
88            reachable: raw.reachable,
89        })
90    }
91}
92
93impl CapabilityFinding {
94    /// Returns `true` when this finding comes from a build-time execution context
95    /// (e.g., `build.rs` or equivalent).
96    pub fn is_build_hook(&self) -> bool {
97        self.execution_context == Some(ExecutionContext::BuildHook)
98    }
99}