parlov-elicit 0.1.1

Elicitation engine: strategy selection and probe plan generation for parlov.
Documentation
//! Scan context: the operator-supplied parameters that drive strategy selection.

use http::HeaderMap;

use crate::types::RiskLevel;

/// A duplicate field value that already exists in the target system.
///
/// Supplied when the operator knows a specific field value is taken, enabling
/// uniqueness-conflict elicitation strategies.
#[derive(Debug, Clone)]
pub struct KnownDuplicate {
    /// JSON field name, e.g. `"email"`.
    pub field: String,
    /// The value already in use, e.g. `"alice@example.com"`.
    pub value: String,
}

/// A field value that puts the target resource into an invalid or rejected state.
///
/// Supplied to enable state-transition elicitation strategies.
#[derive(Debug, Clone)]
pub struct StateField {
    /// JSON field name, e.g. `"status"`.
    pub field: String,
    /// The invalid value, e.g. `"invalid_state"`.
    pub value: String,
}

/// All operator-supplied parameters that govern a single elicitation scan.
///
/// Strategies inspect `ScanContext` to decide applicability and to construct
/// `ProbeSpec` values. No I/O occurs here — this is pure configuration.
#[derive(Debug, Clone)]
pub struct ScanContext {
    /// URL template for the target endpoint. May contain `{id}` placeholder.
    pub target: String,
    /// ID of a known-existing resource, used as the baseline control input.
    pub baseline_id: String,
    /// ID of a known-nonexistent resource, used as the probe input.
    pub probe_id: String,
    /// Request headers common to all probes, typically including `Authorization`.
    pub headers: HeaderMap,
    /// Maximum risk level the operator permits; strategies above this are skipped.
    pub max_risk: RiskLevel,
    /// Known duplicate field value for uniqueness-conflict strategies. `None` if
    /// not applicable.
    pub known_duplicate: Option<KnownDuplicate>,
    /// Field value that triggers invalid-state for state-transition strategies.
    /// `None` if not applicable.
    pub state_field: Option<StateField>,
    /// Alternative (under-scoped) credential set for scope-manipulation strategies.
    /// `None` if not applicable.
    pub alt_credential: Option<HeaderMap>,
}

#[cfg(test)]
mod tests {
    use super::*;
    use http::HeaderMap;

    // --- Type construction ---

    #[test]
    fn known_duplicate_fields_accessible() {
        let kd = KnownDuplicate {
            field: "email".to_owned(),
            value: "alice@example.com".to_owned(),
        };
        assert_eq!(kd.field, "email");
        assert_eq!(kd.value, "alice@example.com");
    }

    #[test]
    fn state_field_fields_accessible() {
        let sf = StateField {
            field: "status".to_owned(),
            value: "invalid_state".to_owned(),
        };
        assert_eq!(sf.field, "status");
        assert_eq!(sf.value, "invalid_state");
    }

    #[test]
    fn scan_context_all_some_fields_accessible() {
        let ctx = ScanContext {
            target: "https://example.com/users/{id}".to_owned(),
            baseline_id: "00000000-0000-0000-0000-000000000001".to_owned(),
            probe_id: "ffffffff-ffff-ffff-ffff-ffffffffffff".to_owned(),
            headers: HeaderMap::new(),
            max_risk: RiskLevel::MethodDestructive,
            known_duplicate: Some(KnownDuplicate {
                field: "email".to_owned(),
                value: "alice@example.com".to_owned(),
            }),
            state_field: Some(StateField {
                field: "status".to_owned(),
                value: "invalid_state".to_owned(),
            }),
            alt_credential: Some(HeaderMap::new()),
        };

        assert_eq!(ctx.target, "https://example.com/users/{id}");
        assert_eq!(ctx.max_risk, RiskLevel::MethodDestructive);
        assert!(ctx.known_duplicate.is_some());
        assert!(ctx.state_field.is_some());
        assert!(ctx.alt_credential.is_some());
    }

    #[test]
    fn scan_context_all_none_fields_accessible() {
        let ctx = ScanContext {
            target: "https://example.com/items/{id}".to_owned(),
            baseline_id: "1".to_owned(),
            probe_id: "999999".to_owned(),
            headers: HeaderMap::new(),
            max_risk: RiskLevel::Safe,
            known_duplicate: None,
            state_field: None,
            alt_credential: None,
        };

        assert!(ctx.known_duplicate.is_none());
        assert!(ctx.state_field.is_none());
        assert!(ctx.alt_credential.is_none());
    }
}