diff --git a/src/sqlite.rs b/src/sqlite.rs index b3060be..30c5bdd 100644 --- a/src/sqlite.rs +++ b/src/sqlite.rs @@ -3,43 +3,115 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use thiserror::Error; -/// Actions are sent to a specific sqlite database, `db` is the name, -/// `package_id` is the [`PackageId`]. Capabilities are checked, you can access another process's -/// database if it has given you the [`crate::Capability`]. -#[derive(Debug, Serialize, Deserialize)] +/// Actions are sent to a specific SQLite database. `db` is the name, +/// `package_id` is the [`PackageId`] that created the database. Capabilities +/// are checked: you can access another process's database if it has given +/// you the read and/or write capability to do so. +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct SqliteRequest { pub package_id: PackageId, pub db: String, pub action: SqliteAction, } -#[derive(Debug, Serialize, Deserialize)] +/// IPC Action format representing operations that can be performed on the +/// SQLite runtime module. These actions are included in a [`SqliteRequest`] +/// sent to the `sqlite:distro:sys` runtime module. +#[derive(Clone, Debug, Serialize, Deserialize)] pub enum SqliteAction { + /// Opens an existing key-value database or creates a new one if it doesn't exist. + /// Requires `package_id` in [`SqliteRequest`] to match the package ID of the sender. + /// The sender will own the database and can remove it with [`SqliteAction::RemoveDb`]. + /// + /// A successful open will respond with [`SqliteResponse::Ok`]. Any error will be + /// contained in the [`SqliteResponse::Err`] variant. Open, + /// Permanently deletes the entire key-value database. + /// Requires `package_id` in [`SqliteRequest`] to match the package ID of the sender. + /// Only the owner can remove the database. + /// + /// A successful remove will respond with [`SqliteResponse::Ok`]. Any error will be + /// contained in the [`SqliteResponse::Err`] variant. RemoveDb, + /// Executes a write statement (INSERT/UPDATE/DELETE) + /// + /// * `statement` - SQL statement to execute + /// * `tx_id` - Optional transaction ID + /// * blob: Vec - Parameters for the SQL statement, where SqlValue can be: + /// - null + /// - boolean + /// - i64 + /// - f64 + /// - String + /// - Vec (binary data) + /// + /// Using this action requires the sender to have the write capability + /// for the database. + /// + /// A successful write will respond with [`SqliteResponse::Ok`]. Any error will be + /// contained in the [`SqliteResponse::Err`] variant. Write { statement: String, tx_id: Option, }, - Read { - query: String, - }, + /// Executes a read query (SELECT) + /// + /// * blob: Vec - Parameters for the SQL query, where SqlValue can be: + /// - null + /// - boolean + /// - i64 + /// - f64 + /// - String + /// - Vec (binary data) + /// + /// Using this action requires the sender to have the read capability + /// for the database. + /// + /// A successful query will respond with [`SqliteResponse::Query`], where the + /// response blob contains the results of the query. Any error will be contained + /// in the [`SqliteResponse::Err`] variant. + Query(String), + /// Begins a new transaction for atomic operations. + /// + /// Sending this will prompt a [`SqliteResponse::BeginTx`] response with the + /// transaction ID. Any error will be contained in the [`SqliteResponse::Err`] variant. BeginTx, - Commit { - tx_id: u64, - }, - Backup, + /// Commits all operations in the specified transaction. + /// + /// # Parameters + /// * `tx_id` - The ID of the transaction to commit + /// + /// A successful commit will respond with [`SqliteResponse::Ok`]. Any error will be + /// contained in the [`SqliteResponse::Err`] variant. + Commit { tx_id: u64 }, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub enum SqliteResponse { + /// Indicates successful completion of an operation. + /// Sent in response to actions Open, RemoveDb, Write, Query, BeginTx, and Commit. Ok, + /// Returns the results of a query. + /// + /// * blob: Vec> - Array of rows, where each row contains SqlValue types: + /// - null + /// - boolean + /// - i64 + /// - f64 + /// - String + /// - Vec (binary data) Read, + /// Returns the transaction ID for a newly created transaction. + /// + /// # Fields + /// * `tx_id` - The ID of the newly created transaction BeginTx { tx_id: u64 }, + /// Indicates an error occurred during the operation. Err(SqliteError), } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +/// Used in blobs to represent array row values in SQLite. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub enum SqlValue { Integer(i64), Real(f64), @@ -49,28 +121,50 @@ pub enum SqlValue { Null, } -#[derive(Debug, Serialize, Deserialize, Error)] +#[derive(Clone, Debug, Serialize, Deserialize, Error)] pub enum SqliteError { - #[error("sqlite: DbDoesNotExist")] - NoDb, - #[error("sqlite: NoTx")] - NoTx, - #[error("sqlite: No capability: {error}")] - NoCap { error: String }, - #[error("sqlite: UnexpectedResponse")] - UnexpectedResponse, - #[error("sqlite: NotAWriteKeyword")] + #[error("db [{0}, {1}] does not exist")] + NoDb(PackageId, String), + #[error("no transaction {0} found")] + NoTx(u64), + #[error("no write capability for requested DB")] + NoWriteCap, + #[error("no read capability for requested DB")] + NoReadCap, + #[error("request to open or remove DB with mismatching package ID")] + MismatchingPackageId, + #[error("failed to generate capability for new DB")] + AddCapFailed, + #[error("write statement started with non-existent write keyword")] NotAWriteKeyword, - #[error("sqlite: NotAReadKeyword")] + #[error("read query started with non-existent read keyword")] NotAReadKeyword, - #[error("sqlite: Invalid Parameters")] + #[error("parameters blob in read/write was misshapen or contained invalid JSON objects")] InvalidParameters, - #[error("sqlite: IO error: {error}")] - IOError { error: String }, - #[error("sqlite: rusqlite error: {error}")] - RusqliteError { error: String }, - #[error("sqlite: input bytes/json/key error: {error}")] - InputError { error: String }, + #[error("sqlite got a malformed request that failed to deserialize")] + MalformedRequest, + #[error("rusqlite error: {0}")] + RusqliteError(String), + #[error("IO error: {0}")] + IOError(String), +} + +/// The JSON parameters contained in all capabilities issued by `sqlite:distro:sys`. +/// +/// # Fields +/// * `kind` - The kind of capability, either [`SqliteCapabilityKind::Read`] or [`SqliteCapabilityKind::Write`] +/// * `db_key` - The database key, a tuple of the [`PackageId`] that created the database and the database name +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SqliteCapabilityParams { + pub kind: SqliteCapabilityKind, + pub db_key: (PackageId, String), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum SqliteCapabilityKind { + Read, + Write, } /// Sqlite helper struct for a db. @@ -95,7 +189,7 @@ impl Sqlite { .body(serde_json::to_vec(&SqliteRequest { package_id: self.package_id.clone(), db: self.db.clone(), - action: SqliteAction::Read { query }, + action: SqliteAction::Query(query), })?) .blob_bytes(serde_json::to_vec(¶ms)?) .send_and_await_response(self.timeout)?; @@ -106,15 +200,11 @@ impl Sqlite { match response { SqliteResponse::Read => { - let blob = get_blob().ok_or_else(|| SqliteError::InputError { - error: "sqlite: no blob".to_string(), - })?; + let blob = get_blob().ok_or_else(|| SqliteError::MalformedRequest)?; let values = serde_json::from_slice::< Vec>, >(&blob.bytes) - .map_err(|e| SqliteError::InputError { - error: format!("sqlite: gave unparsable response: {}", e), - })?; + .map_err(|_| SqliteError::MalformedRequest)?; Ok(values) } SqliteResponse::Err(error) => Err(error.into()),