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