pub trait ResourceStorage: Send + Sync {
// Required methods
fn backend_name(&self) -> &'static str;
fn create<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
tenant: &'life1 TenantContext,
resource_type: &'life2 str,
resource: Value,
fhir_version: FhirVersion,
) -> Pin<Box<dyn Future<Output = StorageResult<StoredResource>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait;
fn create_or_update<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
tenant: &'life1 TenantContext,
resource_type: &'life2 str,
id: &'life3 str,
resource: Value,
fhir_version: FhirVersion,
) -> Pin<Box<dyn Future<Output = StorageResult<(StoredResource, bool)>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait;
fn read<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
tenant: &'life1 TenantContext,
resource_type: &'life2 str,
id: &'life3 str,
) -> Pin<Box<dyn Future<Output = StorageResult<Option<StoredResource>>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait;
fn update<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
tenant: &'life1 TenantContext,
current: &'life2 StoredResource,
resource: Value,
) -> Pin<Box<dyn Future<Output = StorageResult<StoredResource>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait;
fn delete<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
tenant: &'life1 TenantContext,
resource_type: &'life2 str,
id: &'life3 str,
) -> Pin<Box<dyn Future<Output = StorageResult<()>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait;
fn count<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
tenant: &'life1 TenantContext,
resource_type: Option<&'life2 str>,
) -> Pin<Box<dyn Future<Output = StorageResult<u64>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait;
// Provided methods
fn exists<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
tenant: &'life1 TenantContext,
resource_type: &'life2 str,
id: &'life3 str,
) -> Pin<Box<dyn Future<Output = StorageResult<bool>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait { ... }
fn read_batch<'life0, 'life1, 'life2, 'life3, 'life4, 'async_trait>(
&'life0 self,
tenant: &'life1 TenantContext,
resource_type: &'life2 str,
ids: &'life3 [&'life4 str],
) -> Pin<Box<dyn Future<Output = StorageResult<Vec<StoredResource>>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait { ... }
}Expand description
Core storage trait for FHIR resources.
This trait defines the fundamental CRUD (Create, Read, Update, Delete) operations
for persisting FHIR resources. All operations require a TenantContext to ensure
proper tenant isolation - there is no escape hatch.
§Tenant Isolation
Every operation takes a TenantContext as its first parameter. This design ensures
that tenant isolation is enforced at the type level - it’s impossible to perform
storage operations without specifying the tenant context.
§Versioning
All mutating operations (create, update, delete) create new versions of resources.
The version ID is monotonically increasing and is used for optimistic locking via
the If-Match HTTP header.
§Soft Deletes
The delete operation performs a soft delete by default, marking the resource as
deleted but retaining its history. Use purge for permanent deletion (if supported).
§Example
use helios_fhir::FhirVersion;
use helios_persistence::core::ResourceStorage;
use helios_persistence::tenant::{TenantContext, TenantId, TenantPermissions};
async fn example<S: ResourceStorage>(storage: &S) -> Result<(), StorageError> {
let tenant = TenantContext::new(
TenantId::new("acme"),
TenantPermissions::full_access(),
);
// Create a new patient with FHIR R4
let patient = serde_json::json!({
"resourceType": "Patient",
"name": [{"family": "Smith"}]
});
let stored = storage.create(&tenant, "Patient", patient, FhirVersion::default()).await?;
println!("Created: {}", stored.url());
// Read it back
let read = storage.read(&tenant, "Patient", stored.id()).await?;
assert!(read.is_some());
// Update it
let mut updated_content = stored.content().clone();
updated_content["active"] = serde_json::json!(true);
let updated = storage.update(&tenant, &stored, updated_content).await?;
assert_eq!(updated.version_id(), "2");
// Delete it
storage.delete(&tenant, "Patient", stored.id()).await?;
Ok(())
}Required Methods§
Sourcefn backend_name(&self) -> &'static str
fn backend_name(&self) -> &'static str
Returns a human-readable name for this storage backend.
Sourcefn create<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
tenant: &'life1 TenantContext,
resource_type: &'life2 str,
resource: Value,
fhir_version: FhirVersion,
) -> Pin<Box<dyn Future<Output = StorageResult<StoredResource>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn create<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
tenant: &'life1 TenantContext,
resource_type: &'life2 str,
resource: Value,
fhir_version: FhirVersion,
) -> Pin<Box<dyn Future<Output = StorageResult<StoredResource>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Creates a new resource.
§Arguments
tenant- The tenant context for this operationresource_type- The FHIR resource type (e.g., “Patient”)resource- The resource content as JSONfhir_version- The FHIR specification version for this resource
§Returns
The stored resource with assigned ID, version, and metadata.
§Errors
StorageError::Validation- If the resource is invalidStorageError::Resource(AlreadyExists)- If a resource with the same ID existsStorageError::Tenant- If the tenant doesn’t have create permission
Sourcefn create_or_update<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
tenant: &'life1 TenantContext,
resource_type: &'life2 str,
id: &'life3 str,
resource: Value,
fhir_version: FhirVersion,
) -> Pin<Box<dyn Future<Output = StorageResult<(StoredResource, bool)>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
fn create_or_update<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
tenant: &'life1 TenantContext,
resource_type: &'life2 str,
id: &'life3 str,
resource: Value,
fhir_version: FhirVersion,
) -> Pin<Box<dyn Future<Output = StorageResult<(StoredResource, bool)>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Creates a resource with a specific ID (PUT semantics).
If the resource doesn’t exist, creates it with version “1”. If it exists, this is equivalent to an update.
§Arguments
tenant- The tenant context for this operationresource_type- The FHIR resource typeid- The desired resource IDresource- The resource content as JSONfhir_version- The FHIR specification version for this resource
§Returns
A tuple of (StoredResource, created: bool) where created indicates whether a new resource was created (true) or an existing one updated (false).
Sourcefn read<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
tenant: &'life1 TenantContext,
resource_type: &'life2 str,
id: &'life3 str,
) -> Pin<Box<dyn Future<Output = StorageResult<Option<StoredResource>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
fn read<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
tenant: &'life1 TenantContext,
resource_type: &'life2 str,
id: &'life3 str,
) -> Pin<Box<dyn Future<Output = StorageResult<Option<StoredResource>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Reads a resource by type and ID.
§Arguments
tenant- The tenant context for this operationresource_type- The FHIR resource typeid- The resource’s logical ID
§Returns
The stored resource if found and not deleted, or None.
§Errors
StorageError::Tenant- If the tenant doesn’t have read permissionStorageError::Resource(Gone)- If the resource was deleted (optional behavior)
Sourcefn update<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
tenant: &'life1 TenantContext,
current: &'life2 StoredResource,
resource: Value,
) -> Pin<Box<dyn Future<Output = StorageResult<StoredResource>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn update<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
tenant: &'life1 TenantContext,
current: &'life2 StoredResource,
resource: Value,
) -> Pin<Box<dyn Future<Output = StorageResult<StoredResource>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Updates an existing resource.
§Arguments
tenant- The tenant context for this operationcurrent- The current version of the resource (for optimistic locking)resource- The new resource content
§Returns
The updated resource with incremented version.
§Errors
StorageError::Resource(NotFound)- If the resource doesn’t existStorageError::Concurrency(VersionConflict)- If the resource was modifiedStorageError::Tenant- If the tenant doesn’t have update permission
Sourcefn delete<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
tenant: &'life1 TenantContext,
resource_type: &'life2 str,
id: &'life3 str,
) -> Pin<Box<dyn Future<Output = StorageResult<()>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
fn delete<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
tenant: &'life1 TenantContext,
resource_type: &'life2 str,
id: &'life3 str,
) -> Pin<Box<dyn Future<Output = StorageResult<()>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Deletes a resource (soft delete).
The resource is marked as deleted but its history is preserved.
Subsequent reads will return None (or Gone error depending on config).
§Arguments
tenant- The tenant context for this operationresource_type- The FHIR resource typeid- The resource’s logical ID
§Errors
StorageError::Resource(NotFound)- If the resource doesn’t existStorageError::Resource(Gone)- If already deletedStorageError::Tenant- If the tenant doesn’t have delete permission
Sourcefn count<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
tenant: &'life1 TenantContext,
resource_type: Option<&'life2 str>,
) -> Pin<Box<dyn Future<Output = StorageResult<u64>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn count<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
tenant: &'life1 TenantContext,
resource_type: Option<&'life2 str>,
) -> Pin<Box<dyn Future<Output = StorageResult<u64>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Provided Methods§
Sourcefn exists<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
tenant: &'life1 TenantContext,
resource_type: &'life2 str,
id: &'life3 str,
) -> Pin<Box<dyn Future<Output = StorageResult<bool>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
fn exists<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
tenant: &'life1 TenantContext,
resource_type: &'life2 str,
id: &'life3 str,
) -> Pin<Box<dyn Future<Output = StorageResult<bool>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Checks if a resource exists.
This is more efficient than read when you only need to check existence.
§Arguments
tenant- The tenant context for this operationresource_type- The FHIR resource typeid- The resource’s logical ID
§Returns
true if the resource exists and is not deleted, false otherwise.