Skip to main content

ResourceStorage

Trait ResourceStorage 

Source
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§

Source

fn backend_name(&self) -> &'static str

Returns a human-readable name for this storage backend.

Source

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 operation
  • resource_type - The FHIR resource type (e.g., “Patient”)
  • resource - The resource content as JSON
  • fhir_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 invalid
  • StorageError::Resource(AlreadyExists) - If a resource with the same ID exists
  • StorageError::Tenant - If the tenant doesn’t have create permission
Source

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 operation
  • resource_type - The FHIR resource type
  • id - The desired resource ID
  • resource - The resource content as JSON
  • fhir_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).

Source

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 operation
  • resource_type - The FHIR resource type
  • id - 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 permission
  • StorageError::Resource(Gone) - If the resource was deleted (optional behavior)
Source

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 operation
  • current - 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 exist
  • StorageError::Concurrency(VersionConflict) - If the resource was modified
  • StorageError::Tenant - If the tenant doesn’t have update permission
Source

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 operation
  • resource_type - The FHIR resource type
  • id - The resource’s logical ID
§Errors
  • StorageError::Resource(NotFound) - If the resource doesn’t exist
  • StorageError::Resource(Gone) - If already deleted
  • StorageError::Tenant - If the tenant doesn’t have delete permission
Source

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,

Counts the total number of resources of a given type.

§Arguments
  • tenant - The tenant context for this operation
  • resource_type - The FHIR resource type (or None for all types)
§Returns

The count of non-deleted resources.

Provided Methods§

Source

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 operation
  • resource_type - The FHIR resource type
  • id - The resource’s logical ID
§Returns

true if the resource exists and is not deleted, false otherwise.

Source

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,

Reads multiple resources by their IDs.

This is more efficient than multiple individual reads.

§Arguments
  • tenant - The tenant context for this operation
  • resource_type - The FHIR resource type
  • ids - The resource IDs to read
§Returns

A vector of found resources (missing/deleted resources are omitted).

Implementors§