djogi 0.1.0-alpha.2

Model-first web framework for Rust — web-framework-agnostic core; Axum integration opt-in via the `axum` feature flag
Documentation
//! Query and ordering surface for presentation-codec-governed fields.
//!
//! This module provides the types that generated visage fields return when
//! a field is governed by a `protected(per_scope = { ... })` declaration.
//! The surface is intentionally narrow: callers cannot reach the underlying
//! storage `FieldRef` directly — they interact only through the codec
//! trait gates.
//!
//! # Access control
//!
//! - Predicate access is available only when `Codec: PresentationQueryCodec<FieldTy>`.
//! - Order access is available only when `Codec: PresentationOrderCodec<FieldTy>`.
//! - The `eq_storage`, `asc_storage`, `desc_storage` methods on
//!   [`PresentationQueryField`] / [`PresentationOrderField`] are public for
//!   codec implementations but are not directly reachable from generated
//!   user-facing visage accessors. Callers obtain them only through
//!   `PresentationFieldRef`'s codec-gated methods.
//!
//! # Two-layer design
//!
//! Generated field surfaces return `PresentationFieldRef<Source, Codec, FieldTy>`.
//! That type exposes predicate/order methods only when `Codec` implements the
//! matching query codec trait. Those public methods construct a temporary
//! `PresentationQueryField` / `PresentationOrderField` and delegate to the
//! codec trait implementation, which in turn calls the narrow storage-delegate
//! methods (`eq_storage`, `asc_storage`, `desc_storage`) to produce the final
//! `Q<M>` / `OrderExpr`.
//!
//! This layering ensures:
//! 1. The underlying storage `FieldRef` is never exposed to callers.
//! 2. The only queryability path is through explicit codec trait impls.
//! 3. `Identity` (which delegates storage-value equality) works naturally.

use crate::model::Model;
use crate::query::{FieldRef, OrderExpr, Q};

use super::PresentationCodecInfo;

/// User-facing accessor for a presentation-governed protected field.
///
/// Generated by `#[model]` for each field with a `per_scope` block.
/// The `Source` parameter is the owning model type, `Codec` is the
/// selected presentation codec for this scope, and `FieldTy` is the
/// field's storage type.
///
/// Predicate methods (`eq`) are only available when
/// `Codec: PresentationQueryCodec<FieldTy>`. Ordering methods (`asc`, `desc`)
/// are only available when `Codec: PresentationOrderCodec<FieldTy>`.
///
/// # Security note
///
/// `PresentationFieldRef` never exposes the underlying `FieldRef<Source,
/// FieldTy>` getter. Storage-level predicates on this field are only
/// available through `Model::objects()` on the source model, which remains
/// privileged and is not narrowed by presentation rules.
pub struct PresentationFieldRef<Source: Model, Codec, FieldTy> {
    /// The crate-internal storage-level field reference.
    ///
    /// Kept private — callers must go through the codec-gated methods
    /// on this type, not through the `FieldRef` directly.
    inner: FieldRef<Source, FieldTy>,
    _codec: std::marker::PhantomData<Codec>,
}

impl<Source: Model, Codec, FieldTy> PresentationFieldRef<Source, Codec, FieldTy> {
    /// Construct a `PresentationFieldRef` from a storage-level `FieldRef`.
    ///
    /// This is a crate-private constructor called only from macro-emitted
    /// code. Downstream code cannot construct this type; it is received
    /// from generated accessor methods on visage `Fields` structs.
    #[doc(hidden)]
    pub fn __new_crate_private(inner: FieldRef<Source, FieldTy>) -> Self {
        Self {
            inner,
            _codec: std::marker::PhantomData,
        }
    }
}

impl<Source, Codec, FieldTy> PresentationFieldRef<Source, Codec, FieldTy>
where
    Source: Model,
    Codec: PresentationQueryCodec<FieldTy> + PresentationCodecInfo<FieldTy>,
{
    /// Build a predicate against the presented query value.
    ///
    /// Available only when `Codec: PresentationQueryCodec<FieldTy>`. The
    /// query value type is `Codec::QueryValue`, not automatically the source
    /// field type or output field type.
    ///
    /// # Example
    ///
    /// ```ignore
    /// // For a field with Identity (which is queryable by storage value):
    /// UserPublicFields::default().ssn().eq("123-45-6789")
    /// ```
    pub fn eq(&self, value: Codec::QueryValue) -> Q<Source> {
        // `FieldRef<Source, FieldTy>` is unconditionally `Copy`
        // (`impl<M, V> Copy for FieldRef<M, V> {}`), so copying `self.inner`
        // through the shared reference requires no explicit clone.
        Codec::to_query_value_and_build(PresentationQueryField { inner: self.inner }, value)
    }
}

impl<Source, Codec, FieldTy> PresentationFieldRef<Source, Codec, FieldTy>
where
    Source: Model,
    Codec: PresentationOrderCodec<FieldTy> + PresentationCodecInfo<FieldTy>,
{
    /// Build an ascending ORDER BY expression for this field.
    ///
    /// Available only when `Codec: PresentationOrderCodec<FieldTy>`.
    pub fn asc(&self) -> OrderExpr {
        // `FieldRef` is unconditionally `Copy`; the compiler copies `self.inner`
        // when calling the by-value `asc()` through `&self`.
        self.inner.asc()
    }

    /// Build a descending ORDER BY expression for this field.
    ///
    /// Available only when `Codec: PresentationOrderCodec<FieldTy>`.
    pub fn desc(&self) -> OrderExpr {
        // `FieldRef` is unconditionally `Copy`; the compiler copies `self.inner`
        // when calling the by-value `desc()` through `&self`.
        self.inner.desc()
    }
}

