use crate::{Explanation, Location};
#[non_exhaustive]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum SensitiveCandidateKind {
RecoveryLikeCode,
}
#[non_exhaustive]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum CandidateEvidence {
Structural,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SensitiveCandidate {
kind: SensitiveCandidateKind,
location: Location,
evidence: CandidateEvidence,
}
impl SensitiveCandidate {
pub(crate) const fn new(
kind: SensitiveCandidateKind,
location: Location,
evidence: CandidateEvidence,
) -> Self {
Self {
kind,
location,
evidence,
}
}
#[must_use]
pub const fn kind(&self) -> SensitiveCandidateKind {
self.kind
}
#[must_use]
pub const fn location(&self) -> &Location {
&self.location
}
#[must_use]
pub const fn evidence(&self) -> CandidateEvidence {
self.evidence
}
#[must_use]
pub const fn explanation(&self) -> Explanation {
Explanation::ambiguous(self.evidence)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn exposes_candidate_metadata_without_finding_semantics() {
let candidate = SensitiveCandidate::new(
SensitiveCandidateKind::RecoveryLikeCode,
Location::from_span(4, 23),
CandidateEvidence::Structural,
);
assert_eq!(candidate.kind(), SensitiveCandidateKind::RecoveryLikeCode);
assert_eq!(candidate.location().start(), 4);
assert_eq!(candidate.location().end(), 23);
assert_eq!(candidate.evidence(), CandidateEvidence::Structural);
assert_eq!(
candidate.explanation(),
Explanation::Ambiguous(CandidateEvidence::Structural)
);
assert_eq!(
candidate.explanation().candidate_evidence(),
Some(CandidateEvidence::Structural)
);
assert!(candidate.explanation().is_ambiguous());
assert!(!candidate.explanation().is_classified());
}
#[cfg(feature = "serde")]
#[test]
fn serde_round_trip_preserves_candidate_metadata() {
let candidate = SensitiveCandidate::new(
SensitiveCandidateKind::RecoveryLikeCode,
Location::from_span(2, 21),
CandidateEvidence::Structural,
);
let json = serde_json::to_string(&candidate).expect("serialize candidate");
let decoded: SensitiveCandidate =
serde_json::from_str(&json).expect("deserialize candidate");
assert_eq!(decoded, candidate);
assert!(!json.contains("matched_value"));
assert!(!json.contains("secret"));
}
}