haz-query 0.1.0

Query evaluator over haz task DAGs.
Documentation
//! Inputs and errors of the query engine.
//!
//! [`QuerySpec`] is the typed bundle of filters the CLI hands
//! to the engine. Every field is optional; an absent filter
//! does not constrain the candidate set per `QRY-006`.
//! Atom-level validation has already happened at the lift
//! layer in [`crate::expr`]; the engine consumes a well-typed
//! spec.
//!
//! [`QueryError`] enumerates the failure modes the engine
//! surfaces. Validation-class errors (unknown bearing project)
//! and runtime-class errors (glob-intersection DFA build
//! failure) are kept distinct so the CLI can render appropriate
//! diagnostics.

use haz_domain::name::{ProjectName, TagName, TaskName};
use haz_domain::path::PathPattern;
use haz_query_lang::expr::Expr;
use snafu::Snafu;

use crate::expr::glob_intersect::GlobIntersectError;
use crate::expr::relational::RelationalAtom;
use crate::expr::shortcut::BooleanShortcut;

/// The full set of filters accepted by `haz query` per
/// `QRY-001`. Each field corresponds to one CLI flag (or, for
/// `shortcuts`, the collection of boolean-shortcut flags).
///
/// An absent (`None`) per-attribute or relational filter
/// contributes no constraint per `QRY-006`. The `shortcuts`
/// vector accumulates every boolean shortcut the user supplied;
/// the engine intersects them against the candidate set.
#[derive(Debug, Default, Clone)]
pub struct QuerySpec {
    /// `--tags <EXPR>` per `QRY-003`.
    pub tags: Option<Expr<TagName>>,
    /// `--projects <EXPR>` per `QRY-003`.
    pub projects: Option<Expr<ProjectName>>,
    /// `--tasks <EXPR>` per `QRY-003`.
    pub tasks: Option<Expr<TaskName>>,
    /// `--inputs <EXPR>` per `QRY-003`.
    pub inputs: Option<Expr<PathPattern>>,
    /// `--outputs <EXPR>` per `QRY-003`.
    pub outputs: Option<Expr<PathPattern>>,
    /// `--child-of <EXPR>` per `QRY-004`.
    pub child_of: Option<Expr<RelationalAtom>>,
    /// `--parent-of <EXPR>` per `QRY-004`.
    pub parent_of: Option<Expr<RelationalAtom>>,
    /// `--depends-on <EXPR>` per `QRY-004`.
    pub depends_on: Option<Expr<RelationalAtom>>,
    /// `--ancestor-of <EXPR>` per `QRY-004`.
    pub ancestor_of: Option<Expr<RelationalAtom>>,
    /// Boolean shortcuts per `QRY-005`. Multiple shortcuts
    /// compose by intersection; the CLI parser enforces
    /// within-pair mutual exclusion (`--has-X` vs `--no-X`).
    pub shortcuts: Vec<BooleanShortcut>,
}

/// Failure produced by the query engine.
///
/// Maps to the `EXEC-021` internal / configuration-error class
/// per `QRY-009`. Each variant carries enough context to render
/// a precise CLI diagnostic.
#[derive(Debug, Snafu)]
pub enum QueryError {
    /// The bearing project supplied by the caller does not
    /// exist in the workspace. Should not occur when the
    /// bearing project is derived from a cwd lookup via
    /// `EXEC-022`, but the engine surfaces it as a typed error
    /// rather than panicking.
    #[snafu(display("bearing project '{name}' is not in the workspace"))]
    BearingProjectNotInWorkspace {
        /// The name of the missing project.
        name: String,
    },

    /// A canonicalised path pattern failed to re-parse after
    /// anchoring to its workspace-absolute form. Should not
    /// occur for patterns that already passed validation; the
    /// variant exists so the engine has a typed surface for
    /// any future canonicalisation regressions.
    #[snafu(display("could not re-parse canonicalised path pattern '{canonical}'"))]
    CanonicalisePattern {
        /// The offending canonicalised pattern string.
        canonical: String,
    },

    /// The glob-glob intersection routine failed for one of the
    /// `--inputs` / `--outputs` atoms.
    #[snafu(display("glob intersection failed: {source}"))]
    GlobIntersect {
        /// The underlying intersection-routine failure.
        source: GlobIntersectError,
    },
}