use async_trait::async_trait;
use helios_fhir::FhirVersion;
use serde_json::Value;
use crate::error::{StorageError, StorageResult};
use crate::tenant::TenantContext;
use crate::types::StoredResource;
#[async_trait]
pub trait ResourceStorage: Send + Sync {
fn backend_name(&self) -> &'static str;
async fn create(
&self,
tenant: &TenantContext,
resource_type: &str,
resource: Value,
fhir_version: FhirVersion,
) -> StorageResult<StoredResource>;
async fn create_or_update(
&self,
tenant: &TenantContext,
resource_type: &str,
id: &str,
resource: Value,
fhir_version: FhirVersion,
) -> StorageResult<(StoredResource, bool)>;
async fn read(
&self,
tenant: &TenantContext,
resource_type: &str,
id: &str,
) -> StorageResult<Option<StoredResource>>;
async fn update(
&self,
tenant: &TenantContext,
current: &StoredResource,
resource: Value,
) -> StorageResult<StoredResource>;
async fn delete(
&self,
tenant: &TenantContext,
resource_type: &str,
id: &str,
) -> StorageResult<()>;
async fn exists(
&self,
tenant: &TenantContext,
resource_type: &str,
id: &str,
) -> StorageResult<bool> {
Ok(self.read(tenant, resource_type, id).await?.is_some())
}
async fn read_batch(
&self,
tenant: &TenantContext,
resource_type: &str,
ids: &[&str],
) -> StorageResult<Vec<StoredResource>> {
let mut results = Vec::with_capacity(ids.len());
for id in ids {
if let Some(resource) = self.read(tenant, resource_type, id).await? {
results.push(resource);
}
}
Ok(results)
}
async fn count(
&self,
tenant: &TenantContext,
resource_type: Option<&str>,
) -> StorageResult<u64>;
}
#[async_trait]
pub trait PurgableStorage: ResourceStorage {
async fn purge(
&self,
tenant: &TenantContext,
resource_type: &str,
id: &str,
) -> StorageResult<()>;
async fn purge_all(&self, tenant: &TenantContext, resource_type: &str) -> StorageResult<u64>;
}
#[derive(Debug, Clone)]
pub enum ConditionalCreateResult {
Created(StoredResource),
Exists(StoredResource),
MultipleMatches(usize),
}
#[derive(Debug, Clone)]
pub enum ConditionalUpdateResult {
Updated(StoredResource),
Created(StoredResource),
NoMatch,
MultipleMatches(usize),
}
#[derive(Debug, Clone)]
pub enum ConditionalDeleteResult {
Deleted,
NoMatch,
MultipleMatches(usize),
}
#[derive(Debug, Clone)]
pub enum ConditionalPatchResult {
Patched(StoredResource),
NoMatch,
MultipleMatches(usize),
}
#[derive(Debug, Clone)]
pub enum PatchFormat {
JsonPatch(Value),
FhirPathPatch(Value),
MergePatch(Value),
}
#[async_trait]
pub trait ConditionalStorage: ResourceStorage {
async fn conditional_create(
&self,
tenant: &TenantContext,
resource_type: &str,
resource: Value,
search_params: &str,
fhir_version: FhirVersion,
) -> StorageResult<ConditionalCreateResult>;
async fn conditional_update(
&self,
tenant: &TenantContext,
resource_type: &str,
resource: Value,
search_params: &str,
upsert: bool,
fhir_version: FhirVersion,
) -> StorageResult<ConditionalUpdateResult>;
async fn conditional_delete(
&self,
tenant: &TenantContext,
resource_type: &str,
search_params: &str,
) -> StorageResult<ConditionalDeleteResult>;
async fn conditional_patch(
&self,
tenant: &TenantContext,
resource_type: &str,
search_params: &str,
patch: &PatchFormat,
) -> StorageResult<ConditionalPatchResult> {
let _ = (tenant, resource_type, search_params, patch);
Err(StorageError::Backend(
crate::error::BackendError::UnsupportedCapability {
backend_name: "unknown".to_string(),
capability: "conditional_patch".to_string(),
},
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_conditional_create_result_debug() {
let result = ConditionalCreateResult::MultipleMatches(3);
let debug = format!("{:?}", result);
assert!(debug.contains("MultipleMatches"));
assert!(debug.contains("3"));
}
#[test]
fn test_conditional_update_result_variants() {
let _created = ConditionalUpdateResult::Created(StoredResource::new(
"Patient",
"123",
crate::tenant::TenantId::new("t1"),
serde_json::json!({}),
FhirVersion::default(),
));
let _no_match = ConditionalUpdateResult::NoMatch;
let _multiple = ConditionalUpdateResult::MultipleMatches(2);
}
#[test]
fn test_conditional_delete_result_variants() {
let _deleted = ConditionalDeleteResult::Deleted;
let _no_match = ConditionalDeleteResult::NoMatch;
let _multiple = ConditionalDeleteResult::MultipleMatches(5);
}
}