1use std::sync::Arc;
2
3use serde::de::Error;
4use serde::{Deserialize, Deserializer, Serialize};
5
6use crate::{Capability, ExecutionContext, FindingOrigin, Language};
7
8#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
10pub struct SourceLocation {
11 pub file: Arc<str>,
13 pub line: usize,
15 pub column: usize,
17}
18
19#[derive(Serialize, Clone, Debug, PartialEq, Eq, Hash)]
21pub struct CapabilityFinding {
22 pub capability: Capability,
24 pub location: SourceLocation,
26 pub evidence: Arc<str>,
28 #[serde(default, skip_serializing_if = "Option::is_none")]
30 pub origin: Option<FindingOrigin>,
31 #[serde(default, skip_serializing_if = "Option::is_none")]
33 pub language: Option<Language>,
34 #[serde(default, skip_serializing_if = "Option::is_none")]
36 pub execution_context: Option<ExecutionContext>,
37 #[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 pub fn is_build_hook(&self) -> bool {
97 self.execution_context == Some(ExecutionContext::BuildHook)
98 }
99}