1
use std::collections::{BTreeMap, BTreeSet};
2

            
3
use serde::{Deserialize, Serialize};
4
use thiserror::Error;
5

            
6
use crate::core::{
7
    database_privileges::{DatabasePrivilegeRow, DatabasePrivilegeRowDiff, DatabasePrivilegesDiff},
8
    protocol::request_validation::ValidationError,
9
    types::{DbOrUser, MySQLDatabase, MySQLUser},
10
};
11

            
12
pub type ModifyPrivilegesRequest = BTreeSet<DatabasePrivilegesDiff>;
13

            
14
pub type ModifyPrivilegesResponse =
15
    BTreeMap<MySQLDatabase, BTreeMap<MySQLUser, Result<(), ModifyDatabasePrivilegesError>>>;
16

            
17
#[derive(Error, Debug, Clone, PartialEq, Serialize, Deserialize)]
18
pub enum ModifyDatabasePrivilegesError {
19
    #[error("Database validation error: {0}")]
20
    DatabaseValidationError(ValidationError),
21

            
22
    #[error("User validation error: {0}")]
23
    UserValidationError(ValidationError),
24

            
25
    #[error("Database does not exist")]
26
    DatabaseDoesNotExist,
27

            
28
    #[error("User does not exist")]
29
    UserDoesNotExist,
30

            
31
    #[error("Diff does not apply: {0}")]
32
    DiffDoesNotApply(DiffDoesNotApplyError),
33

            
34
    #[error("MySQL error: {0}")]
35
    MySqlError(String),
36
}
37

            
38
#[allow(clippy::enum_variant_names)]
39
#[derive(Error, Debug, Clone, PartialEq, Serialize, Deserialize)]
40
pub enum DiffDoesNotApplyError {
41
    #[error("Privileges row already exists for database '{0}' and user '{1}'")]
42
    RowAlreadyExists(MySQLDatabase, MySQLUser),
43

            
44
    #[error("Privileges row does not exist for database '{0}' and user '{1}'")]
45
    RowDoesNotExist(MySQLDatabase, MySQLUser),
46

            
47
    #[error("Privilege change '{0:?}' does not apply to row '{1:?}'")]
48
    RowPrivilegeChangeDoesNotApply(DatabasePrivilegeRowDiff, DatabasePrivilegeRow),
49
}
50

            
51
pub fn print_modify_database_privileges_output_status(output: &ModifyPrivilegesResponse) {
52
    for ((database_name, username), result) in output.iter().flat_map(|(db, user_map)| {
53
        user_map
54
            .iter()
55
            .map(move |(user, result)| ((db, user), result))
56
    }) {
57
        match result {
58
            Ok(()) => {
59
                println!(
60
                    "Privileges for user '{username}' on database '{database_name}' modified successfully."
61
                );
62
            }
63
            Err(err) => {
64
                eprintln!("{}", err.to_error_message(database_name, username));
65
                eprintln!("Skipping...");
66
            }
67
        }
68
        println!();
69
    }
70
}
71

            
72
impl ModifyDatabasePrivilegesError {
73
    #[must_use]
74
    pub fn to_error_message(&self, database_name: &MySQLDatabase, username: &MySQLUser) -> String {
75
        match self {
76
            ModifyDatabasePrivilegesError::DatabaseValidationError(err) => {
77
                err.to_error_message(&DbOrUser::Database(database_name.clone()))
78
            }
79
            ModifyDatabasePrivilegesError::UserValidationError(err) => {
80
                err.to_error_message(&DbOrUser::User(username.clone()))
81
            }
82
            ModifyDatabasePrivilegesError::DatabaseDoesNotExist => {
83
                format!("Database '{database_name}' does not exist.")
84
            }
85
            ModifyDatabasePrivilegesError::UserDoesNotExist => {
86
                format!("User '{username}' does not exist.")
87
            }
88
            ModifyDatabasePrivilegesError::DiffDoesNotApply(diff) => {
89
                format!(
90
                    "Could not apply privilege change:\n{}",
91
                    diff.to_error_message()
92
                )
93
            }
94
            ModifyDatabasePrivilegesError::MySqlError(err) => {
95
                format!("MySQL error: {err}")
96
            }
97
        }
98
    }
99

            
100
    #[allow(dead_code)]
101
    #[must_use]
102
    pub fn error_type(&self) -> String {
103
        match self {
104
            ModifyDatabasePrivilegesError::DatabaseValidationError(err) => {
105
                err.error_type() + "/database"
106
            }
107
            ModifyDatabasePrivilegesError::UserValidationError(err) => err.error_type() + "/user",
108
            ModifyDatabasePrivilegesError::DatabaseDoesNotExist => {
109
                "database-does-not-exist".to_string()
110
            }
111
            ModifyDatabasePrivilegesError::UserDoesNotExist => "user-does-not-exist".to_string(),
112
            ModifyDatabasePrivilegesError::DiffDoesNotApply(err) => {
113
                format!("diff-does-not-apply/{}", err.error_type())
114
            }
115
            ModifyDatabasePrivilegesError::MySqlError(_) => "mysql-error".to_string(),
116
        }
117
    }
118
}
119

            
120
impl DiffDoesNotApplyError {
121
    #[must_use]
122
    pub fn to_error_message(&self) -> String {
123
        match self {
124
            DiffDoesNotApplyError::RowAlreadyExists(database_name, username) => {
125
                format!(
126
                    "Privileges for user '{username}' on database '{database_name}' already exist."
127
                )
128
            }
129
            DiffDoesNotApplyError::RowDoesNotExist(database_name, username) => {
130
                format!(
131
                    "Privileges for user '{username}' on database '{database_name}' do not exist."
132
                )
133
            }
134
            DiffDoesNotApplyError::RowPrivilegeChangeDoesNotApply(diff, row) => {
135
                format!("Could not apply privilege change {diff:?} to row {row:?}")
136
            }
137
        }
138
    }
139

            
140
    #[must_use]
141
    pub fn error_type(&self) -> String {
142
        match self {
143
            DiffDoesNotApplyError::RowAlreadyExists(_, _) => "row-already-exists".to_string(),
144
            DiffDoesNotApplyError::RowDoesNotExist(_, _) => "row-does-not-exist".to_string(),
145
            DiffDoesNotApplyError::RowPrivilegeChangeDoesNotApply(_, _) => {
146
                "row-privilege-change-does-not-apply".to_string()
147
            }
148
        }
149
    }
150
}
151

            
152
#[cfg(test)]
153
mod tests {
154
    use super::*;
155
    use crate::core::*;
156

            
157
    #[test]
158
1
    fn test_serialize_deserialize_request() {
159
1
        let request =
160
1
            BTreeSet::from([DatabasePrivilegesDiff::Modified(DatabasePrivilegeRowDiff {
161
1
                db: "test_db".into(),
162
1
                user: "test_user".into(),
163
1
                select_priv: Some(database_privileges::DatabasePrivilegeChange::NoToYes),
164
1
                ..Default::default()
165
1
            })]);
166

            
167
1
        let json = serde_json::to_string_pretty(&request).unwrap();
168
1
        println!("Serialized request:\n{}", json);
169

            
170
1
        let deserialized: ModifyPrivilegesRequest = serde_json::from_str(&json).unwrap();
171
1
        assert_eq!(request, deserialized);
172
1
    }
173

            
174
    #[test]
175
1
    fn test_serialize_deserialize_response() {
176
1
        let response: ModifyPrivilegesResponse = BTreeMap::from([(
177
1
            "test_db".into(),
178
1
            BTreeMap::from([
179
1
                ("test_user".into(), Ok(())),
180
1
                (
181
1
                    "invalid_user".into(),
182
1
                    Err(ModifyDatabasePrivilegesError::UserDoesNotExist),
183
1
                ),
184
1
            ]),
185
1
        )]);
186

            
187
1
        let json = serde_json::to_string_pretty(&response).unwrap();
188
1
        println!("Serialized response:\n{}", json);
189

            
190
1
        let deserialized: ModifyPrivilegesResponse = serde_json::from_str(&json).unwrap();
191
1
        assert_eq!(response, deserialized);
192
1
    }
193
}