cloudiful-redactor 0.2.9

Structured text redaction with reversible sessions for secrets, domains, URLs, and related sensitive values.
Documentation
use crate::input::redactable_ranges;
use crate::types::Finding;

use super::Redactor;

#[derive(Debug, Default)]
pub(super) struct DetectionStats {
    pub(super) dropped_findings: usize,
    pub(super) llm_candidates_total: usize,
    pub(super) llm_request_failed: bool,
    pub(super) llm_error: Option<String>,
}

#[derive(Debug)]
pub(super) struct DetectionOutcome {
    pub(super) findings: Vec<Finding>,
    pub(super) stats: DetectionStats,
}

pub(super) fn detect_internal(
    redactor: &Redactor,
    text: &str,
    input_kind: crate::InputKind,
    source_path: Option<&str>,
) -> DetectionOutcome {
    let ranges = redactable_ranges(text, input_kind, source_path);
    if ranges.is_empty() {
        return DetectionOutcome {
            findings: Vec::new(),
            stats: DetectionStats::default(),
        };
    }

    let result = crate::detect::detect_with_policy(text, &redactor.policy, &ranges);
    let mut findings = result.findings;
    let mut stats = DetectionStats {
        dropped_findings: result.dropped_findings,
        ..DetectionStats::default()
    };

    if let Some(config) = &redactor.llm {
        match crate::llm::discover_candidates(config, text, redactor.policy.rules) {
            Ok(mut llm_findings) => {
                stats.llm_candidates_total += llm_findings.len();
                findings.append(&mut llm_findings);
            }
            Err(error) => {
                stats.llm_request_failed = true;
                stats.llm_error = Some(error.to_string());
            }
        }
        let (merged, dropped) = crate::detect::select_non_overlapping(findings);
        stats.dropped_findings += dropped;
        findings = merged;
    }

    DetectionOutcome { findings, stats }
}