Skip to content

Commit

Permalink
Merge pull request #104 from xsnippet/crud-storage
Browse files Browse the repository at this point in the history
The initial implementation of the storage layer
  • Loading branch information
malor committed Aug 30, 2020
2 parents 4ff54e4 + 66f16ce commit 543cdb7
Show file tree
Hide file tree
Showing 10 changed files with 922 additions and 7 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
sudo -u postgres createuser -s devel
sudo -u postgres createdb -O devel devel
sudo -u postgres psql -c "ALTER USER devel PASSWORD 'devel';" devel
echo '::set-env name=DATABASE_URL::postgres://devel:devel@localhost/devel'
echo '::set-env name=ROCKET_DATABASE_URL::postgres://devel:devel@localhost/devel'
- name: Start PostgreSQL and create a database (MacOS)
if: runner.os == 'macOS'
Expand All @@ -81,7 +81,7 @@ jobs:
/usr/local/opt/postgres/bin/createuser -s devel
/usr/local/opt/postgres/bin/createdb -O devel devel
/usr/local/opt/postgres/bin/psql -c "ALTER USER devel PASSWORD 'devel';" devel
echo '::set-env name=DATABASE_URL::postgres://devel:devel@localhost/devel'
echo '::set-env name=ROCKET_DATABASE_URL::postgres://devel:devel@localhost/devel'
- name: Start PostgreSQL and create a database (Windows)
if: runner.os == 'Windows'
Expand All @@ -95,7 +95,7 @@ jobs:
"C:\Program Files\PostgreSQL\12\bin\createuser" -s devel
"C:\Program Files\PostgreSQL\12\bin\createdb" -O devel devel
"C:\Program Files\PostgreSQL\12\bin\psql" -c "ALTER USER devel PASSWORD 'devel';" devel
echo '::set-env name=DATABASE_URL::postgres://devel:devel@localhost/devel'
echo '::set-env name=ROCKET_DATABASE_URL::postgres://devel:devel@localhost/devel'
- name: Install Alembic and psycopg2
run: |
Expand Down
79 changes: 79 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ publish = false

[dependencies]
chrono = "0.4.13"
diesel = { version = "1.4.5", features = ["postgres"] }
diesel = { version = "1.4.5", features = ["chrono", "postgres", "r2d2"] }
rand = "0.7.3"
rocket = "0.4.5"
rocket_contrib = {version = "0.4.5", features = ["json"]}
8 changes: 8 additions & 0 deletions src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::collections::HashSet;
use std::error::Error;

use super::routes;
use super::storage::{SqlStorage, Storage};

#[derive(Debug)]
pub struct Config {
Expand Down Expand Up @@ -34,7 +35,14 @@ pub fn create_app() -> Result<rocket::Rocket, Box<dyn Error>> {
Err(err) => return Err(Box::new(err)),
};

let database_url = match app.config().get_string("database_url") {
Ok(database_url) => database_url,
Err(err) => return Err(Box::new(err)),
};
let storage: Box<dyn Storage> = Box::new(SqlStorage::new(&database_url)?);

Ok(app
.manage(Config { syntaxes })
.manage(storage)
.mount("/v1", routes![routes::syntaxes::get_syntaxes]))
}
63 changes: 63 additions & 0 deletions src/storage/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use std::error;
use std::fmt;

/// All errors that can be returned by the storage layer wrapped into a single
/// enum type for convinience.
#[derive(Debug)]
pub enum StorageError {
/// Snippet with this id already exists in the database
Duplicate { id: String },
/// Snippet with this id can't be found
NotFound { id: String },
/// All other errors that can't be handled by the storage layer
InternalError(Box<dyn error::Error>),
}

impl fmt::Display for StorageError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
StorageError::Duplicate { id } => write!(f, "Snippet with id `{}` already exists", id),
StorageError::NotFound { id } => write!(f, "Snippet with id `{}` is not found", id),
StorageError::InternalError(e) => write!(f, "Internal error: `{}`", e),
}
}
}

// this is not strictly required, but allows for wrapping a StorageError
// instance into a Box<dyn Error> if needed. Error's only requirement is for
// a type to implement Debug + Display
impl error::Error for StorageError {}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_display() {
assert_eq!(
"Snippet with id `spam` already exists",
format!(
"{}",
StorageError::Duplicate {
id: "spam".to_string()
}
)
);

assert_eq!(
"Snippet with id `eggs` is not found",
format!(
"{}",
StorageError::NotFound {
id: "eggs".to_string()
}
)
);

let error = "foo".parse::<f32>().err().unwrap();
assert_eq!(
"Internal error: `invalid float literal`",
format!("{}", StorageError::InternalError(Box::new(error)))
);
}
}
28 changes: 27 additions & 1 deletion src/storage/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,27 @@
pub mod sql;
mod errors;
mod models;
mod sql;

pub use errors::StorageError;
pub use models::{Changeset, Snippet};
pub use sql::SqlStorage;

/// CRUD interface for storing/loading snippets from a persistent storage.
///
/// Types implementing this trait are required to be both Send and Sync, so
/// that their instances can be safely shared between multiple threads.
pub trait Storage: Send + Sync {
/// Save the state of the given snippet to the persistent storage.
fn create(&self, snippet: &Snippet) -> Result<Snippet, StorageError>;

/// Returns the snippet uniquely identified by a given id (a slug or a
/// legacy numeric id)
fn get(&self, id: &str) -> Result<Snippet, StorageError>;

/// Update the state of the given snippet in the persistent storage
fn update(&self, snippet: &Snippet) -> Result<Snippet, StorageError>;

/// Delete the snippet uniquely identified by a given id (a slug or a legacy
/// numeric id)
fn delete(&self, id: &str) -> Result<(), StorageError>;
}

0 comments on commit 543cdb7

Please sign in to comment.