Skip to content

Commit

Permalink
feat: add precondition check middleware and handler headers
Browse files Browse the repository at this point in the history
Most handler functions now have the appropriate header added. The
precondition middleware returns the request early if the modified
header and database differ.

Closes #78
  • Loading branch information
bbangert committed Nov 10, 2018
1 parent e96608b commit b066fba
Show file tree
Hide file tree
Showing 10 changed files with 293 additions and 121 deletions.
2 changes: 2 additions & 0 deletions src/db/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ impl Db for MockDb {
mock_db_method!(lock_for_read, LockCollection);
mock_db_method!(lock_for_write, LockCollection);
mock_db_method!(get_collection_modifieds, GetCollectionModifieds);
mock_db_method!(get_collection_modified, GetCollectionModified);
mock_db_method!(get_collection_counts, GetCollectionCounts);
mock_db_method!(get_collection_usage, GetCollectionUsage);
mock_db_method!(get_storage_modified, GetStorageModified);
Expand All @@ -67,6 +68,7 @@ impl Db for MockDb {
mock_db_method!(post_bsos, PostBsos);
mock_db_method!(delete_bso, DeleteBso);
mock_db_method!(get_bso, GetBso, Option<results::GetBso>);
mock_db_method!(get_bso_modified, GetBsoModified, results::GetBsoModified);
mock_db_method!(put_bso, PutBso);
}

Expand Down
68 changes: 67 additions & 1 deletion src/db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ pub mod util;
use futures::future::Future;

pub use self::error::{DbError, DbErrorKind};
use self::util::SyncTimestamp;
use web::extractors::HawkIdentifier;

lazy_static! {
/// For efficiency, it's possible to use fixed pre-determined IDs for
Expand Down Expand Up @@ -55,6 +57,11 @@ pub trait Db: Send {
params: params::GetCollectionModifieds,
) -> DbFuture<results::GetCollectionModifieds>;

fn get_collection_modified(
&self,
params: params::GetCollectionModified,
) -> DbFuture<results::GetCollectionModified>;

fn get_collection_counts(
&self,
params: params::GetCollectionCounts,
Expand Down Expand Up @@ -92,9 +99,61 @@ pub trait Db: Send {

fn get_bso(&self, params: params::GetBso) -> DbFuture<Option<results::GetBso>>;

fn get_bso_modified(&self, params: params::GetBsoModified)
-> DbFuture<results::GetBsoModified>;

fn put_bso(&self, params: params::PutBso) -> DbFuture<results::PutBso>;

fn box_clone(&self) -> Box<dyn Db>;

/// Retrieve the timestamp for an item/collection
///
/// Modeled on the Python `get_resource_timestamp` function.
fn extract_resource(
&self,
user_id: HawkIdentifier,
collection: Option<String>,
bso: Option<String>,
) -> DbFuture<SyncTimestamp> {
// If there's no collection, we return the overall storage timestamp
let collection = match collection {
Some(collection) => collection,
None => return Box::new(self.get_storage_modified(user_id)),
};
// If there's no bso, return the collection
let bso = match bso {
Some(bso) => bso,
None => {
return Box::new(
self.get_collection_modified(params::GetCollectionModified {
user_id,
collection,
}).then(|v| match v {
Ok(v) => Ok(v),
Err(e) => match e.kind() {
DbErrorKind::CollectionNotFound => {
Ok(SyncTimestamp::from_seconds(0f64))
}
_ => Err(e),
},
}),
)
}
};
Box::new(
self.get_bso_modified(params::GetBsoModified {
user_id,
collection,
id: bso,
}).then(|v| match v {
Ok(v) => Ok(v),
Err(e) => match e.kind() {
DbErrorKind::CollectionNotFound => Ok(SyncTimestamp::from_seconds(0f64)),
_ => Err(e),
},
}),
)
}
}

impl Clone for Box<dyn Db> {
Expand All @@ -103,10 +162,17 @@ impl Clone for Box<dyn Db> {
}
}

#[derive(Debug)]
#[derive(Debug, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum Sorting {
None,
Newest,
Oldest,
Index,
}

impl Default for Sorting {
fn default() -> Self {
Sorting::None
}
}
69 changes: 38 additions & 31 deletions src/db/mysql/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use db::{
util::SyncTimestamp,
Db, DbFuture, Sorting,
};
use web::extractors::HawkIdentifier;
use web::extractors::{BsoQueryParams, HawkIdentifier};

no_arg_sql_function!(last_insert_id, Integer);

Expand Down Expand Up @@ -364,25 +364,17 @@ impl MysqlDb {
pub fn get_bsos_sync(&self, params: params::GetBsos) -> Result<results::GetBsos> {
let user_id = params.user_id.legacy_id as i32;
let collection_id = self.get_collection_id(&params.collection)?;
// XXX: ensure offset/limit/newer are valid
let params::GetBsos {
mut ids,
older,
let BsoQueryParams {
newer,
older,
sort,
limit,
offset,
..
} = params;

// XXX: should error out (400 Bad Request) when more than 100
// are provided (move to validation layer)
if ids.len() > 100 {
// spec says only 100 ids at a time
ids.truncate(100);
}
ids,
// XXX: Do the appropriate thing depending on if full is true/false
full: _full,
} = params.params;

// XXX: convert to raw SQL for use by other backends?
let mut query = bso::table
.select((
bso::id,
Expand All @@ -392,11 +384,16 @@ impl MysqlDb {
bso::expiry,
)).filter(bso::user_id.eq(user_id))
.filter(bso::collection_id.eq(collection_id as i32)) // XXX:
.filter(bso::modified.lt(older as i64))
.filter(bso::modified.gt(newer as i64))
.filter(bso::expiry.gt(self.timestamp().as_i64()))
.into_boxed();

if let Some(older) = older {
query = query.filter(bso::modified.lt(older.as_i64()));
}
if let Some(newer) = newer {
query = query.filter(bso::modified.gt(newer.as_i64()));
}

if !ids.is_empty() {
query = query.filter(bso::id.eq_any(ids));
}
Expand All @@ -410,6 +407,8 @@ impl MysqlDb {

// fetch an extra row to detect if there are more rows that
// match the query conditions
let limit = limit.unwrap_or(0) as i64;
let offset = offset.unwrap_or(0) as i64;
query = query.limit(if limit >= 0 { limit + 1 } else { limit });
if offset != 0 {
// XXX: copy over this optimization:
Expand Down Expand Up @@ -517,10 +516,10 @@ impl MysqlDb {

pub fn get_collection_modified_sync(
&self,
user_id: u32,
collection: &str,
params: params::GetCollectionModified,
) -> Result<SyncTimestamp> {
let collection_id = self.get_collection_id(collection)?;
let user_id = params.user_id.legacy_id as u32;
let collection_id = self.get_collection_id(&params.collection)?;
if let Some(modified) = self
.session
.borrow()
Expand All @@ -538,21 +537,18 @@ impl MysqlDb {
.ok_or(DbErrorKind::CollectionNotFound.into())
}

pub fn get_bso_modified_sync(
&self,
user_id: u32,
collection: &str,
bso_id: &str,
) -> Result<SyncTimestamp> {
let collection_id = self.get_collection_id(collection)?;
bso::table
pub fn get_bso_modified_sync(&self, params: params::GetBsoModified) -> Result<SyncTimestamp> {
let user_id = params.user_id.legacy_id;
let collection_id = self.get_collection_id(&params.collection)?;
let modified = bso::table
.select(bso::modified)
.filter(bso::user_id.eq(user_id as i32))
.filter(bso::collection_id.eq(&collection_id))
.filter(bso::id.eq(&bso_id))
.first(&self.conn)
.filter(bso::id.eq(&params.id))
.first::<i64>(&self.conn)
.optional()?
.ok_or(DbErrorKind::ItemNotFound.into())
.unwrap_or_default();
Ok(SyncTimestamp::from_i64(modified)?)
}

pub fn get_collection_modifieds_sync(
Expand Down Expand Up @@ -722,6 +718,11 @@ impl Db for MysqlDb {
get_collection_modifieds_sync,
GetCollectionModifieds
);
sync_db_method!(
get_collection_modified,
get_collection_modified_sync,
GetCollectionModified
);
sync_db_method!(
get_collection_counts,
get_collection_counts_sync,
Expand All @@ -745,6 +746,12 @@ impl Db for MysqlDb {
sync_db_method!(post_bsos, post_bsos_sync, PostBsos);
sync_db_method!(delete_bso, delete_bso_sync, DeleteBso);
sync_db_method!(get_bso, get_bso_sync, GetBso, Option<results::GetBso>);
sync_db_method!(
get_bso_modified,
get_bso_modified_sync,
GetBsoModified,
results::GetBsoModified
);
sync_db_method!(put_bso, put_bso_sync, PutBso);
}

Expand Down
54 changes: 40 additions & 14 deletions src/db/mysql/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ use db::mysql::{
pool::MysqlDbPool,
schema::collections,
};
use db::util::SyncTimestamp;
use db::{params, DbErrorKind, Sorting};
use env_logger;
use settings::{Secrets, ServerLimits, Settings};
use web::extractors::HawkIdentifier;
use web::extractors::{BsoQueryParams, HawkIdentifier};

// distant future (year 2099) timestamp for tests
pub const MAX_TIMESTAMP: u64 = 4070937600000;
Expand Down Expand Up @@ -100,12 +101,15 @@ fn gbsos(
params::GetBsos {
user_id: HawkIdentifier::new_legacy(user_id as u64),
collection: coll.to_owned(),
ids: bids.into_iter().map(|id| id.to_owned().into()).collect(),
older,
newer,
sort,
limit,
offset,
params: BsoQueryParams {
ids: bids.into_iter().map(|id| id.to_owned().into()).collect(),
older: Some(SyncTimestamp::from_milliseconds(older)),
newer: Some(SyncTimestamp::from_milliseconds(newer)),
sort,
limit: Some(limit as u32),
offset: Some(offset as u64),
full: true,
},
}
}

Expand Down Expand Up @@ -602,7 +606,10 @@ fn delete_collection() -> Result<()> {
assert!(result.is_none());
}

let result = db.get_collection_modified_sync(uid, coll);
let result = db.get_collection_modified_sync(params::GetCollectionModified {
user_id: uid.into(),
collection: coll.to_string(),
});
match result.unwrap_err().kind() {
DbErrorKind::CollectionNotFound => assert!(true),
_ => assert!(false),
Expand All @@ -622,7 +629,10 @@ fn get_collection_modifieds() -> Result<()> {
assert!(cols.contains_key(coll));
assert_eq!(cols.get(coll), Some(&db.timestamp()));

let modified = db.get_collection_modified_sync(uid, coll)?;
let modified = db.get_collection_modified_sync(params::GetCollectionModified {
user_id: uid.into(),
collection: coll.to_string(),
})?;
assert_eq!(Some(&modified), cols.get(coll));
Ok(())
}
Expand Down Expand Up @@ -691,7 +701,10 @@ fn put_bso() -> Result<()> {
let bid = "b0";
let bso1 = pbso(uid, coll, bid, Some("foo"), Some(1), Some(DEFAULT_BSO_TTL));
db.put_bso_sync(bso1)?;
let modified = db.get_collection_modified_sync(uid, coll)?;
let modified = db.get_collection_modified_sync(params::GetCollectionModified {
user_id: uid.into(),
collection: coll.to_string(),
})?;
assert_eq!(modified, db.timestamp());

let bso = db.get_bso_sync(gbso(uid, coll, bid))?.unwrap();
Expand All @@ -701,7 +714,10 @@ fn put_bso() -> Result<()> {
let bso2 = pbso(uid, coll, bid, Some("bar"), Some(2), Some(DEFAULT_BSO_TTL));
db.set_timestamp(db.timestamp().as_i64() + 19);
db.put_bso_sync(bso2)?;
let modified = db.get_collection_modified_sync(uid, coll)?;
let modified = db.get_collection_modified_sync(params::GetCollectionModified {
user_id: uid.into(),
collection: coll.to_string(),
})?;
assert_eq!(modified, db.timestamp());

let bso = db.get_bso_sync(gbso(uid, coll, bid))?.unwrap();
Expand Down Expand Up @@ -733,7 +749,10 @@ fn post_bsos() -> Result<()> {
//assert!(!result.failed.contains_key("b1"));
//assert!(!result.failed.contains_key("b1"));

let modified = db.get_collection_modified_sync(uid, coll)?;
let modified = db.get_collection_modified_sync(params::GetCollectionModified {
user_id: uid.into(),
collection: coll.to_string(),
})?;
// XXX: casts
assert_eq!(result.modified, modified);

Expand All @@ -758,7 +777,10 @@ fn post_bsos() -> Result<()> {
assert_eq!(bso.sortindex, Some(22));
assert_eq!(bso.payload, "updated 2");

let modified = db.get_collection_modified_sync(uid, coll)?;
let modified = db.get_collection_modified_sync(params::GetCollectionModified {
user_id: uid.into(),
collection: coll.to_string(),
})?;
assert_eq!(result2.modified, modified);
Ok(())
}
Expand Down Expand Up @@ -847,7 +869,11 @@ fn get_bso_modified() -> Result<()> {
let bid = "b0";
let bso = pbso(uid, coll, bid, Some("a"), None, None);
db.put_bso_sync(bso)?;
let modified = db.get_bso_modified_sync(uid, coll, bid)?;
let modified = db.get_bso_modified_sync(params::GetBsoModified {
user_id: uid.into(),
collection: coll.to_string(),
id: bid.to_string(),
})?;
assert_eq!(modified, db.timestamp());
Ok(())
}
Expand Down
Loading

0 comments on commit b066fba

Please sign in to comment.