use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub(crate) struct SeamId(String);
impl SeamId {
pub(crate) fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub(crate) enum SeamKind {
PredicateBoundary,
ErrorVariant,
ReturnValue,
FieldConstruction,
SideEffect,
MatchArm,
CallPresence,
}
impl SeamKind {
pub(crate) fn as_str(&self) -> &'static str {
match self {
SeamKind::PredicateBoundary => "predicate_boundary",
SeamKind::ErrorVariant => "error_variant",
SeamKind::ReturnValue => "return_value",
SeamKind::FieldConstruction => "field_construction",
SeamKind::SideEffect => "side_effect",
SeamKind::MatchArm => "match_arm",
SeamKind::CallPresence => "call_presence",
}
}
#[cfg(test)]
pub(crate) fn from_str(s: &str) -> Option<Self> {
Some(match s {
"predicate_boundary" => SeamKind::PredicateBoundary,
"error_variant" => SeamKind::ErrorVariant,
"return_value" => SeamKind::ReturnValue,
"field_construction" => SeamKind::FieldConstruction,
"side_effect" => SeamKind::SideEffect,
"match_arm" => SeamKind::MatchArm,
"call_presence" => SeamKind::CallPresence,
_ => return None,
})
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub(crate) enum RequiredDiscriminator {
BoundaryValue { description: String },
ErrorVariant { variant: String },
ReturnValue { description: String },
FieldValue { field: String },
Effect { sink: String },
MatchArmTaken { arm: String },
CallSite { target: String },
}
impl RequiredDiscriminator {
pub(crate) fn as_str(&self) -> &'static str {
match self {
RequiredDiscriminator::BoundaryValue { .. } => "boundary_value",
RequiredDiscriminator::ErrorVariant { .. } => "error_variant",
RequiredDiscriminator::ReturnValue { .. } => "return_value",
RequiredDiscriminator::FieldValue { .. } => "field_value",
RequiredDiscriminator::Effect { .. } => "effect",
RequiredDiscriminator::MatchArmTaken { .. } => "match_arm_taken",
RequiredDiscriminator::CallSite { .. } => "call_site",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub(crate) enum ExpectedSink {
ReturnValue,
OutputField,
ErrorChannel,
SideEffect,
}
impl ExpectedSink {
pub(crate) fn as_str(&self) -> &'static str {
match self {
ExpectedSink::ReturnValue => "return_value",
ExpectedSink::OutputField => "output_field",
ExpectedSink::ErrorChannel => "error_channel",
ExpectedSink::SideEffect => "side_effect",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub(crate) enum SeamGripClass {
StronglyGripped,
WeaklyGripped,
Ungripped,
ReachableUnrevealed,
ActivationUnknown,
PropagationUnknown,
ObservationUnknown,
DiscriminationUnknown,
Opaque,
Intentional,
Suppressed,
}
impl SeamGripClass {
pub(crate) const ALL: [SeamGripClass; 11] = [
SeamGripClass::StronglyGripped,
SeamGripClass::WeaklyGripped,
SeamGripClass::Ungripped,
SeamGripClass::ReachableUnrevealed,
SeamGripClass::ActivationUnknown,
SeamGripClass::PropagationUnknown,
SeamGripClass::ObservationUnknown,
SeamGripClass::DiscriminationUnknown,
SeamGripClass::Opaque,
SeamGripClass::Intentional,
SeamGripClass::Suppressed,
];
pub(crate) fn as_str(&self) -> &'static str {
match self {
SeamGripClass::StronglyGripped => "strongly_gripped",
SeamGripClass::WeaklyGripped => "weakly_gripped",
SeamGripClass::Ungripped => "ungripped",
SeamGripClass::ReachableUnrevealed => "reachable_unrevealed",
SeamGripClass::ActivationUnknown => "activation_unknown",
SeamGripClass::PropagationUnknown => "propagation_unknown",
SeamGripClass::ObservationUnknown => "observation_unknown",
SeamGripClass::DiscriminationUnknown => "discrimination_unknown",
SeamGripClass::Opaque => "opaque",
SeamGripClass::Intentional => "intentional",
SeamGripClass::Suppressed => "suppressed",
}
}
pub(crate) fn is_headline_eligible(&self) -> bool {
matches!(
self,
SeamGripClass::Ungripped
| SeamGripClass::WeaklyGripped
| SeamGripClass::ReachableUnrevealed
| SeamGripClass::ActivationUnknown
| SeamGripClass::PropagationUnknown
| SeamGripClass::ObservationUnknown
| SeamGripClass::DiscriminationUnknown
)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub(crate) struct RepoSeam {
id: SeamId,
kind: SeamKind,
file: PathBuf,
owner: String,
byte_offset: usize,
display_line: usize,
expression: String,
required_discriminator: RequiredDiscriminator,
expected_sink: ExpectedSink,
}
impl RepoSeam {
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
file: impl AsRef<Path>,
owner: impl Into<String>,
kind: SeamKind,
byte_offset: usize,
display_line: usize,
expression: impl Into<String>,
required_discriminator: RequiredDiscriminator,
expected_sink: ExpectedSink,
) -> Self {
let file_normalized = normalize_path(file.as_ref());
let owner = owner.into();
let id = compute_seam_id(&file_normalized, &owner, kind, byte_offset);
RepoSeam {
id,
kind,
file: PathBuf::from(file_normalized),
owner,
byte_offset,
display_line,
expression: expression.into(),
required_discriminator,
expected_sink,
}
}
pub(crate) fn id(&self) -> &SeamId {
&self.id
}
pub(crate) fn kind(&self) -> SeamKind {
self.kind
}
pub(crate) fn file(&self) -> &Path {
&self.file
}
pub(crate) fn owner(&self) -> &str {
&self.owner
}
pub(crate) fn byte_offset(&self) -> usize {
self.byte_offset
}
pub(crate) fn display_line(&self) -> usize {
self.display_line
}
pub(crate) fn expression(&self) -> &str {
&self.expression
}
pub(crate) fn required_discriminator(&self) -> &RequiredDiscriminator {
&self.required_discriminator
}
pub(crate) fn expected_sink(&self) -> ExpectedSink {
self.expected_sink
}
}
fn normalize_path(p: &Path) -> String {
let s = p.to_string_lossy().replace('\\', "/");
s.strip_prefix("./").unwrap_or(&s).to_string()
}
fn compute_seam_id(file: &str, owner: &str, kind: SeamKind, byte_offset: usize) -> SeamId {
const FNV_OFFSET: u64 = 0xcbf29ce484222325;
const FNV_PRIME: u64 = 0x100000001b3;
let offset_str = byte_offset.to_string();
let parts: [&[u8]; 7] = [
file.as_bytes(),
b"\0",
owner.as_bytes(),
b"\0",
kind.as_str().as_bytes(),
b"\0",
offset_str.as_bytes(),
];
let mut hash: u64 = FNV_OFFSET;
for part in parts {
for byte in part {
hash ^= u64::from(*byte);
hash = hash.wrapping_mul(FNV_PRIME);
}
}
SeamId(format!("{hash:016x}"))
}
#[cfg(test)]
mod tests {
use super::*;
fn make_seam(file: &str, owner: &str, kind: SeamKind, off: usize) -> RepoSeam {
RepoSeam::new(
file,
owner,
kind,
off,
1,
"amount >= threshold",
RequiredDiscriminator::BoundaryValue {
description: "amount >= threshold".to_string(),
},
ExpectedSink::ReturnValue,
)
}
#[test]
fn seam_id_is_deterministic_for_identical_inputs() {
let a = make_seam(
"src/pricing.rs",
"pricing::quote",
SeamKind::PredicateBoundary,
88,
);
let b = make_seam(
"src/pricing.rs",
"pricing::quote",
SeamKind::PredicateBoundary,
88,
);
assert_eq!(a.id(), b.id());
}
#[test]
fn seam_id_differs_when_any_canonical_field_differs() {
let base = make_seam(
"src/pricing.rs",
"pricing::quote",
SeamKind::PredicateBoundary,
88,
);
let other_file = make_seam(
"src/checkout.rs",
"pricing::quote",
SeamKind::PredicateBoundary,
88,
);
let other_owner = make_seam(
"src/pricing.rs",
"pricing::compute",
SeamKind::PredicateBoundary,
88,
);
let other_kind = make_seam(
"src/pricing.rs",
"pricing::quote",
SeamKind::ReturnValue,
88,
);
let other_offset = make_seam(
"src/pricing.rs",
"pricing::quote",
SeamKind::PredicateBoundary,
89,
);
assert_ne!(base.id(), other_file.id());
assert_ne!(base.id(), other_owner.id());
assert_ne!(base.id(), other_kind.id());
assert_ne!(base.id(), other_offset.id());
}
#[test]
fn seam_ids_do_not_depend_on_construction_order() -> Result<(), String> {
let inputs = [
("src/a.rs", "a::f", SeamKind::PredicateBoundary, 10),
("src/b.rs", "b::g", SeamKind::ErrorVariant, 20),
("src/c.rs", "c::h", SeamKind::ReturnValue, 30),
];
let forward: Vec<String> = inputs
.iter()
.map(|(f, o, k, off)| make_seam(f, o, *k, *off).id().as_str().to_string())
.collect();
let mut reversed: Vec<String> = inputs
.iter()
.rev()
.map(|(f, o, k, off)| make_seam(f, o, *k, *off).id().as_str().to_string())
.collect();
reversed.reverse();
if forward != reversed {
return Err("seam IDs depend on construction order".to_string());
}
Ok(())
}
#[test]
fn seam_id_normalizes_windows_path_separators() {
let unix = make_seam(
"src/pricing.rs",
"pricing::quote",
SeamKind::PredicateBoundary,
88,
);
let windows = make_seam(
"src\\pricing.rs",
"pricing::quote",
SeamKind::PredicateBoundary,
88,
);
assert_eq!(unix.id(), windows.id());
}
#[test]
fn seam_id_normalizes_leading_dot_slash() {
let plain = make_seam(
"src/pricing.rs",
"pricing::quote",
SeamKind::PredicateBoundary,
88,
);
let dotted = make_seam(
"./src/pricing.rs",
"pricing::quote",
SeamKind::PredicateBoundary,
88,
);
assert_eq!(plain.id(), dotted.id());
}
#[test]
fn seam_id_is_16_lowercase_hex_chars() -> Result<(), String> {
let seam = make_seam("src/x.rs", "x::y", SeamKind::PredicateBoundary, 0);
let id = seam.id().as_str();
if id.len() != 16 {
return Err(format!(
"seam id should be 16 chars, got {}: {id}",
id.len()
));
}
for c in id.chars() {
if !c.is_ascii_hexdigit() {
return Err(format!("seam id should be hex, got: {id}"));
}
if c.is_ascii_alphabetic() && !c.is_ascii_lowercase() {
return Err(format!("seam id hex should be lowercase, got: {id}"));
}
}
Ok(())
}
#[test]
fn seam_kind_round_trips_through_str() -> Result<(), String> {
let all = [
SeamKind::PredicateBoundary,
SeamKind::ErrorVariant,
SeamKind::ReturnValue,
SeamKind::FieldConstruction,
SeamKind::SideEffect,
SeamKind::MatchArm,
SeamKind::CallPresence,
];
for kind in all {
let s = kind.as_str();
let parsed = SeamKind::from_str(s)
.ok_or_else(|| format!("SeamKind::from_str rejected its own as_str: {s}"))?;
if parsed != kind {
return Err(format!("round-trip failed for {s}"));
}
}
if SeamKind::from_str("nonsense").is_some() {
return Err("SeamKind::from_str should reject unknown strings".to_string());
}
Ok(())
}
#[test]
fn required_discriminator_carries_kind_via_as_str() {
let cases: &[(RequiredDiscriminator, &str)] = &[
(
RequiredDiscriminator::BoundaryValue {
description: "amount >= threshold".to_string(),
},
"boundary_value",
),
(
RequiredDiscriminator::ErrorVariant {
variant: "QuoteError::Insolvent".to_string(),
},
"error_variant",
),
(
RequiredDiscriminator::ReturnValue {
description: "non-zero discount".to_string(),
},
"return_value",
),
(
RequiredDiscriminator::FieldValue {
field: "Discount.amount".to_string(),
},
"field_value",
),
(
RequiredDiscriminator::Effect {
sink: "log::error".to_string(),
},
"effect",
),
(
RequiredDiscriminator::MatchArmTaken {
arm: "Pricing::Premium".to_string(),
},
"match_arm_taken",
),
(
RequiredDiscriminator::CallSite {
target: "metrics::record".to_string(),
},
"call_site",
),
];
for (case, expected) in cases {
assert_eq!(case.as_str(), *expected);
}
}
#[test]
fn expected_sink_str_covers_all_variants() {
let all = [
(ExpectedSink::ReturnValue, "return_value"),
(ExpectedSink::OutputField, "output_field"),
(ExpectedSink::ErrorChannel, "error_channel"),
(ExpectedSink::SideEffect, "side_effect"),
];
for (sink, expected) in all {
assert_eq!(sink.as_str(), expected);
}
}
#[test]
fn repo_seam_accessors_round_trip_construction_inputs() -> Result<(), String> {
let seam = RepoSeam::new(
"src/pricing.rs",
"pricing::check_discount",
SeamKind::PredicateBoundary,
1234,
88,
"amount >= discount_threshold",
RequiredDiscriminator::BoundaryValue {
description: "amount >= discount_threshold".to_string(),
},
ExpectedSink::ReturnValue,
);
assert_eq!(seam.kind(), SeamKind::PredicateBoundary);
assert_eq!(seam.owner(), "pricing::check_discount");
assert_eq!(seam.byte_offset(), 1234);
assert_eq!(seam.display_line(), 88);
assert_eq!(seam.expression(), "amount >= discount_threshold");
assert_eq!(seam.expected_sink(), ExpectedSink::ReturnValue);
assert_eq!(seam.file().to_string_lossy(), "src/pricing.rs");
match seam.required_discriminator() {
RequiredDiscriminator::BoundaryValue { description } => {
assert_eq!(description, "amount >= discount_threshold");
Ok(())
}
other => Err(format!("expected BoundaryValue, got {}", other.as_str())),
}
}
}