add get_snapshot API method
This commit is contained in:
111
sync-server/src/api/get_snapshot.rs
Normal file
111
sync-server/src/api/get_snapshot.rs
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
use crate::api::{
|
||||||
|
client_key_header, failure_to_ise, ServerState, SNAPSHOT_CONTENT_TYPE, VERSION_ID_HEADER,
|
||||||
|
};
|
||||||
|
use crate::server::get_snapshot;
|
||||||
|
use actix_web::{error, get, web, HttpRequest, HttpResponse, Result};
|
||||||
|
|
||||||
|
/// Get a snapshot.
|
||||||
|
///
|
||||||
|
/// If a snapshot for this client exists, it is returned with content-type
|
||||||
|
/// `application/vnd.taskchampion.snapshot`. The `X-Version-Id` header contains the version of the
|
||||||
|
/// snapshot.
|
||||||
|
///
|
||||||
|
/// If no snapshot exists, returns a 404 with no content. Returns other 4xx or 5xx responses on
|
||||||
|
/// other errors.
|
||||||
|
#[get("/v1/client/snapshot")]
|
||||||
|
pub(crate) async fn service(
|
||||||
|
req: HttpRequest,
|
||||||
|
server_state: web::Data<ServerState>,
|
||||||
|
) -> Result<HttpResponse> {
|
||||||
|
let mut txn = server_state.txn().map_err(failure_to_ise)?;
|
||||||
|
|
||||||
|
let client_key = client_key_header(&req)?;
|
||||||
|
|
||||||
|
let client = txn
|
||||||
|
.get_client(client_key)
|
||||||
|
.map_err(failure_to_ise)?
|
||||||
|
.ok_or_else(|| error::ErrorNotFound("no such client"))?;
|
||||||
|
|
||||||
|
if let Some((version_id, data)) =
|
||||||
|
get_snapshot(txn, client_key, client).map_err(failure_to_ise)?
|
||||||
|
{
|
||||||
|
Ok(HttpResponse::Ok()
|
||||||
|
.content_type(SNAPSHOT_CONTENT_TYPE)
|
||||||
|
.header(VERSION_ID_HEADER, version_id.to_string())
|
||||||
|
.body(data))
|
||||||
|
} else {
|
||||||
|
Err(error::ErrorNotFound("no snapshot"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::storage::{InMemoryStorage, Snapshot, Storage};
|
||||||
|
use crate::Server;
|
||||||
|
use actix_web::{http::StatusCode, test, App};
|
||||||
|
use chrono::{TimeZone, Utc};
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_not_found() {
|
||||||
|
let client_key = Uuid::new_v4();
|
||||||
|
let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new());
|
||||||
|
|
||||||
|
// set up the storage contents..
|
||||||
|
{
|
||||||
|
let mut txn = storage.txn().unwrap();
|
||||||
|
txn.new_client(client_key, Uuid::new_v4()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let server = Server::new(storage);
|
||||||
|
let mut app = test::init_service(App::new().service(server.service())).await;
|
||||||
|
|
||||||
|
let uri = "/v1/client/snapshot";
|
||||||
|
let req = test::TestRequest::get()
|
||||||
|
.uri(uri)
|
||||||
|
.header("X-Client-Key", client_key.to_string())
|
||||||
|
.to_request();
|
||||||
|
let resp = test::call_service(&mut app, req).await;
|
||||||
|
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_success() {
|
||||||
|
let client_key = Uuid::new_v4();
|
||||||
|
let version_id = Uuid::new_v4();
|
||||||
|
let snapshot_data = vec![1, 2, 3, 4];
|
||||||
|
let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new());
|
||||||
|
|
||||||
|
// set up the storage contents..
|
||||||
|
{
|
||||||
|
let mut txn = storage.txn().unwrap();
|
||||||
|
txn.new_client(client_key, Uuid::new_v4()).unwrap();
|
||||||
|
txn.set_snapshot(
|
||||||
|
client_key,
|
||||||
|
Snapshot {
|
||||||
|
version_id,
|
||||||
|
versions_since: 3,
|
||||||
|
timestamp: Utc.ymd(2001, 9, 9).and_hms(1, 46, 40),
|
||||||
|
},
|
||||||
|
snapshot_data.clone(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let server = Server::new(storage);
|
||||||
|
let mut app = test::init_service(App::new().service(server.service())).await;
|
||||||
|
|
||||||
|
let uri = "/v1/client/snapshot";
|
||||||
|
let req = test::TestRequest::get()
|
||||||
|
.uri(uri)
|
||||||
|
.header("X-Client-Key", client_key.to_string())
|
||||||
|
.to_request();
|
||||||
|
let mut resp = test::call_service(&mut app, req).await;
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
|
||||||
|
use futures::StreamExt;
|
||||||
|
let (bytes, _) = resp.take_body().into_future().await;
|
||||||
|
assert_eq!(bytes.unwrap().unwrap().as_ref(), snapshot_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,11 +5,15 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
mod add_version;
|
mod add_version;
|
||||||
mod get_child_version;
|
mod get_child_version;
|
||||||
|
mod get_snapshot;
|
||||||
|
|
||||||
/// The content-type for history segments (opaque blobs of bytes)
|
/// The content-type for history segments (opaque blobs of bytes)
|
||||||
pub(crate) const HISTORY_SEGMENT_CONTENT_TYPE: &str =
|
pub(crate) const HISTORY_SEGMENT_CONTENT_TYPE: &str =
|
||||||
"application/vnd.taskchampion.history-segment";
|
"application/vnd.taskchampion.history-segment";
|
||||||
|
|
||||||
|
/// The content-type for snapshots (opaque blobs of bytes)
|
||||||
|
pub(crate) const SNAPSHOT_CONTENT_TYPE: &str = "application/vnd.taskchampion.snapshot";
|
||||||
|
|
||||||
/// The header name for version ID
|
/// The header name for version ID
|
||||||
pub(crate) const VERSION_ID_HEADER: &str = "X-Version-Id";
|
pub(crate) const VERSION_ID_HEADER: &str = "X-Version-Id";
|
||||||
|
|
||||||
@@ -26,6 +30,7 @@ pub(crate) fn api_scope() -> Scope {
|
|||||||
web::scope("")
|
web::scope("")
|
||||||
.service(get_child_version::service)
|
.service(get_child_version::service)
|
||||||
.service(add_version::service)
|
.service(add_version::service)
|
||||||
|
.service(get_snapshot::service)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a failure::Error to an Actix ISE
|
/// Convert a failure::Error to an Actix ISE
|
||||||
|
|||||||
@@ -105,6 +105,20 @@ pub(crate) fn add_version<'a>(
|
|||||||
Ok(AddVersionResult::Ok(version_id))
|
Ok(AddVersionResult::Ok(version_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implementation of the GetSnapshot protocol transaction
|
||||||
|
pub(crate) fn get_snapshot<'a>(
|
||||||
|
mut txn: Box<dyn StorageTxn + 'a>,
|
||||||
|
client_key: ClientKey,
|
||||||
|
client: Client,
|
||||||
|
) -> anyhow::Result<Option<(Uuid, Vec<u8>)>> {
|
||||||
|
Ok(if let Some(snap) = client.snapshot {
|
||||||
|
txn.get_snapshot_data(client_key, snap.version_id)?
|
||||||
|
.map(|data| (snap.version_id, data))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -302,4 +316,46 @@ mod test {
|
|||||||
fn av_success_nil_latest_version_id() -> anyhow::Result<()> {
|
fn av_success_nil_latest_version_id() -> anyhow::Result<()> {
|
||||||
test_av_success(false)
|
test_av_success(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_snapshot_found() -> anyhow::Result<()> {
|
||||||
|
let storage = InMemoryStorage::new();
|
||||||
|
let mut txn = storage.txn()?;
|
||||||
|
let client_key = Uuid::new_v4();
|
||||||
|
let data = vec![1, 2, 3];
|
||||||
|
let snapshot_version_id = Uuid::new_v4();
|
||||||
|
|
||||||
|
txn.new_client(client_key, snapshot_version_id)?;
|
||||||
|
txn.set_snapshot(
|
||||||
|
client_key,
|
||||||
|
Snapshot {
|
||||||
|
version_id: snapshot_version_id,
|
||||||
|
versions_since: 3,
|
||||||
|
timestamp: Utc.ymd(2001, 9, 9).and_hms(1, 46, 40),
|
||||||
|
},
|
||||||
|
data.clone(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let client = txn.get_client(client_key)?.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
get_snapshot(txn, client_key, client)?,
|
||||||
|
Some((snapshot_version_id, data.clone()))
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_snapshot_not_found() -> anyhow::Result<()> {
|
||||||
|
let storage = InMemoryStorage::new();
|
||||||
|
let mut txn = storage.txn()?;
|
||||||
|
let client_key = Uuid::new_v4();
|
||||||
|
|
||||||
|
txn.new_client(client_key, NO_VERSION_ID)?;
|
||||||
|
let client = txn.get_client(client_key)?.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(get_snapshot(txn, client_key, client)?, None);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user