waypoint_core/commands/
safety.rs1use serde::Serialize;
4
5#[cfg(feature = "postgres")]
6use tokio_postgres::Client;
7
8use crate::config::WaypointConfig;
9use crate::db::DbClient;
10use crate::error::Result;
11use crate::safety;
12
13#[derive(Debug, Clone, Serialize)]
15pub struct SafetyCommandReport {
16 pub reports: Vec<safety::SafetyReport>,
18 pub overall_verdict: safety::SafetyVerdict,
20}
21
22#[cfg(feature = "postgres")]
24pub async fn execute_file(
25 client: &Client,
26 config: &WaypointConfig,
27 file_path: &str,
28) -> Result<safety::SafetyReport> {
29 let sql = std::fs::read_to_string(file_path)?;
30 let script = filename_from_path(file_path);
31 safety::analyze_migration(
32 client,
33 &config.migrations.schema,
34 &sql,
35 &script,
36 &config.safety,
37 )
38 .await
39}
40
41pub async fn execute_file_db(
43 client: &DbClient,
44 config: &WaypointConfig,
45 file_path: &str,
46) -> Result<safety::SafetyReport> {
47 let sql = std::fs::read_to_string(file_path)?;
48 let script = filename_from_path(file_path);
49 let schema = client.resolve_schema(&config.migrations.schema).await?;
50 safety::analyze_migration_db(client, &schema, &sql, &script, &config.safety).await
51}
52
53fn filename_from_path(file_path: &str) -> String {
54 std::path::Path::new(file_path)
55 .file_name()
56 .map(|f| f.to_string_lossy().to_string())
57 .unwrap_or_else(|| file_path.to_string())
58}
59
60#[cfg(feature = "postgres")]
62#[deprecated(
63 since = "0.6.0",
64 note = "Unused PostgreSQL-only entry point superseded by `execute_db`, which handles both engines. Will be removed in 1.0."
65)]
66pub async fn execute(client: &Client, config: &WaypointConfig) -> Result<SafetyCommandReport> {
67 use crate::history;
68 use crate::migration::scan_migrations;
69
70 let schema = &config.migrations.schema;
71 let table = &config.migrations.table;
72
73 history::create_history_table(client, schema, table).await?;
74 let resolved = scan_migrations(&config.migrations.locations)?;
75 let applied = history::get_applied_migrations(client, schema, table).await?;
76 let effective = history::effective_applied_versions(&applied);
77
78 let mut reports = Vec::new();
79 let mut overall = safety::SafetyVerdict::Safe;
80
81 for migration in &resolved {
82 if migration.is_undo() {
83 continue;
84 }
85 if let Some(version) = migration.version()
86 && effective.contains(&version.raw)
87 {
88 continue;
89 }
90
91 let report = safety::analyze_migration(
92 client,
93 schema,
94 &migration.sql,
95 &migration.script,
96 &config.safety,
97 )
98 .await?;
99
100 if report.overall_verdict > overall {
101 overall = report.overall_verdict;
102 }
103 reports.push(report);
104 }
105
106 Ok(SafetyCommandReport {
107 reports,
108 overall_verdict: overall,
109 })
110}
111
112pub async fn execute_db(client: &DbClient, config: &WaypointConfig) -> Result<SafetyCommandReport> {
114 use crate::history;
115 use crate::migration::scan_migrations;
116
117 let schema = client.resolve_schema(&config.migrations.schema).await?;
118 let table = &config.migrations.table;
119
120 history::create_history_table_db(client, &schema, table).await?;
121 let resolved = scan_migrations(&config.migrations.locations)?;
122 let applied = history::get_applied_migrations_db(client, &schema, table).await?;
123 let effective = history::effective_applied_versions(&applied);
124
125 let mut reports = Vec::new();
126 let mut overall = safety::SafetyVerdict::Safe;
127
128 for migration in &resolved {
129 if migration.is_undo() {
130 continue;
131 }
132 if let Some(version) = migration.version()
133 && effective.contains(&version.raw)
134 {
135 continue;
136 }
137
138 let report = safety::analyze_migration_db(
139 client,
140 &schema,
141 &migration.sql,
142 &migration.script,
143 &config.safety,
144 )
145 .await?;
146
147 if report.overall_verdict > overall {
148 overall = report.overall_verdict;
149 }
150 reports.push(report);
151 }
152
153 Ok(SafetyCommandReport {
154 reports,
155 overall_verdict: overall,
156 })
157}