use std::path::PathBuf;
use antigen_macros::{antigen_tolerance, presents};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[presents(VecCardinalityMasqueradingAsSet)]
#[antigen_tolerance(
VecCardinalityMasqueradingAsSet,
rationale = "Accepted: `category` is a Vec modeling a set (each AntigenCategory meaningful at \
most once), so it structurally presents the masquerade shape. The duplicate-injection \
risk is DEFENDED UPSTREAM at the declaration boundary — AntigenArgs::validate() in \
antigen-macros rejects duplicate category variants at parse-time (fixed 30e10e6, pinned \
by antigen_parser_duplicate_category_in_array_is_rejected). It cannot be marked \
#[immune] here because the defense + witness live in the proc-macro crate, which the \
scanned struct's crate cannot reference (dependency-cycle + proc-macro-self-application \
constraints — see MarkerStructDeadCodeInBinary). So this scanned representation tolerates \
the shape; the real guard is one layer up at macro-validate.",
until = "v0.3"
)]
pub struct AntigenDeclaration {
pub name: String,
pub type_name: String,
pub file: PathBuf,
pub line: usize,
pub family: Option<String>,
pub summary: Option<String>,
pub fingerprint: Option<String>,
#[serde(default)]
pub canonical_path: Option<String>,
#[serde(default)]
pub category: Vec<crate::category::AntigenCategory>,
#[serde(default)]
pub provenance: Option<String>,
#[serde(default)]
pub presentation: Option<String>,
}
impl AntigenDeclaration {
#[must_use]
pub fn resolved_provenance(&self) -> crate::finding::Provenance {
self.provenance
.as_deref()
.and_then(crate::finding::Provenance::from_variant_str)
.unwrap_or(crate::finding::Provenance::DEFAULT)
}
#[must_use]
pub fn resolved_presentation(&self) -> crate::finding::Presentation {
self.presentation
.as_deref()
.and_then(crate::finding::Presentation::from_variant_str)
.unwrap_or(crate::finding::Presentation::DEFAULT)
}
#[must_use]
pub fn provenance_is_explicit(&self) -> bool {
self.provenance
.as_deref()
.is_some_and(|s| crate::finding::Provenance::from_variant_str(s).is_some())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ItemTarget {
Struct(String),
Enum(String),
Trait(String),
Fn(String),
TypeAlias(String),
Impl {
trait_path: Option<String>,
target_type: String,
},
ImplFn {
trait_path: Option<String>,
target_type: String,
fn_name: String,
},
TraitFn {
trait_name: String,
fn_name: String,
},
EnumVariant {
enum_name: String,
variant_name: String,
},
ImplConst {
trait_path: Option<String>,
target_type: String,
const_name: String,
},
Const(String),
Static(String),
Union(String),
Unknown {
line: usize,
},
}
impl ItemTarget {
#[must_use]
pub fn label(&self) -> String {
match self {
Self::Struct(n)
| Self::Enum(n)
| Self::Trait(n)
| Self::Fn(n)
| Self::TypeAlias(n)
| Self::Const(n)
| Self::Static(n)
| Self::Union(n) => n.clone(),
Self::Impl {
trait_path: Some(t),
target_type,
} => format!("impl {t} for {target_type}"),
Self::Impl {
trait_path: None,
target_type,
} => format!("impl {target_type}"),
Self::ImplFn {
trait_path: Some(t),
target_type,
fn_name,
} => format!("<{target_type} as {t}>::{fn_name}"),
Self::ImplFn {
trait_path: None,
target_type,
fn_name,
} => format!("{target_type}::{fn_name}"),
Self::TraitFn {
trait_name,
fn_name,
} => format!("trait {trait_name}::{fn_name}"),
Self::EnumVariant {
enum_name,
variant_name,
} => format!("{enum_name}::{variant_name}"),
Self::ImplConst {
trait_path: Some(t),
target_type,
const_name,
} => format!("<{target_type} as {t}>::{const_name}"),
Self::ImplConst {
trait_path: None,
target_type,
const_name,
} => format!("{target_type}::{const_name}"),
Self::Unknown { line } => format!("<unknown at line {line}>"),
}
}
#[must_use]
#[allow(
clippy::match_same_arms,
reason = "the explicit `Unknown` arm is the load-bearing invariant — \
Unknown items must NEVER match anything, including each other. \
Keeping it explicit (even though it duplicates the `_` wildcard's \
body) makes the invariant readable and refactor-safe."
)]
pub fn addresses(&self, other: &Self) -> bool {
match (self, other) {
(Self::Unknown { .. }, _) | (_, Self::Unknown { .. }) => false,
(Self::Struct(a), Self::Struct(b))
| (Self::Enum(a), Self::Enum(b))
| (Self::Trait(a), Self::Trait(b))
| (Self::Fn(a), Self::Fn(b))
| (Self::TypeAlias(a), Self::TypeAlias(b))
| (Self::Const(a), Self::Const(b))
| (Self::Static(a), Self::Static(b))
| (Self::Union(a), Self::Union(b)) => a == b,
(
Self::Impl {
target_type: t1, ..
},
Self::Impl {
target_type: t2, ..
},
) => normalize_type_name(t1) == normalize_type_name(t2),
(
Self::ImplFn {
target_type: t1,
fn_name: f1,
..
},
Self::ImplFn {
target_type: t2,
fn_name: f2,
..
},
) => normalize_type_name(t1) == normalize_type_name(t2) && f1 == f2,
(
Self::TraitFn {
trait_name,
fn_name: tf,
},
Self::ImplFn {
trait_path: Some(t),
fn_name: imf,
..
},
)
| (
Self::ImplFn {
trait_path: Some(t),
fn_name: imf,
..
},
Self::TraitFn {
trait_name,
fn_name: tf,
},
) => trait_name == t && tf == imf,
(
Self::TraitFn {
trait_name: t1,
fn_name: f1,
},
Self::TraitFn {
trait_name: t2,
fn_name: f2,
},
) => t1 == t2 && f1 == f2,
(
Self::EnumVariant {
enum_name: e1,
variant_name: v1,
},
Self::EnumVariant {
enum_name: e2,
variant_name: v2,
},
) => e1 == e2 && v1 == v2,
(
Self::ImplConst {
target_type: t1,
const_name: c1,
..
},
Self::ImplConst {
target_type: t2,
const_name: c2,
..
},
) => normalize_type_name(t1) == normalize_type_name(t2) && c1 == c2,
_ => false,
}
}
}
fn normalize_type_name(rendered: &str) -> String {
let s = rendered.trim();
s.find('<')
.map_or_else(|| s.to_string(), |idx| s[..idx].trim().to_string())
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum MatchKind {
#[default]
ExplicitMarker,
FingerprintMatch,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct ProvenanceEntry {
pub antigen_type: String,
pub canonical_path: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Presentation {
pub antigen_type: String,
pub file: PathBuf,
pub line: usize,
pub item_kind: String,
pub item_target: ItemTarget,
#[serde(default)]
pub match_kind: MatchKind,
#[serde(default)]
pub canonical_path: Option<String>,
#[serde(default)]
pub inherited_from: Option<Vec<ProvenanceEntry>>,
#[serde(default)]
pub structural_fingerprint: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub requires_predicate: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub proof: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Immunity {
pub antigen_type: String,
pub witness: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub requires_predicate: Option<String>,
pub file: PathBuf,
pub line: usize,
pub item_kind: String,
pub item_target: ItemTarget,
#[serde(default)]
pub canonical_path: Option<String>,
#[serde(default)]
pub structural_fingerprint: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Defense {
pub antigen_type: String,
pub file: PathBuf,
pub line: usize,
pub item_kind: String,
pub item_target: ItemTarget,
#[serde(default)]
pub canonical_path: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GeneratesDeclaration {
pub antigen_type: String,
pub rationale: String,
pub macro_name: String,
pub file: PathBuf,
pub line: usize,
#[serde(default)]
pub canonical_path: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MarkedUnknown {
pub marker: String,
pub magnitude: String,
pub existence_certainty: String,
pub trigger: String,
pub file: PathBuf,
pub line: usize,
}
impl MarkedUnknown {
#[must_use]
pub fn to_finding(&self, timestamp: u64) -> crate::finding::Finding {
use crate::finding::{
cluster_key_of, ExistenceCertainty, Finding, FindingBody, Magnitude, OriginStage,
Presentation, Provenance, Severity, FINDING_SCHEMA_VERSION,
};
let magnitude = Magnitude::from_variant_str(&self.magnitude).unwrap_or(Magnitude::Aura);
let existence_certainty = ExistenceCertainty::from_variant_str(&self.existence_certainty)
.unwrap_or(ExistenceCertainty::Unsure);
let severity = match (existence_certainty, magnitude) {
(ExistenceCertainty::Sure, _) | (_, Magnitude::Dread) => Severity::High,
(_, Magnitude::Aura) => Severity::Medium,
(_, Magnitude::Smell) => Severity::Low,
};
let cluster_key = cluster_key_of("", &self.marker);
Finding {
schema_version: FINDING_SCHEMA_VERSION,
file: self.file.to_string_lossy().into_owned(),
line: self.line,
structural_digest: String::new(),
cluster_key,
severity,
source: format!("scan:marked-unknown:{}", self.marker),
class_provenance: Provenance::Encountered,
presentation: Presentation::Active,
timestamp,
origin_stage: OriginStage::Scan,
body: FindingBody::MarkedUnknown {
magnitude,
existence_certainty,
trigger: self.trigger.clone(),
},
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Toleration {
pub antigen_type: String,
pub rationale: String,
pub until: Option<String>,
pub see: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub requires_predicate: Option<String>,
pub file: PathBuf,
pub line: usize,
pub item_kind: String,
pub item_target: ItemTarget,
#[serde(default)]
pub canonical_path: Option<String>,
#[serde(default)]
pub structural_fingerprint: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum DeferredDefenseKind {
Anergy,
Immunosuppress,
Poxparty,
Orient,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeferredDefense {
pub kind: DeferredDefenseKind,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub antigen_type: Option<String>,
pub text: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub until: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expected_co_stimulation: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub signed_by: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub see: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub since: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub duration_cap: Option<u64>,
pub file: PathBuf,
pub line: usize,
pub item_kind: String,
pub item_target: ItemTarget,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ConvergentEvidenceKind {
Diagnostic,
Clonal,
Igg,
Crossreactive,
Polyclonal,
Monoclonal,
Adcc,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConvergentEvidence {
pub kind: ConvergentEvidenceKind,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub modality_classes: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub min_independent: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub witness: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub iterations: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub seed_kind: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub historical_span: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub min_reattestations: Option<u64>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub witnesses: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub fingerprints: Vec<String>,
pub file: PathBuf,
pub line: usize,
pub item_kind: String,
pub item_target: ItemTarget,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum RecurrentKind {
Itch,
RecurrenceAnchor,
Crystallize,
Chronic,
Saturate,
Strand,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecurrentDeclaration {
pub kind: RecurrentKind,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub antigen_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub instances: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub since: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rationale: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub from_itches: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub anchored_by: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub managed_by: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub contributing_to: Option<String>,
pub file: PathBuf,
pub line: usize,
pub item_kind: String,
pub item_target: ItemTarget,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum MucosalKindTag {
Mucosal,
MucosalDelegate,
MucosalTolerant,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MucosalDeclaration {
pub tag: MucosalKindTag,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub boundary_kind: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rationale: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub handled_by: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub accepts: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reviewed_by: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub until: Option<String>,
pub file: PathBuf,
pub line: usize,
pub item_kind: String,
pub item_target: ItemTarget,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum PrescriptiveKind {
Panel,
Rx,
Refer,
Biopsy,
Ddx,
Triage,
Culture,
Quarantine,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum WorkShape {
RoleWorkflow,
Elimination,
Ordering,
FrameOnly,
}
impl PrescriptiveKind {
#[must_use]
pub const fn shape(self) -> WorkShape {
match self {
Self::Panel | Self::Rx | Self::Refer | Self::Biopsy => WorkShape::RoleWorkflow,
Self::Ddx => WorkShape::Elimination,
Self::Triage => WorkShape::Ordering,
Self::Culture | Self::Quarantine => WorkShape::FrameOnly,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PrescriptiveDeclaration {
pub kind: PrescriptiveKind,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub items: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub filled_by: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub reviewed_by: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ordered_by: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub frame: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub need_text: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
pub file: PathBuf,
pub line: usize,
pub item_kind: String,
pub item_target: ItemTarget,
#[serde(default)]
pub structural_fingerprint: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ParseFailure {
pub file: PathBuf,
pub error: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LineageEdge {
pub child: String,
pub parent: String,
pub file: PathBuf,
pub line: usize,
#[serde(default)]
pub parent_canonical_path: Option<String>,
#[serde(default)]
pub child_canonical_path: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ScanReport {
pub antigens: Vec<AntigenDeclaration>,
pub presentations: Vec<Presentation>,
pub immunities: Vec<Immunity>,
#[serde(default)]
pub tolerances: Vec<Toleration>,
#[serde(default)]
pub lineage_edges: Vec<LineageEdge>,
#[serde(default)]
pub deferred_defenses: Vec<DeferredDefense>,
#[serde(default)]
pub convergent_evidences: Vec<ConvergentEvidence>,
#[serde(default)]
pub recurrent_declarations: Vec<RecurrentDeclaration>,
#[serde(default)]
pub mucosal_declarations: Vec<MucosalDeclaration>,
#[serde(default)]
pub prescriptive_declarations: Vec<PrescriptiveDeclaration>,
#[serde(default)]
pub defenses: Vec<Defense>,
#[serde(default)]
pub generates_declarations: Vec<GeneratesDeclaration>,
#[serde(default)]
pub marked_unknowns: Vec<MarkedUnknown>,
pub files_scanned: usize,
pub parse_failures: Vec<ParseFailure>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub scan_coverage: Option<ScanCoverage>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ScanCoverage {
pub enumerated_members: Vec<String>,
pub scanned_members: Vec<String>,
}
impl ScanCoverage {
#[must_use]
pub fn unscanned_members(&self) -> Vec<&str> {
let scanned: std::collections::HashSet<&str> =
self.scanned_members.iter().map(String::as_str).collect();
let mut seen: std::collections::HashSet<&str> = std::collections::HashSet::new();
self.enumerated_members
.iter()
.map(String::as_str)
.filter(|m| !scanned.contains(m) && seen.insert(m))
.collect()
}
#[must_use]
pub fn is_complete(&self) -> bool {
self.unscanned_members().is_empty()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UnaddressedPresentation {
pub presentation: Presentation,
pub antigen_known: bool,
}
#[derive(Debug, Clone, Default)]
pub struct PartitionedPresentations {
pub explicit: Vec<UnaddressedPresentation>,
pub inferred: Vec<UnaddressedPresentation>,
}
impl ScanReport {
#[must_use]
pub fn unaddressed_presentations(&self) -> Vec<UnaddressedPresentation> {
let known_antigens: std::collections::HashSet<(&str, Option<&str>)> = self
.antigens
.iter()
.map(|a| (a.type_name.as_str(), a.canonical_path.as_deref()))
.collect();
let mut result = Vec::new();
for p in &self.presentations {
let has_matching_immunity =
self.immunities.iter().any(|i| addresses_for_immunity(i, p));
let has_matching_tolerance = self
.tolerances
.iter()
.any(|t| addresses_for_tolerance(t, p));
let has_matching_defense = self.defenses.iter().any(|d| defense_addresses(d, p));
if !has_matching_immunity && !has_matching_tolerance && !has_matching_defense {
result.push(UnaddressedPresentation {
presentation: p.clone(),
antigen_known: known_antigens
.contains(&(p.antigen_type.as_str(), p.canonical_path.as_deref())),
});
}
}
result
}
#[must_use]
pub fn partitioned_presentations(&self) -> PartitionedPresentations {
let mut out = PartitionedPresentations::default();
for up in self.unaddressed_presentations() {
match up.presentation.match_kind {
MatchKind::ExplicitMarker => out.explicit.push(up),
MatchKind::FingerprintMatch => out.inferred.push(up),
}
}
out
}
#[must_use]
pub fn orphaned_tolerances(&self) -> Vec<&Toleration> {
let known: std::collections::HashSet<(&str, Option<&str>)> = self
.antigens
.iter()
.map(|a| (a.type_name.as_str(), a.canonical_path.as_deref()))
.collect();
self.tolerances
.iter()
.filter(|t| !known.contains(&(t.antigen_type.as_str(), t.canonical_path.as_deref())))
.collect()
}
#[must_use]
pub fn orphaned_lineage_edges(&self) -> Vec<&LineageEdge> {
let known: std::collections::HashSet<(&str, Option<&str>)> = self
.antigens
.iter()
.map(|a| (a.type_name.as_str(), a.canonical_path.as_deref()))
.collect();
self.lineage_edges
.iter()
.filter(|e| !known.contains(&(e.parent.as_str(), e.parent_canonical_path.as_deref())))
.collect()
}
#[must_use]
pub fn dangling_child_lineage_edges(&self) -> Vec<&LineageEdge> {
let known: std::collections::HashSet<(&str, Option<&str>)> = self
.antigens
.iter()
.map(|a| (a.type_name.as_str(), a.canonical_path.as_deref()))
.collect();
self.lineage_edges
.iter()
.filter(|e| !known.contains(&(e.child.as_str(), e.child_canonical_path.as_deref())))
.collect()
}
pub fn stamp_canonical_path(&mut self, crate_id: &str) {
for a in &mut self.antigens {
if a.canonical_path.is_none() {
a.canonical_path = Some(crate_id.to_string());
}
}
for p in &mut self.presentations {
if p.canonical_path.is_none() {
p.canonical_path = Some(crate_id.to_string());
}
}
for i in &mut self.immunities {
if i.canonical_path.is_none() {
i.canonical_path = Some(crate_id.to_string());
}
}
for t in &mut self.tolerances {
if t.canonical_path.is_none() {
t.canonical_path = Some(crate_id.to_string());
}
}
for d in &mut self.defenses {
if d.canonical_path.is_none() {
d.canonical_path = Some(crate_id.to_string());
}
}
for g in &mut self.generates_declarations {
if g.canonical_path.is_none() {
g.canonical_path = Some(crate_id.to_string());
}
}
for e in &mut self.lineage_edges {
if e.parent_canonical_path.is_none() {
e.parent_canonical_path = Some(crate_id.to_string());
}
if e.child_canonical_path.is_none() {
e.child_canonical_path = Some(crate_id.to_string());
}
}
}
#[must_use]
pub const fn total_declarations(&self) -> usize {
self.antigens.len()
+ self.presentations.len()
+ self.immunities.len()
+ self.tolerances.len()
}
}
pub fn locus_matches(
a_path: &std::path::Path,
a_canonical: Option<&str>,
b_path: &std::path::Path,
b_canonical: Option<&str>,
) -> bool {
match (a_canonical, b_canonical) {
(None, None) => a_path == b_path,
(Some(x), Some(y)) => x == y,
_ => false,
}
}
fn addresses_for_immunity(i: &Immunity, p: &Presentation) -> bool {
i.antigen_type == p.antigen_type
&& canonical_paths_match(i.canonical_path.as_deref(), p.canonical_path.as_deref())
&& i.item_target.addresses(&p.item_target)
&& locus_matches(
i.file.as_path(),
i.canonical_path.as_deref(),
p.file.as_path(),
p.canonical_path.as_deref(),
)
}
fn addresses_for_tolerance(t: &Toleration, p: &Presentation) -> bool {
t.antigen_type == p.antigen_type
&& canonical_paths_match(t.canonical_path.as_deref(), p.canonical_path.as_deref())
&& t.item_target.addresses(&p.item_target)
&& locus_matches(
t.file.as_path(),
t.canonical_path.as_deref(),
p.file.as_path(),
p.canonical_path.as_deref(),
)
}
#[must_use]
pub fn defense_addresses(d: &Defense, p: &Presentation) -> bool {
d.antigen_type == p.antigen_type
&& canonical_paths_match(d.canonical_path.as_deref(), p.canonical_path.as_deref())
}
#[must_use]
pub fn canonical_paths_match(
item_canonical_path: Option<&str>,
decl_canonical_path: Option<&str>,
) -> bool {
item_canonical_path == decl_canonical_path
}
pub const MAX_LINEAGE_DEPTH: usize = 64;