Skip to main content

safe_migrate/report/
violations.rs

1// FILE: ./src/report/violations.rs
2
3#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
4pub enum OperationKind {
5    DropColumn,
6    DropTable,
7    DropIndex,
8    DropView,
9    DropMaterializedView,
10    DropFunction,
11    DropProcedure,
12    DropSchema,
13    DropDatabase,
14    DropSequence,
15    DropDomain,
16    DropType,
17    DropPublication,
18    DropTrigger,
19    DropPolicy,
20    AddColumn,
21    AlterColumnType,
22    AddConstraint,
23    CreateIndex,
24    CreateTable,
25    CreateView,
26    CreateFunction,
27    CreateProcedure,
28    AlterFunction,
29    AlterProcedure,
30    RefreshMaterializedView,
31    AttachPartition,
32    DetachPartition,
33    VacuumFull,
34    Grant,
35    RevokeGrant,
36    AlterType,
37    CreateTrigger,
38    CreatePolicy,
39    DisableTrigger,
40    EnableTrigger,
41    RenameTable,
42    RenameColumn,
43    Rename,
44    OpaqueSql,
45    CreateSchema,
46    SetDefault,
47    CreateSequence,
48    CreateDomain,
49    AlterSchema,
50    Conflict,
51    Irreversible,
52    UnresolvedReference,
53    Other(String),
54}
55
56impl std::fmt::Display for OperationKind {
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        match self {
59            OperationKind::DropColumn => write!(f, "drop_column"),
60            OperationKind::DropTable => write!(f, "drop_table"),
61            OperationKind::DropIndex => write!(f, "drop_index"),
62            OperationKind::DropView => write!(f, "drop_view"),
63            OperationKind::DropMaterializedView => write!(f, "drop_materialized_view"),
64            OperationKind::DropFunction => write!(f, "drop_function"),
65            OperationKind::DropProcedure => write!(f, "drop_procedure"),
66            OperationKind::DropSchema => write!(f, "drop_schema"),
67            OperationKind::DropDatabase => write!(f, "drop_database"),
68            OperationKind::DropSequence => write!(f, "drop_sequence"),
69            OperationKind::DropDomain => write!(f, "drop_domain"),
70            OperationKind::DropType => write!(f, "drop_type"),
71            OperationKind::DropPublication => write!(f, "drop_publication"),
72            OperationKind::DropTrigger => write!(f, "drop_trigger"),
73            OperationKind::DropPolicy => write!(f, "drop_policy"),
74            OperationKind::AddColumn => write!(f, "add_column"),
75            OperationKind::AlterColumnType => write!(f, "alter_column_type"),
76            OperationKind::AddConstraint => write!(f, "add_constraint"),
77            OperationKind::CreateIndex => write!(f, "create_index"),
78            OperationKind::CreateTable => write!(f, "create_table"),
79            OperationKind::CreateView => write!(f, "create_view"),
80            OperationKind::CreateFunction => write!(f, "create_function"),
81            OperationKind::CreateProcedure => write!(f, "create_procedure"),
82            OperationKind::AlterFunction => write!(f, "alter_function"),
83            OperationKind::AlterProcedure => write!(f, "alter_procedure"),
84            OperationKind::RefreshMaterializedView => write!(f, "refresh_materialized_view"),
85            OperationKind::AttachPartition => write!(f, "attach_partition"),
86            OperationKind::DetachPartition => write!(f, "detach_partition"),
87            OperationKind::VacuumFull => write!(f, "vacuum_full"),
88            OperationKind::Grant => write!(f, "grant"),
89            OperationKind::RevokeGrant => write!(f, "revoke_grant"),
90            OperationKind::AlterType => write!(f, "alter_type"),
91            OperationKind::CreateTrigger => write!(f, "create_trigger"),
92            OperationKind::CreatePolicy => write!(f, "create_policy"),
93            OperationKind::DisableTrigger => write!(f, "disable_trigger"),
94            OperationKind::EnableTrigger => write!(f, "enable_trigger"),
95            OperationKind::RenameTable => write!(f, "rename_table"),
96            OperationKind::RenameColumn => write!(f, "rename_column"),
97            OperationKind::Rename => write!(f, "rename"),
98            OperationKind::OpaqueSql => write!(f, "opaque_sql"),
99            OperationKind::CreateSchema => write!(f, "create_schema"),
100            OperationKind::SetDefault => write!(f, "set_default"),
101            OperationKind::CreateSequence => write!(f, "create_sequence"),
102            OperationKind::CreateDomain => write!(f, "create_domain"),
103            OperationKind::AlterSchema => write!(f, "alter_schema"),
104            OperationKind::Conflict => write!(f, "conflict"),
105            OperationKind::Irreversible => write!(f, "irreversible"),
106            OperationKind::UnresolvedReference => write!(f, "unresolved_reference"),
107            OperationKind::Other(s) => write!(f, "{}", s),
108        }
109    }
110}
111
112#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
113pub enum ObjectKind {
114    Table,
115    Index,
116    View,
117    MaterializedView,
118    Function,
119    Procedure,
120    Trigger,
121    Sequence,
122    Schema,
123    Role,
124    Publication,
125    Subscription,
126    Database,
127    Domain,
128    Policy,
129    Type,
130    Opaque,
131    Unknown,
132}
133
134impl std::fmt::Display for ObjectKind {
135    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136        match self {
137            ObjectKind::Table => write!(f, "table"),
138            ObjectKind::Index => write!(f, "index"),
139            ObjectKind::View => write!(f, "view"),
140            ObjectKind::MaterializedView => write!(f, "materialized view"),
141            ObjectKind::Function => write!(f, "function"),
142            ObjectKind::Procedure => write!(f, "procedure"),
143            ObjectKind::Trigger => write!(f, "trigger"),
144            ObjectKind::Sequence => write!(f, "sequence"),
145            ObjectKind::Schema => write!(f, "schema"),
146            ObjectKind::Role => write!(f, "role"),
147            ObjectKind::Publication => write!(f, "publication"),
148            ObjectKind::Subscription => write!(f, "subscription"),
149            ObjectKind::Database => write!(f, "database"),
150            ObjectKind::Domain => write!(f, "domain"),
151            ObjectKind::Policy => write!(f, "policy"),
152            ObjectKind::Type => write!(f, "type"),
153            ObjectKind::Opaque => write!(f, "opaque"),
154            ObjectKind::Unknown => write!(f, "object"),
155        }
156    }
157}
158
159/// ViolationTier represents the severity of a finding.
160/// Tier1 is declared first so `derive(Ord)` sorts it before Tier2 and Tier3.
161#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize)]
162pub enum ViolationTier {
163    Tier1, // HALT — Access Exclusive / data-destructive, sorts first
164    Tier2, // WARN — Share Row Exclusive / cautious
165    Tier3, // SAFE — informational / low risk, sorts last
166}
167
168#[derive(Debug, Clone, serde::Serialize)]
169pub struct Violation {
170    #[serde(skip)]
171    pub source_range: Option<rowan::TextRange>,
172    pub rule_id: &'static str,
173    pub operation_kind: OperationKind,
174    pub object_kind: ObjectKind,
175    pub object_name: String,
176    pub tier: ViolationTier,
177    pub reason: String,
178    pub recipe: &'static str,
179    pub dedup_key: Option<String>,
180    pub sql: Option<String>,
181    #[serde(default)]
182    pub fk_dependency_related: bool,
183}
184
185/// Stable source location attached at reporting time. Rules remain independent
186/// of file layout; the engine derives this from the parsed statement range.
187#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
188pub struct SourceLocation {
189    pub file: String,
190    pub line: usize,
191    pub column: usize,
192}
193
194/// A violation paired with the file and line that produced it. The flattened
195/// serialization keeps the JSON violation schema additive.
196#[derive(Debug, Clone, serde::Serialize)]
197pub struct ReportFinding {
198    #[serde(flatten)]
199    pub violation: Violation,
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub location: Option<SourceLocation>,
202    /// One-based statement position within the source file.
203    #[serde(skip_serializing_if = "Option::is_none")]
204    pub statement_index: Option<usize>,
205}