Skip to main content

pgevolve_core/lint/
mod.rs

1//! Lint engine. Spec ยง12.
2//!
3//! Two-stage check:
4//! 1. [`universal::check_universal`] โ€” rules that apply to every profile.
5//! 2. [`profile::check_profile`] โ€” path-shape rules specific to one layout
6//!    profile (schema-mirror, kind-grouped, feature-grouped, free-form,
7//!    custom).
8//!
9//! Callers compose both via [`run`].
10
11pub mod finding;
12pub mod profile;
13pub(crate) mod rules;
14pub mod source_tree;
15pub mod universal;
16
17use std::path::Path;
18
19pub use finding::{Finding, Severity};
20pub use profile::{CustomProfile, PathPattern, Profile, check_profile};
21pub use source_tree::{ObjectKey, SourceTree};
22pub use universal::{
23    LINT_AT_PLAN_RULES, check_changeset, check_cluster_changeset, check_plan_time_catalog,
24    check_universal, check_universal_with_cluster,
25};
26
27/// Inputs for [`run`].
28#[derive(Debug, Clone)]
29pub struct LintInputs<'a> {
30    /// Parsed source tree.
31    pub tree: &'a SourceTree,
32    /// `[managed]` config block.
33    pub managed: &'a ManagedConfig,
34    /// Profile to apply.
35    pub profile: &'a Profile,
36    /// Source-tree root, used to compute relative paths for profile checks.
37    pub schema_dir: &'a Path,
38}
39
40/// `[managed]` view passed to the lint engine.
41///
42/// Mirrors the binary's `ManagedConfig` but lives in the core crate so the
43/// lint engine doesn't depend on the binary.
44#[derive(Debug, Clone, Default)]
45pub struct ManagedConfig {
46    /// Schemas under pgevolve's control.
47    pub schemas: Vec<crate::identifier::Identifier>,
48}
49
50/// Run universal rules + the configured profile against `inputs`.
51pub fn run(inputs: &LintInputs<'_>) -> Vec<Finding> {
52    let mut out = check_universal(inputs.tree, inputs.managed);
53    out.extend(check_profile(
54        inputs.profile,
55        inputs.tree,
56        inputs.schema_dir,
57    ));
58    out
59}
60
61impl SourceTree {
62    /// Parse `root` into a `SourceTree`.
63    pub fn parse(root: &Path, ignores: &[glob::Pattern]) -> Result<Self, crate::parse::ParseError> {
64        let (catalog, string_locations) =
65            crate::parse::parse_directory_with_locations(root, ignores)?;
66        let mut object_locations = std::collections::HashMap::new();
67        for s in &catalog.schemas {
68            if let Some(loc) = string_locations.get(&s.name.to_string()) {
69                object_locations.insert(ObjectKey::Schema(s.name.clone()), loc.clone());
70            }
71        }
72        for t in &catalog.tables {
73            if let Some(loc) = string_locations.get(&t.qname.to_string()) {
74                object_locations.insert(ObjectKey::Table(t.qname.clone()), loc.clone());
75            }
76        }
77        for i in &catalog.indexes {
78            if let Some(loc) = string_locations.get(&i.qname.to_string()) {
79                object_locations.insert(ObjectKey::Index(i.qname.clone()), loc.clone());
80            }
81        }
82        for seq in &catalog.sequences {
83            if let Some(loc) = string_locations.get(&seq.qname.to_string()) {
84                object_locations.insert(ObjectKey::Sequence(seq.qname.clone()), loc.clone());
85            }
86        }
87        Ok(Self::new(catalog, object_locations))
88    }
89}