qubit-validator 0.1.2

Type-safe validation traits and registries for Qubit applications
Documentation
// =============================================================================
//    Copyright (c) 2025 - 2026 Haixing Hu.
//
//    SPDX-License-Identifier: Apache-2.0
//
//    Licensed under the Apache License, Version 2.0.
// =============================================================================

//! Structured data violations.

use std::collections::BTreeMap;
use std::fmt::Debug;
use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Result as FmtResult;

use super::ValidationPath;
use super::ViolationCode;
use super::ViolationParam;
use crate::ValidatorId;

/// One rule violation without the rejected value.
///
/// # Examples
///
/// ```
/// use qubit_validator::{ValidatorId, Violation, ViolationCode};
///
/// let violation = Violation::new(
///     ValidatorId::new("text.required"),
///     ViolationCode::new("text.blank"),
/// );
/// assert_eq!(violation.code().as_str(), "text.blank");
/// ```
#[must_use]
#[derive(Clone, Eq, PartialEq)]
pub struct Violation {
    /// Stable identifier of the rule which rejected the value.
    rule_id: ValidatorId,
    /// Stable, program-declared violation code.
    code: ViolationCode,
    /// Structured location of the rejected value.
    path: ValidationPath,
    /// Structured parameters whose provenance must be checked by their caller.
    params: BTreeMap<&'static str, ViolationParam>,
}

impl Violation {
    /// Creates a safe violation at the root path.
    ///
    /// # Parameters
    ///
    /// - `rule_id`: Stable identifier of the rule that rejected the value.
    /// - `code`: Program-declared code describing the violation.
    ///
    /// # Returns
    ///
    /// A violation with an empty path and no parameters.
    #[inline]
    pub fn new(rule_id: ValidatorId, code: ViolationCode) -> Self {
        Self {
            rule_id,
            code,
            path: ValidationPath::root(),
            params: BTreeMap::new(),
        }
    }

    /// Returns the stable rule identifier.
    ///
    /// # Returns
    ///
    /// The identifier assigned by the bound validator.
    #[must_use]
    #[inline]
    pub const fn rule_id(&self) -> ValidatorId {
        self.rule_id
    }

    /// Returns the stable violation code.
    ///
    /// # Returns
    ///
    /// The program-declared code for this violation.
    #[must_use]
    #[inline]
    pub const fn code(&self) -> ViolationCode {
        self.code
    }

    /// Returns the structured path.
    ///
    /// The path contains no raw rejected value or map key.
    ///
    /// # Returns
    ///
    /// The structured path to the rejected value.
    #[must_use]
    #[inline]
    pub const fn path(&self) -> &ValidationPath {
        &self.path
    }

    /// Returns structured parameters supplied by the caller.
    ///
    /// The type restricts their representation but does not prove provenance;
    /// callers remain responsible for keeping rejected input and sensitive
    /// values derived from it out of this collection.
    ///
    /// # Returns
    ///
    /// The named parameters associated with this violation.
    #[must_use]
    #[inline]
    pub const fn params(&self) -> &BTreeMap<&'static str, ViolationParam> {
        &self.params
    }

    /// Replaces the structured location of the rejected value.
    ///
    /// # Parameters
    ///
    /// - `path`: Path containing only declared fields and opaque positions.
    ///
    /// # Returns
    ///
    /// The violation with `path` stored in place of its previous path.
    #[inline]
    pub fn with_path(mut self, path: ValidationPath) -> Self {
        self.path = path;
        self
    }

    /// Adds or replaces a structured parameter by its declared name.
    ///
    /// The type restricts representation but cannot prove provenance. Supply
    /// only program-declared tokens or rule configuration values, never
    /// rejected input or sensitive values derived from it.
    ///
    /// # Parameters
    ///
    /// - `name`: Static program-declared parameter name.
    /// - `value`: Scalar or static token associated with the name, subject to
    ///   the caller's provenance and redaction responsibility.
    ///
    /// # Returns
    ///
    /// The violation with the named parameter inserted or replaced.
    pub fn with_param(mut self, name: &'static str, value: ViolationParam) -> Self {
        self.params.insert(name, value);
        self
    }

    /// Converts an adapter-produced draft into a violation for its bound rule.
    ///
    /// The draft is consumed so its code, path, and parameters can be moved
    /// into the violation without cloning. The supplied identifier comes from
    /// the validator binding that owns the draft.
    ///
    /// # Parameters
    ///
    /// - `rule_id`: Identifier of the validator that owns the draft.
    /// - `draft`: Adapter-produced violation data consumed by this conversion.
    ///
    /// # Returns
    ///
    /// A violation associated with `rule_id` and containing the draft data.
    pub(crate) fn from_draft(rule_id: ValidatorId, draft: crate::ViolationDraft) -> Self {
        let (code, path, params) = draft.parts();
        Self {
            rule_id,
            code,
            path,
            params,
        }
    }
}

impl Debug for Violation {
    /// Formats stable identifiers without exposing path labels or parameters.
    ///
    /// # Parameters
    ///
    /// - `formatter`: Destination and formatting options for the debug output.
    fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
        formatter
            .debug_struct("Violation")
            .field("rule_id", &self.rule_id)
            .field("code", &self.code)
            .finish_non_exhaustive()
    }
}

impl Display for Violation {
    /// Formats only the stable violation code.
    ///
    /// # Parameters
    ///
    /// - `formatter`: Destination and formatting options for the display
    ///   output.
    fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
        Display::fmt(&self.code, formatter)
    }
}