refatored vault

This commit is contained in:
2025-09-24 11:32:11 +02:00
parent d5670ca470
commit 1a40f9d2aa
16 changed files with 2364 additions and 1481 deletions

View File

@ -1,48 +1,374 @@
mod permissions;
// src-tauri/src/extension/database/mod.rs
use crate::database;
use crate::database::DbConnection;
use crate::models::ExtensionState;
pub mod permissions;
use crate::crdt::hlc::HlcService;
use crate::crdt::transformer::CrdtTransformer;
use crate::crdt::trigger;
use crate::database::core::{parse_sql_statements, with_connection, ValueConverter};
use crate::database::error::DatabaseError;
use crate::database::AppState;
use permissions::{check_read_permission, check_write_permission, PermissionError};
use rusqlite::params_from_iter;
use rusqlite::types::Value as SqlValue;
use rusqlite::Transaction;
use serde_json::json;
use serde_json::Value as JsonValue;
use std::collections::HashMap;
use tauri::{AppHandle, State};
// Extension-bezogene Funktionen mit extension_-Präfix
/// Lädt eine Extension aus einer Manifest-Datei
/* #[tauri::command]
pub fn extension_load(
manifest_path: String,
app: AppHandle,
) -> Result<crate::models::ExtensionManifest, String> {
let manifest_content = std::fs::read_to_string(&manifest_path).map_err(|e| e.to_string())?;
let manifest: crate::models::ExtensionManifest =
serde_json::from_str(&manifest_content).map_err(|e| e.to_string())?;
app.state::<ExtensionState>()
.add_extension(manifest_path.clone(), manifest.clone());
Ok(manifest)
}
*/
/// Führt SQL-Leseoperationen mit Berechtigungsprüfung aus
#[tauri::command]
pub async fn extension_sql_select(
app: AppHandle,
extension_id: String,
sql: String,
params: Vec<JsonValue>,
state: State<'_, DbConnection>,
) -> Result<Vec<Vec<JsonValue>>, String> {
permissions::check_read_permission(&app, &extension_id, &sql).await?;
database::core::select(sql, params, &state).await
use sqlparser::ast::{Statement, TableFactor, TableObject};
use std::collections::HashSet;
use tauri::State;
use thiserror::Error;
/// Combined error type für Extension-Database operations
#[derive(Error, Debug)]
pub enum ExtensionDatabaseError {
#[error("Permission denied: {source}")]
Permission {
#[from]
source: PermissionError,
},
#[error("Database error: {source}")]
Database {
#[from]
source: DatabaseError,
},
#[error("Parameter validation failed: {reason}")]
ParameterValidation { reason: String },
#[error("Statement execution failed: {reason}")]
StatementExecution { reason: String },
}
// Für Tauri Command Serialization
impl serde::Serialize for ExtensionDatabaseError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
/// Führt Statements mit korrekter Parameter-Bindung aus
pub struct StatementExecutor<'a> {
transaction: &'a Transaction<'a>,
hlc_service: &'a HlcService,
}
impl<'a> StatementExecutor<'a> {
fn new(transaction: &'a Transaction<'a>, hlc_service: &'a HlcService) -> Self {
Self {
transaction,
hlc_service,
}
}
/// Führt ein einzelnes Statement mit Parametern aus
fn execute_statement_with_params(
&self,
statement: &Statement,
params: &[SqlValue],
) -> Result<(), ExtensionDatabaseError> {
let sql = statement.to_string();
let expected_params = count_sql_placeholders(&sql);
if expected_params != params.len() {
return Err(ExtensionDatabaseError::ParameterValidation {
reason: format!(
"Parameter count mismatch for statement: {} (expected: {}, provided: {})",
truncate_sql(&sql, 100),
expected_params,
params.len()
),
});
}
self.transaction
.execute(&sql, params_from_iter(params.iter()))
.map_err(|e| ExtensionDatabaseError::StatementExecution {
reason: format!(
"Failed to execute statement on table {}: {}",
self.extract_table_name_from_statement(statement)
.unwrap_or_else(|| "unknown".to_string()),
e
),
})?;
Ok(())
}
/// Extrahiert den Tabellennamen aus einem Statement für bessere Fehlermeldungen
fn extract_table_name_from_statement(&self, statement: &Statement) -> Option<String> {
match statement {
Statement::Insert(insert) => {
if let TableObject::TableName(name) = &insert.table {
Some(name.to_string())
} else {
None
}
}
Statement::Update { table, .. } => {
if let TableFactor::Table { name, .. } = &table.relation {
Some(name.to_string())
} else {
None
}
}
Statement::Delete(delete) => {
// Verbessertes Extrahieren für DELETE
use sqlparser::ast::FromTable;
match &delete.from {
FromTable::WithFromKeyword(tables) | FromTable::WithoutKeyword(tables) => {
if !tables.is_empty() {
if let TableFactor::Table { name, .. } = &tables[0].relation {
Some(name.to_string())
} else {
None
}
} else if !delete.tables.is_empty() {
Some(delete.tables[0].to_string())
} else {
None
}
}
}
}
Statement::CreateTable(create) => Some(create.name.to_string()),
Statement::AlterTable { name, .. } => Some(name.to_string()),
Statement::Drop { names, .. } => names.first().map(|name| name.to_string()),
_ => None,
}
}
}
/// Führt SQL-Schreiboperationen mit Berechtigungsprüfung aus
#[tauri::command]
pub async fn extension_sql_execute(
app: AppHandle,
extension_id: String,
sql: String,
sql: &str,
params: Vec<JsonValue>,
state: State<'_, DbConnection>,
) -> Result<usize, String> {
permissions::check_write_permission(&app, &extension_id, &sql).await?;
database::core::execute(sql, params, &state).await
extension_id: String,
state: State<'_, AppState>,
hlc_service: State<'_, HlcService>,
) -> Result<Vec<String>, ExtensionDatabaseError> {
// Permission check
check_write_permission(&state.db, &extension_id, sql).await?;
// Parameter validation
validate_params(sql, &params)?;
// SQL parsing
let mut ast_vec = parse_sql_statements(sql)?;
// Database operation
with_connection(&state.db, |conn| {
let tx = conn.transaction().map_err(DatabaseError::from)?;
let transformer = CrdtTransformer::new();
let executor = StatementExecutor::new(&tx, &hlc_service);
// Generate HLC timestamp
let hlc_timestamp =
hlc_service
.new_timestamp_and_persist(&tx)
.map_err(|e| DatabaseError::HlcError {
reason: e.to_string(),
})?;
// Transform statements
let mut modified_schema_tables = HashSet::new();
for statement in &mut ast_vec {
if let Some(table_name) =
transformer.transform_execute_statement(statement, &hlc_timestamp)?
{
modified_schema_tables.insert(table_name);
}
}
// Convert parameters
let sql_values = ValueConverter::convert_params(&params)?;
// Execute statements
for statement in ast_vec {
executor.execute_statement_with_params(&statement, &sql_values)?;
if let Statement::CreateTable(create_table_details) = statement {
let table_name_str = create_table_details.name.to_string();
println!(
"Table '{}' created by extension, setting up CRDT triggers...",
table_name_str
);
trigger::setup_triggers_for_table(&tx, &table_name_str, false)?;
println!(
"Triggers for table '{}' successfully created.",
table_name_str
);
}
}
// Commit transaction
tx.commit().map_err(DatabaseError::from)?;
Ok(modified_schema_tables.into_iter().collect())
})
.map_err(ExtensionDatabaseError::from)
}
#[tauri::command]
pub async fn extension_sql_select(
sql: &str,
params: Vec<JsonValue>,
extension_id: String,
state: State<'_, AppState>,
) -> Result<Vec<JsonValue>, ExtensionDatabaseError> {
// Permission check
check_read_permission(&state.db, &extension_id, sql).await?;
// Parameter validation
validate_params(sql, &params)?;
// SQL parsing
let mut ast_vec = parse_sql_statements(sql)?;
if ast_vec.is_empty() {
return Ok(vec![]);
}
// Validate that all statements are queries
for stmt in &ast_vec {
if !matches!(stmt, Statement::Query(_)) {
return Err(ExtensionDatabaseError::StatementExecution {
reason: "Only SELECT statements are allowed in extension_sql_select".to_string(),
});
}
}
// Database operation
with_connection(&state.db, |conn| {
let sql_params = ValueConverter::convert_params(&params)?;
let transformer = CrdtTransformer::new();
// Use the last statement for result set
let last_statement = ast_vec.pop().unwrap();
let mut stmt_to_execute = last_statement;
// Transform the statement
transformer.transform_select_statement(&mut stmt_to_execute)?;
let transformed_sql = stmt_to_execute.to_string();
// Prepare and execute query
let mut prepared_stmt =
conn.prepare(&transformed_sql)
.map_err(|e| DatabaseError::ExecutionError {
sql: transformed_sql.clone(),
reason: e.to_string(),
table: None,
})?;
let column_names: Vec<String> = prepared_stmt
.column_names()
.into_iter()
.map(|s| s.to_string())
.collect();
let rows = prepared_stmt
.query_map(params_from_iter(sql_params.iter()), |row| {
row_to_json_value(row, &column_names)
})
.map_err(|e| DatabaseError::QueryError {
reason: e.to_string(),
})?;
let mut results = Vec::new();
for row_result in rows {
results.push(row_result.map_err(|e| DatabaseError::RowProcessingError {
reason: e.to_string(),
})?);
}
Ok(results)
})
.map_err(ExtensionDatabaseError::from)
}
/// Konvertiert eine SQLite-Zeile zu JSON
fn row_to_json_value(
row: &rusqlite::Row,
columns: &[String],
) -> Result<JsonValue, rusqlite::Error> {
let mut map = serde_json::Map::new();
for (i, col_name) in columns.iter().enumerate() {
let value = row.get::<usize, rusqlite::types::Value>(i)?;
let json_value = match value {
rusqlite::types::Value::Null => JsonValue::Null,
rusqlite::types::Value::Integer(i) => json!(i),
rusqlite::types::Value::Real(f) => json!(f),
rusqlite::types::Value::Text(s) => json!(s),
rusqlite::types::Value::Blob(blob) => json!(blob.to_vec()),
};
map.insert(col_name.clone(), json_value);
}
Ok(JsonValue::Object(map))
}
/// Validiert Parameter gegen SQL-Platzhalter
fn validate_params(sql: &str, params: &[JsonValue]) -> Result<(), ExtensionDatabaseError> {
let total_placeholders = count_sql_placeholders(sql);
if total_placeholders != params.len() {
return Err(ExtensionDatabaseError::ParameterValidation {
reason: format!(
"Parameter count mismatch: SQL has {} placeholders but {} parameters provided",
total_placeholders,
params.len()
),
});
}
Ok(())
}
/// Zählt SQL-Platzhalter (verbesserte Version)
fn count_sql_placeholders(sql: &str) -> usize {
sql.matches('?').count()
}
/// Kürzt SQL für Fehlermeldungen
fn truncate_sql(sql: &str, max_length: usize) -> String {
if sql.len() <= max_length {
sql.to_string()
} else {
format!("{}...", &sql[..max_length])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_count_sql_placeholders() {
assert_eq!(
count_sql_placeholders("SELECT * FROM users WHERE id = ?"),
1
);
assert_eq!(
count_sql_placeholders("SELECT * FROM users WHERE id = ? AND name = ?"),
2
);
assert_eq!(count_sql_placeholders("SELECT * FROM users"), 0);
}
#[test]
fn test_truncate_sql() {
let sql = "SELECT * FROM very_long_table_name";
assert_eq!(truncate_sql(sql, 10), "SELECT * F...");
assert_eq!(truncate_sql(sql, 50), sql);
}
#[test]
fn test_validate_params() {
let params = vec![json!(1), json!("test")];
assert!(validate_params("SELECT * FROM users WHERE id = ? AND name = ?", &params).is_ok());
assert!(validate_params("SELECT * FROM users WHERE id = ?", &params).is_err());
assert!(validate_params("SELECT * FROM users", &params).is_err());
}
}

View File

@ -1,160 +1,206 @@
// database/permissions.rs
use crate::database::core::extract_tables_from_query;
// src-tauri/src/extension/database/permissions.rs
use crate::database::core::{
extract_table_names_from_sql, parse_single_statement, with_connection,
};
use crate::database::error::DatabaseError;
use crate::database::DbConnection;
use crate::models::DbExtensionPermission;
use sqlparser::dialect::SQLiteDialect;
use sqlparser::parser::Parser;
use tauri::{AppHandle, Manager};
use serde::{Deserialize, Serialize};
use sqlparser::ast::{Statement, TableFactor, TableObject};
use thiserror::Error;
/// Prüft Leseberechtigungen für eine Extension basierend auf Datenbankeinträgen
pub async fn check_read_permission(
app: &AppHandle,
extension_id: &str,
sql: &str,
) -> Result<(), String> {
// SQL-Statement parsen
let dialect = SQLiteDialect {};
let statements = Parser::parse_sql(&dialect, sql).map_err(|e| e.to_string())?;
let statement = statements
.into_iter()
.next()
.ok_or("Keine SQL-Anweisung gefunden")?;
#[derive(Error, Debug, Serialize, Deserialize)]
pub enum PermissionError {
#[error("Extension '{extension_id}' has no {operation} permission for {resource}: {reason}")]
AccessDenied {
extension_id: String,
operation: String,
resource: String,
reason: String,
},
#[error("Database error while checking permissions: {source}")]
Database {
#[from]
source: DatabaseError,
},
#[error("SQL parsing error: {reason}")]
SqlParse { reason: String },
#[error("Invalid SQL statement: {reason}")]
InvalidStatement { reason: String },
#[error("No SQL statement found")]
NoStatement,
#[error("Unsupported statement type for permission check")]
UnsupportedStatement,
#[error("No table specified in {statement_type} statement")]
NoTableSpecified { statement_type: String },
}
// Berechtigungsprüfung für SELECT-Statements
if let sqlparser::ast::Statement::Query(query) = statement {
let tables = extract_tables_from_query(&query);
// Berechtigungen aus der Datenbank abrufen
let db_state = app.state::<DbConnection>();
let permissions =
get_extension_permissions(db_state, extension_id, "database", "read").await?;
// Prüfen, ob alle benötigten Tabellen in den Berechtigungen enthalten sind
for table in tables {
let has_permission = permissions.iter().any(|perm| perm.path.contains(&table));
if !has_permission {
return Err(format!("Keine Leseberechtigung für Tabelle {}", table));
}
// Hilfsfunktion für bessere Lesbarkeit
impl PermissionError {
pub fn access_denied(
extension_id: &str,
operation: &str,
resource: &str,
reason: &str,
) -> Self {
Self::AccessDenied {
extension_id: extension_id.to_string(),
operation: operation.to_string(),
resource: resource.to_string(),
reason: reason.to_string(),
}
Ok(())
} else {
Err("Nur SELECT-Anweisungen erlaubt".into())
}
}
/// Prüft Schreibberechtigungen für eine Extension basierend auf Datenbankeinträgen
pub async fn check_write_permission(
app: &AppHandle,
/// Prüft Leseberechtigungen für eine Extension
pub async fn check_read_permission(
connection: &DbConnection,
extension_id: &str,
sql: &str,
) -> Result<(), String> {
// SQL-Statement parsen
let dialect = SQLiteDialect {};
let statements = Parser::parse_sql(&dialect, sql).map_err(|e| e.to_string())?;
let statement = statements
.into_iter()
.next()
.ok_or("Keine SQL-Anweisung gefunden")?;
) -> Result<(), PermissionError> {
let statement = parse_single_statement(sql).map_err(|e| PermissionError::SqlParse {
reason: e.to_string(),
})?;
// Berechtigungsprüfung basierend auf Statement-Typ
match statement {
sqlparser::ast::Statement::Insert(insert) => {
let table_name = match insert.table {
sqlparser::ast::TableObject::TableName(name) => name.to_string(),
_ => return Err("Ungültige Tabellenangabe in INSERT".into()),
};
// Berechtigungen aus der Datenbank abrufen
let db_state = app.state::<DbConnection>();
let permissions =
get_extension_permissions(db_state, extension_id, "database", "write").await?;
// Prüfen, ob die Tabelle in den Berechtigungen enthalten ist
let has_permission = permissions
.iter()
.any(|perm| perm.path.contains(&table_name));
if !has_permission {
return Err(format!(
"Keine Schreibberechtigung für Tabelle {}",
table_name
));
}
Statement::Query(query) => {
let tables = extract_table_names_from_sql(&query.to_string())?;
check_table_permissions(connection, extension_id, &tables, "read").await
}
sqlparser::ast::Statement::Update { table, .. } => {
let table_name = table.relation.to_string();
_ => Err(PermissionError::InvalidStatement {
reason: "Only SELECT statements are allowed for read operations".to_string(),
}),
}
}
// Berechtigungen aus der Datenbank abrufen
let db_state = app.state::<DbConnection>();
let permissions =
get_extension_permissions(db_state, extension_id, "database", "write").await?;
/// Prüft Schreibberechtigungen für eine Extension
pub async fn check_write_permission(
connection: &DbConnection,
extension_id: &str,
sql: &str,
) -> Result<(), PermissionError> {
let statement = parse_single_statement(sql).map_err(|e| PermissionError::SqlParse {
reason: e.to_string(),
})?;
// Prüfen, ob die Tabelle in den Berechtigungen enthalten ist
let has_permission = permissions
.iter()
.any(|perm| perm.path.contains(&table_name));
if !has_permission {
return Err(format!(
"Keine Schreibberechtigung für Tabelle {}",
table_name
));
}
match statement {
Statement::Insert(insert) => {
let table_name = extract_table_name_from_insert(&insert)?;
check_single_table_permission(connection, extension_id, &table_name, "write").await
}
sqlparser::ast::Statement::Delete(delete) => {
let from_tables = match delete.from {
sqlparser::ast::FromTable::WithFromKeyword(tables) => tables,
sqlparser::ast::FromTable::WithoutKeyword(tables) => tables,
};
if from_tables.is_empty() && delete.tables.is_empty() {
return Err("Keine Tabelle in DELETE angegeben".into());
}
let table_name = if !from_tables.is_empty() {
from_tables[0].relation.to_string()
} else {
delete.tables[0].to_string()
};
// Berechtigungen aus der Datenbank abrufen
let db_state = app.state::<DbConnection>();
let permissions =
get_extension_permissions(db_state, extension_id, "database", "write").await?;
// Prüfen, ob die Tabelle in den Berechtigungen enthalten ist
let has_permission = permissions
.iter()
.any(|perm| perm.path.contains(&table_name));
if !has_permission {
return Err(format!(
"Keine Schreibberechtigung für Tabelle {}",
table_name
));
}
Statement::Update { table, .. } => {
let table_name = extract_table_name_from_table_factor(&table.relation)?;
check_single_table_permission(connection, extension_id, &table_name, "write").await
}
sqlparser::ast::Statement::CreateTable(create_table) => {
Statement::Delete(delete) => {
// DELETE wird durch CRDT-Transform zu UPDATE mit tombstone = 1
let table_name = extract_table_name_from_delete(&delete)?;
check_single_table_permission(connection, extension_id, &table_name, "write").await
}
Statement::CreateTable(create_table) => {
let table_name = create_table.name.to_string();
check_single_table_permission(connection, extension_id, &table_name, "create").await
}
Statement::AlterTable { name, .. } => {
let table_name = name.to_string();
check_single_table_permission(connection, extension_id, &table_name, "alter").await
}
Statement::Drop { names, .. } => {
// Für DROP können mehrere Tabellen angegeben sein
let table_names: Vec<String> = names.iter().map(|name| name.to_string()).collect();
check_table_permissions(connection, extension_id, &table_names, "drop").await
}
_ => Err(PermissionError::UnsupportedStatement),
}
}
// Berechtigungen aus der Datenbank abrufen
let db_state = app.state::<DbConnection>();
let permissions =
get_extension_permissions(db_state, extension_id, "database", "create").await?;
/// Extrahiert Tabellenname aus INSERT-Statement
fn extract_table_name_from_insert(
insert: &sqlparser::ast::Insert,
) -> Result<String, PermissionError> {
match &insert.table {
TableObject::TableName(name) => Ok(name.to_string()),
_ => Err(PermissionError::NoTableSpecified {
statement_type: "INSERT".to_string(),
}),
}
}
// Prüfen, ob die Tabelle in den Berechtigungen enthalten ist
let has_permission = permissions
.iter()
.any(|perm| perm.path.contains(&table_name));
/// Extrahiert Tabellenname aus TableFactor
fn extract_table_name_from_table_factor(
table_factor: &TableFactor,
) -> Result<String, PermissionError> {
match table_factor {
TableFactor::Table { name, .. } => Ok(name.to_string()),
_ => Err(PermissionError::InvalidStatement {
reason: "Complex table references not supported".to_string(),
}),
}
}
if !has_permission {
return Err(format!(
"Keine Erstellungsberechtigung für Tabelle {}",
table_name
));
/// Extrahiert Tabellenname aus DELETE-Statement
fn extract_table_name_from_delete(
delete: &sqlparser::ast::Delete,
) -> Result<String, PermissionError> {
use sqlparser::ast::FromTable;
let table_name = match &delete.from {
FromTable::WithFromKeyword(tables) | FromTable::WithoutKeyword(tables) => {
if !tables.is_empty() {
extract_table_name_from_table_factor(&tables[0].relation)?
} else if !delete.tables.is_empty() {
delete.tables[0].to_string()
} else {
return Err(PermissionError::NoTableSpecified {
statement_type: "DELETE".to_string(),
});
}
}
_ => return Err("Nur Schreiboperationen erlaubt (nutze 'select' für Abfragen)".into()),
};
Ok(table_name)
}
/// Prüft Berechtigung für eine einzelne Tabelle
async fn check_single_table_permission(
connection: &DbConnection,
extension_id: &str,
table_name: &str,
operation: &str,
) -> Result<(), PermissionError> {
check_table_permissions(
connection,
extension_id,
&[table_name.to_string()],
operation,
)
.await
}
/// Prüft Berechtigungen für mehrere Tabellen
async fn check_table_permissions(
connection: &DbConnection,
extension_id: &str,
table_names: &[String],
operation: &str,
) -> Result<(), PermissionError> {
let permissions =
get_extension_permissions(connection, extension_id, "database", operation).await?;
for table_name in table_names {
let has_permission = permissions
.iter()
.any(|perm| perm.path.contains(table_name));
if !has_permission {
return Err(PermissionError::access_denied(
extension_id,
operation,
&format!("table '{}'", table_name),
"Table not in permitted resources",
));
}
}
Ok(())
@ -162,42 +208,98 @@ pub async fn check_write_permission(
/// Ruft die Berechtigungen einer Extension aus der Datenbank ab
async fn get_extension_permissions(
db_state: tauri::State<'_, DbConnection>,
connection: &DbConnection,
extension_id: &str,
resource: &str,
operation: &str,
) -> Result<Vec<DbExtensionPermission>, String> {
let db = db_state
.0
.lock()
.map_err(|e| format!("Mutex-Fehler: {}", e))?;
) -> Result<Vec<DbExtensionPermission>, DatabaseError> {
with_connection(connection, |conn| {
let mut stmt = conn
.prepare(
"SELECT id, extension_id, resource, operation, path
FROM haex_vault_extension_permissions
WHERE extension_id = ?1 AND resource = ?2 AND operation = ?3",
)
.map_err(|e| DatabaseError::PrepareError {
reason: e.to_string(),
})?;
let conn = db.as_ref().ok_or("Keine Datenbankverbindung vorhanden")?;
let mut stmt = conn
.prepare(
"SELECT id, extension_id, resource, operation, path
FROM haex_vault_extension_permissions
WHERE extension_id = ? AND resource = ? AND operation = ?",
)
.map_err(|e| format!("SQL-Vorbereitungsfehler: {}", e))?;
let rows = stmt
.query_map(&[extension_id, resource, operation], |row| {
Ok(DbExtensionPermission {
id: row.get(0)?,
extension_id: row.get(1)?,
resource: row.get(2)?,
operation: row.get(3)?,
path: row.get(4)?,
let rows = stmt
.query_map([extension_id, resource, operation], |row| {
Ok(DbExtensionPermission {
id: row.get(0)?,
extension_id: row.get(1)?,
resource: row.get(2)?,
operation: row.get(3)?,
path: row.get(4)?,
})
})
})
.map_err(|e| format!("SQL-Abfragefehler: {}", e))?;
.map_err(|e| DatabaseError::QueryError {
reason: e.to_string(),
})?;
let mut permissions = Vec::new();
for row in rows {
permissions.push(row.map_err(|e| format!("Fehler beim Lesen der Berechtigungen: {}", e))?);
let mut permissions = Vec::new();
for row_result in rows {
let permission = row_result.map_err(|e| DatabaseError::PermissionError {
extension_id: extension_id.to_string(),
operation: Some(operation.to_string()),
resource: Some(resource.to_string()),
reason: e.to_string(),
})?;
permissions.push(permission);
}
Ok(permissions)
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_single_statement() {
let sql = "SELECT * FROM users";
let result = parse_single_statement(sql);
assert!(result.is_ok());
assert!(matches!(result.unwrap(), Statement::Query(_)));
}
Ok(permissions)
#[test]
fn test_parse_invalid_sql() {
let sql = "INVALID SQL";
let result = parse_single_statement(sql);
// parse_single_statement gibt DatabaseError zurück, nicht PermissionError
assert!(result.is_err());
// Wenn du spezifischer sein möchtest, kannst du den DatabaseError-Typ prüfen:
match result {
Err(DatabaseError::ParseError { .. }) => {
// Test erfolgreich - wir haben einen ParseError erhalten
}
Err(other) => {
// Andere DatabaseError-Varianten sind auch akzeptabel für ungültiges SQL
println!("Received other DatabaseError: {:?}", other);
}
Ok(_) => panic!("Expected error for invalid SQL"),
}
}
#[test]
fn test_permission_error_access_denied() {
let error = PermissionError::access_denied("ext1", "read", "table1", "not allowed");
match error {
PermissionError::AccessDenied {
extension_id,
operation,
resource,
reason,
} => {
assert_eq!(extension_id, "ext1");
assert_eq!(operation, "read");
assert_eq!(resource, "table1");
assert_eq!(reason, "not allowed");
}
_ => panic!("Expected AccessDenied error"),
}
}
}

View File

@ -1,8 +1,2 @@
pub mod core;
pub mod database;
use tauri;
#[tauri::command]
pub async fn copy_directory(source: String, destination: String) -> Result<(), String> {
core::copy_directory(source, destination)
}