use async_trait::async_trait;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::error::StorageResult;
use crate::tenant::TenantContext;
use crate::types::{Page, Pagination, StoredResource};
use super::versioned::VersionedStorage;
#[derive(Debug, Clone, Default)]
pub struct HistoryParams {
pub since: Option<DateTime<Utc>>,
pub before: Option<DateTime<Utc>>,
pub pagination: Pagination,
pub include_deleted: bool,
}
impl HistoryParams {
pub fn new() -> Self {
Self::default()
}
pub fn since(mut self, since: DateTime<Utc>) -> Self {
self.since = Some(since);
self
}
pub fn before(mut self, before: DateTime<Utc>) -> Self {
self.before = Some(before);
self
}
pub fn count(mut self, count: u32) -> Self {
self.pagination = self.pagination.with_count(count);
self
}
pub fn include_deleted(mut self, include: bool) -> Self {
self.include_deleted = include;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HistoryEntry {
pub resource: StoredResource,
pub method: HistoryMethod,
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum HistoryMethod {
Post,
Put,
Patch,
Delete,
}
impl std::fmt::Display for HistoryMethod {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
HistoryMethod::Post => write!(f, "POST"),
HistoryMethod::Put => write!(f, "PUT"),
HistoryMethod::Patch => write!(f, "PATCH"),
HistoryMethod::Delete => write!(f, "DELETE"),
}
}
}
pub type HistoryPage = Page<HistoryEntry>;
#[async_trait]
pub trait InstanceHistoryProvider: VersionedStorage {
async fn history_instance(
&self,
tenant: &TenantContext,
resource_type: &str,
id: &str,
params: &HistoryParams,
) -> StorageResult<HistoryPage>;
async fn history_instance_count(
&self,
tenant: &TenantContext,
resource_type: &str,
id: &str,
) -> StorageResult<u64>;
async fn delete_instance_history(
&self,
tenant: &TenantContext,
resource_type: &str,
id: &str,
) -> StorageResult<u64> {
let _ = (tenant, resource_type, id);
Err(crate::error::StorageError::Backend(
crate::error::BackendError::UnsupportedCapability {
backend_name: "unknown".to_string(),
capability: "delete_instance_history".to_string(),
},
))
}
async fn delete_version(
&self,
tenant: &TenantContext,
resource_type: &str,
id: &str,
version_id: &str,
) -> StorageResult<()> {
let _ = (tenant, resource_type, id, version_id);
Err(crate::error::StorageError::Backend(
crate::error::BackendError::UnsupportedCapability {
backend_name: "unknown".to_string(),
capability: "delete_version".to_string(),
},
))
}
}
#[async_trait]
pub trait TypeHistoryProvider: InstanceHistoryProvider {
async fn history_type(
&self,
tenant: &TenantContext,
resource_type: &str,
params: &HistoryParams,
) -> StorageResult<HistoryPage>;
async fn history_type_count(
&self,
tenant: &TenantContext,
resource_type: &str,
) -> StorageResult<u64>;
}
#[async_trait]
pub trait SystemHistoryProvider: TypeHistoryProvider {
async fn history_system(
&self,
tenant: &TenantContext,
params: &HistoryParams,
) -> StorageResult<HistoryPage>;
async fn history_system_count(&self, tenant: &TenantContext) -> StorageResult<u64>;
}
#[async_trait]
pub trait DifferentialHistoryProvider: TypeHistoryProvider {
async fn modified_since(
&self,
tenant: &TenantContext,
resource_type: Option<&str>,
since: DateTime<Utc>,
pagination: &Pagination,
) -> StorageResult<Page<StoredResource>>;
}
#[cfg(test)]
mod tests {
use super::*;
use helios_fhir::FhirVersion;
#[test]
fn test_history_params_builder() {
let now = Utc::now();
let params = HistoryParams::new()
.since(now)
.count(50)
.include_deleted(true);
assert!(params.since.is_some());
assert_eq!(params.pagination.count, 50);
assert!(params.include_deleted);
}
#[test]
fn test_history_method_display() {
assert_eq!(HistoryMethod::Post.to_string(), "POST");
assert_eq!(HistoryMethod::Put.to_string(), "PUT");
assert_eq!(HistoryMethod::Patch.to_string(), "PATCH");
assert_eq!(HistoryMethod::Delete.to_string(), "DELETE");
}
#[test]
fn test_history_entry_creation() {
let resource = StoredResource::new(
"Patient",
"123",
crate::tenant::TenantId::new("t1"),
serde_json::json!({}),
FhirVersion::default(),
);
let entry = HistoryEntry {
resource,
method: HistoryMethod::Post,
timestamp: Utc::now(),
};
assert_eq!(entry.method, HistoryMethod::Post);
}
}