add id to response
All checks were successful
Build & Test / build-and-test (push) Successful in 24s

This commit is contained in:
Dylan Knutson
2024-06-16 18:26:58 -07:00
parent a447a318af
commit de056c8f0c
9 changed files with 152 additions and 121 deletions

View File

@@ -6,7 +6,7 @@ pub trait Compressor: Send {
}
#[cfg(test)]
pub(self) mod test {
mod test {
use super::*;
use crate::compressor::*;
use rstest::*;

View File

@@ -1,6 +1,6 @@
use crate::{
sha256::Sha256,
shard::{StoreArgs, StoreResult},
shard::{StoreArgs, StoreResult, StoreResultKind},
shards::ShardsArc,
};
use axum::http::StatusCode;
@@ -19,60 +19,30 @@ pub struct StoreRequest {
#[derive(Serialize, Debug, PartialEq)]
#[serde(rename_all = "snake_case", tag = "status")]
pub enum StoreResponse {
Created {
stored_size: usize,
data_size: usize,
created_at: String,
},
Exists {
stored_size: usize,
data_size: usize,
created_at: String,
},
Sha256Mismatch {
expected_sha256: String,
},
InternalError {
message: String,
},
Ok(StoreResult),
Sha256Mismatch { expected_sha256: String },
InternalError { message: String },
}
impl From<StoreResultKind> for StatusCode {
fn from(val: StoreResultKind) -> Self {
match val {
StoreResultKind::Created => StatusCode::CREATED,
StoreResultKind::Exists => StatusCode::OK,
}
}
}
impl StoreResponse {
fn status_code(&self) -> StatusCode {
match self {
StoreResponse::Created { .. } => StatusCode::CREATED,
StoreResponse::Exists { .. } => StatusCode::OK,
StoreResponse::Ok(StoreResult { kind, .. }) => (*kind).into(),
StoreResponse::Sha256Mismatch { .. } => StatusCode::BAD_REQUEST,
StoreResponse::InternalError { .. } => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
impl From<StoreResult> for StoreResponse {
fn from(result: StoreResult) -> Self {
match result {
StoreResult::Created {
stored_size,
data_size,
created_at,
} => StoreResponse::Created {
stored_size,
data_size,
created_at: created_at.to_string(),
},
StoreResult::Exists {
stored_size,
data_size,
created_at,
} => StoreResponse::Exists {
stored_size,
data_size,
created_at: created_at.to_string(),
},
}
}
}
impl IntoResponse for StoreResponse {
fn into_response(self) -> axum::response::Response {
(self.status_code(), Json(self)).into_response()
@@ -91,7 +61,7 @@ pub async fn store_handler(
if sha256 != req_sha256 {
let sha256_str = sha256.hex_string();
error!(
"client sent mismatched sha256: (client) {} != (computed) {}",
"sent mismatched sha256: (sent) {} != (computed) {}",
req_sha256, sha256_str
);
@@ -108,7 +78,7 @@ pub async fn store_handler(
})
.await
{
Ok(store_result) => store_result.into(),
Ok(store_result) => StoreResponse::Ok(store_result),
Err(err) => StoreResponse::InternalError {
message: err.to_string(),
},
@@ -117,22 +87,21 @@ pub async fn store_handler(
#[cfg(test)]
pub mod test {
use crate::{compression_manager::test::make_compressor_with, shards::test::make_shards_with};
use super::*;
use crate::CompressionPolicy;
use crate::{compression_manager::test::make_compressor_with, shards::test::make_shards_with};
use axum::body::Bytes;
use axum_typed_multipart::FieldData;
use rstest::rstest;
async fn send_request<D: Into<Bytes>>(
compression_policy: CompressionPolicy,
async fn send_request_with<D: Into<Bytes>>(
shards: ShardsArc,
sha256: Option<Sha256>,
content_type: &str,
data: D,
) -> StoreResponse {
store_handler(
Extension(make_shards_with(make_compressor_with(compression_policy).into_arc()).await),
Extension(shards),
TypedMultipart(StoreRequest {
sha256: sha256.map(|s| s.hex_string()),
content_type: content_type.to_string(),
@@ -145,12 +114,31 @@ pub mod test {
.await
}
async fn send_request<D: Into<Bytes>>(
compression_policy: CompressionPolicy,
sha256: Option<Sha256>,
content_type: &str,
data: D,
) -> StoreResponse {
send_request_with(
make_shards(compression_policy).await,
sha256,
content_type,
data,
)
.await
}
async fn make_shards(compression_policy: CompressionPolicy) -> ShardsArc {
make_shards_with(make_compressor_with(compression_policy).into_arc()).await
}
#[tokio::test]
async fn test_store_handler() {
let result =
send_request(CompressionPolicy::Auto, None, "text/plain", "hello, world!").await;
assert_eq!(result.status_code(), StatusCode::CREATED, "{:?}", result);
assert!(matches!(result, StoreResponse::Created { .. }));
assert!(matches!(result, StoreResponse::Ok(..)));
}
#[tokio::test]
@@ -184,7 +172,32 @@ pub mod test {
)
.await;
assert_eq!(result.status_code(), StatusCode::CREATED);
assert!(matches!(result, StoreResponse::Created { .. }));
assert!(matches!(result, StoreResponse::Ok(..)));
}
#[tokio::test]
async fn test_id() {
let shards = make_shards(CompressionPolicy::Auto).await;
let data = "hello, world!";
let sha256 = Sha256::from_bytes(data.as_bytes());
let result = send_request_with(shards.clone(), Some(sha256), "text/plain", data).await;
assert_eq!(result.status_code(), StatusCode::CREATED);
let result = match result {
StoreResponse::Ok(result) => result,
_ => panic!("expected StoreResponse::Ok"),
};
assert_eq!(result.sha256, sha256);
assert_eq!(result.kind, StoreResultKind::Created);
let id = result.id;
let result = send_request_with(shards, Some(sha256), "text/plain", data).await;
assert_eq!(result.status_code(), StatusCode::OK);
let result = match result {
StoreResponse::Ok(result) => result,
_ => panic!("expected StoreResponse::Ok"),
};
assert_eq!(result.kind, StoreResultKind::Exists);
assert_eq!(result.id, id);
}
fn make_assert_eq<T: PartialEq + std::fmt::Debug>(value: T) -> impl Fn(T) {
@@ -212,13 +225,15 @@ pub mod test {
let result = send_request(compression_policy, None, content_type, vec![0; 1024]).await;
assert_eq!(result.status_code(), StatusCode::CREATED);
match result {
StoreResponse::Created {
StoreResponse::Ok(StoreResult {
kind,
stored_size,
data_size,
..
} => {
}) => {
assert_stored_size(stored_size);
assert_eq!(data_size, 1024);
assert_eq!(kind, StoreResultKind::Created);
}
_ => panic!("expected StoreResponse::Created"),
};

View File

@@ -2,6 +2,7 @@ use rusqlite::{
types::{FromSql, ToSqlOutput},
ToSql,
};
use serde::Serialize;
use sha2::Digest;
use std::{
error::Error,
@@ -50,6 +51,12 @@ impl Sha256 {
}
}
impl Serialize for Sha256 {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_bytes(&self.0)
}
}
impl ToSql for Sha256 {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
Ok(ToSqlOutput::Borrowed(rusqlite::types::ValueRef::Blob(

View File

@@ -1,23 +1,27 @@
use serde::Serialize;
use super::*;
use crate::{
compressible_data::CompressibleData,
concat_lines, into_tokio_rusqlite_err,
sql_types::{CompressionId, UtcDateTime},
sql_types::{CompressionId, EntryId, UtcDateTime},
AsyncBoxError,
};
#[derive(PartialEq, Debug)]
pub enum StoreResult {
Created {
stored_size: usize,
data_size: usize,
created_at: UtcDateTime,
},
Exists {
stored_size: usize,
data_size: usize,
created_at: UtcDateTime,
},
#[derive(PartialEq, Debug, Serialize, Copy, Clone)]
pub enum StoreResultKind {
Created,
Exists,
}
#[derive(PartialEq, Debug, Serialize)]
pub struct StoreResult {
pub kind: StoreResultKind,
pub id: EntryId,
pub sha256: Sha256,
pub stored_size: usize,
pub data_size: usize,
pub created_at: UtcDateTime,
}
#[derive(Default)]
@@ -75,16 +79,19 @@ fn find_with_sha256(
) -> Result<Option<StoreResult>, rusqlite::Error> {
conn.query_row(
concat_lines!(
"SELECT uncompressed_size, compressed_size, created_at",
"SELECT rowid, uncompressed_size, compressed_size, created_at",
"FROM entries",
"WHERE sha256 = ?"
),
params![sha256],
|row| {
Ok(StoreResult::Exists {
stored_size: row.get(0)?,
data_size: row.get(1)?,
created_at: row.get(2)?,
Ok(StoreResult {
kind: StoreResultKind::Exists,
id: row.get(0)?,
sha256: *sha256,
stored_size: row.get(1)?,
data_size: row.get(2)?,
created_at: row.get(3)?,
})
},
)
@@ -102,11 +109,12 @@ fn insert(
let created_at = UtcDateTime::now();
let compressed_size = data.len();
conn.execute(
let id = conn.query_row(
concat_lines!(
"INSERT INTO entries",
" (sha256, content_type, dict_id, uncompressed_size, compressed_size, data, created_at)",
"VALUES (?, ?, ?, ?, ?, ?, ?)",
"RETURNING rowid"
),
params![
sha256,
@@ -117,9 +125,13 @@ fn insert(
data.as_ref(),
created_at,
],
|row| row.get(0),
)?;
Ok(StoreResult::Created {
Ok(StoreResult {
kind: StoreResultKind::Created,
id,
sha256: *sha256,
stored_size: compressed_size,
data_size: uncompressed_size,
created_at,

View File

@@ -5,15 +5,15 @@ pub mod shard_error;
mod shard_struct;
pub use fn_get::{GetArgs, GetResult};
pub use fn_store::{StoreArgs, StoreResult};
pub use fn_store::{StoreArgs, StoreResult, StoreResultKind};
pub use shard_struct::Shard;
#[cfg(test)]
pub mod test {
pub use super::shard_struct::test::*;
}
use crate::{sha256::Sha256, shard::shard_error::ShardError};
use axum::body::Bytes;
use rusqlite::{params, types::FromSql, OptionalExtension};
use tokio_rusqlite::Connection;
use tracing::debug;
#[cfg(test)]
pub mod test {
pub use super::shard_struct::test::*;
}

View File

@@ -64,10 +64,11 @@ async fn get_num_entries(conn: &Connection) -> Result<usize, tokio_rusqlite::Err
pub mod test {
use crate::compression_manager::test::make_compressor_with;
use crate::compression_manager::CompressionManagerArc;
use crate::shard::fn_store::StoreResultKind;
use crate::{
compression_manager::test::make_compressor,
sha256::Sha256,
shard::{GetArgs, StoreArgs, StoreResult},
shard::{GetArgs, StoreArgs},
CompressionPolicy,
};
use rstest::rstest;
@@ -113,22 +114,14 @@ pub mod test {
sha256,
content_type: "text/plain".to_string(),
data: data.into(),
..Default::default()
})
.await
.unwrap();
match store_result {
StoreResult::Created {
stored_size: _,
data_size,
created_at,
} => {
// assert_eq!(stored_size, data.len());
assert_eq!(data_size, data.len());
assert!(created_at > chrono::Utc::now() - chrono::Duration::seconds(1));
}
_ => panic!("expected StoreResult::Created"),
}
assert_eq!(store_result.kind, StoreResultKind::Created);
assert_eq!(store_result.sha256, sha256);
assert_eq!(store_result.data_size, data.len());
assert!(store_result.created_at > chrono::Utc::now() - chrono::Duration::seconds(1));
assert_eq!(shard.num_entries().await.unwrap(), 1);
let get_result = shard.get(GetArgs { sha256 }).await.unwrap().unwrap();
@@ -148,42 +141,31 @@ pub mod test {
sha256,
content_type: "text/plain".to_string(),
data: data.into(),
..Default::default()
})
.await
.unwrap();
assert!(matches!(store_result, StoreResult::Created { .. }));
let created_at = match store_result {
StoreResult::Created {
stored_size,
data_size,
created_at,
} => {
assert_eq!(stored_size, data.len());
assert_eq!(data_size, data.len());
created_at
}
_ => panic!("expected StoreResult::Created"),
};
assert_eq!(store_result.kind, StoreResultKind::Created);
assert_eq!(store_result.sha256, sha256);
assert_eq!(store_result.stored_size, data.len());
assert_eq!(store_result.data_size, data.len());
let created_at = store_result.created_at;
let store_result = shard
.store(StoreArgs {
sha256,
content_type: "text/plain".to_string(),
data: data.into(),
..Default::default()
})
.await
.unwrap();
assert_eq!(
store_result,
StoreResult::Exists {
data_size: data.len(),
stored_size: data.len(),
created_at
}
);
assert_eq!(store_result.kind, StoreResultKind::Exists);
assert_eq!(store_result.sha256, sha256);
assert_eq!(store_result.data_size, data.len(),);
assert_eq!(store_result.stored_size, data.len(),);
assert_eq!(store_result.created_at, created_at);
assert_eq!(shard.num_entries().await.unwrap(), 1);
}
@@ -213,11 +195,10 @@ pub mod test {
sha256,
content_type: content_type.clone(),
data: data.clone().into(),
..Default::default()
})
.await
.unwrap();
assert!(matches!(store_result, StoreResult::Created { .. }));
assert_eq!(store_result.kind, StoreResultKind::Created);
let get_result = shard.get(GetArgs { sha256 }).await.unwrap().unwrap();
assert_eq!(get_result.content_type, content_type);

View File

@@ -2,6 +2,7 @@ use rusqlite::{
types::{FromSql, FromSqlResult, ToSqlOutput, ValueRef},
ToSql,
};
use serde::Serialize;
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
pub struct EntryId(pub i64);
@@ -11,6 +12,12 @@ impl From<i64> for EntryId {
}
}
impl Serialize for EntryId {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.0.serialize(serializer)
}
}
impl FromSql for EntryId {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
Ok(value.as_i64()?.into())

View File

@@ -1,5 +1,7 @@
mod compression_id;
mod entry_id;
mod utc_date_time;
pub use compression_id::CompressionId;
pub use entry_id::EntryId;
pub use utc_date_time::UtcDateTime;

View File

@@ -3,6 +3,7 @@ use rusqlite::{
types::{FromSql, FromSqlError, ToSqlOutput, ValueRef},
Result, ToSql,
};
use serde::Serialize;
#[derive(PartialEq, Debug, PartialOrd)]
pub struct UtcDateTime(DateTime<chrono::Utc>);
@@ -24,6 +25,12 @@ impl ToString for UtcDateTime {
}
}
impl Serialize for UtcDateTime {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.to_string())
}
}
impl PartialEq<DateTime<chrono::Utc>> for UtcDateTime {
fn eq(&self, other: &DateTime<chrono::Utc>) -> bool {
self.0 == *other