/// Narrow predicate-construction handle passed to codec implementations.
///
/// `PresentationQueryField` is the gateway through which a
/// [`PresentationQueryCodec`] implementation produces a `Q<M>` predicate.
/// The codec calls one of the narrow storage-delegate methods (`eq_storage`)
/// to construct the predicate.
///
/// # Security boundary
///
/// The underlying `FieldRef` is crate-private. Codec implementations that
/// receive this type in their trait method can only call the narrow public
/// surface below. A downstream adopter cannot construct `PresentationQueryField`
/// directly — it arrives from `PresentationFieldRef::eq`, which is gated
/// by the `PresentationQueryCodec` bound.
///
/// # Granting storage-predicate power
///
/// Implementing `PresentationQueryCodec` with an `eq_storage`-based
/// implementation (as `Identity` does) grants storage-value predicates for
/// that protected presentation. Document this clearly in codec rustdoc.
pub struct PresentationQueryField<Source: Model, FieldTy> {
    inner: FieldRef<Source, FieldTy>,
}

impl<Source, FieldTy> PresentationQueryField<Source, FieldTy>
where
    Source: Model,
{
    /// Build an equality predicate against the raw storage value.
    ///
    /// Grants storage-value equality access for the protected field. Call this
    /// only from codec implementations that intentionally expose storage
    /// predicates — document the grant in the codec's rustdoc.
    ///
    /// The returned `Q<Source>` wraps the `Condition` leaf produced by
    /// `FieldRef::eq` through `Q::Condition`. This is an SQL-only predicate —
    /// it does not flow through the portable predicate path.
    pub fn eq_storage<P>(self, value: P) -> Q<Source>
    where
        P: crate::query::IntoFieldFilterValue<FieldTy>,
    {
        Q::Condition(self.inner.eq(value))
    }
}

/// Narrow ordering-construction handle passed to codec implementations.
///
/// `PresentationOrderField` is the gateway through which a
/// [`PresentationOrderCodec`] implementation produces `OrderExpr` values.
/// The codec calls one of the narrow storage-delegate methods.
///
/// Same security boundary as [`PresentationQueryField`].
pub struct PresentationOrderField<Source: Model, FieldTy> {
    inner: FieldRef<Source, FieldTy>,
}

impl<Source, FieldTy> PresentationOrderField<Source, FieldTy>
where
    Source: Model,
{
    /// Build an ascending ORDER BY expression over the raw storage column.
    ///
    /// Grants storage-value ascending ordering for the protected field.
    pub fn asc_storage(self) -> OrderExpr {
        self.inner.asc()
    }

    /// Build a descending ORDER BY expression over the raw storage column.
    ///
    /// Grants storage-value descending ordering for the protected field.
    pub fn desc_storage(self) -> OrderExpr {
        self.inner.desc()
    }
}

/// Query codec gate — enables predicate access on a presentation field.
///
/// A codec implements this trait to declare that presented values support
/// equality predicates against the original query surface. The `QueryValue`
/// associated type controls what the caller provides.
///
/// # Security
///
/// Implementing this trait grants callers the ability to probe field values
/// through the generated visage accessor. Use only for transforms where
/// predicate access is safe (e.g. `Identity`, or a blind-index HMAC where
/// the query value is itself HMAC output).
///
/// Default queryability is [`Disabled`](super::Queryability::Disabled). Set
/// [`PresentationCodecInfo::QUERYABILITY`] to at least
/// [`PredicateOnly`](super::Queryability::PredicateOnly) when implementing
/// this trait.
pub trait PresentationQueryCodec<Input>: PresentationCodecInfo<Input> {
    /// The type the caller provides as the query value.
    ///
    /// For `Identity`, this is the storage type itself. For a blind-index
    /// HMAC codec, this could be a pre-computed HMAC output.
    type QueryValue;

    /// Build a `Q<M>` predicate given a narrow handle to the field and the
    /// caller's query value.
    ///
    /// The implementation calls [`PresentationQueryField::eq_storage`]
    /// (or a custom predicate) to construct the final `Q<M>`.
    fn to_query_value_and_build<M: Model>(
        field: PresentationQueryField<M, Input>,
        value: Self::QueryValue,
    ) -> Q<M>;
}

/// Order codec gate — enables ascending/descending ordering on a
/// presentation field.
///
/// A codec implements this trait to declare that presented values support
/// ordering. The trait itself has no required methods — ordering is always
/// over the storage column, delegated through
/// `PresentationFieldRef::asc` / `PresentationFieldRef::desc` which call
/// `FieldRef::asc` / `FieldRef::desc` directly.
///
/// Default queryability is [`Disabled`](super::Queryability::Disabled). Set
/// [`PresentationCodecInfo::QUERYABILITY`] to at least
/// [`OrderOnly`](super::Queryability::OrderOnly) when implementing this
/// trait.
pub trait PresentationOrderCodec<Input>: PresentationCodecInfo<Input> {}