DidDocumentStorage

Trait DidDocumentStorage 

Source
pub trait DidDocumentStorage: Send + Sync {
    // Required methods
    fn get_document_by_did<'life0, 'life1, 'async_trait>(
        &'life0 self,
        did: &'life1 str,
    ) -> Pin<Box<dyn Future<Output = Result<Option<Document>>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait,
             'life1: 'async_trait;
    fn store_document<'life0, 'async_trait>(
        &'life0 self,
        document: Document,
    ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait;
    fn delete_document_by_did<'life0, 'life1, 'async_trait>(
        &'life0 self,
        did: &'life1 str,
    ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait,
             'life1: 'async_trait;
}
Expand description

Trait for implementing DID document CRUD operations across different storage backends.

This trait provides an abstraction layer for storing and retrieving DID documents, allowing different implementations for various storage systems such as databases, file systems, in-memory stores, or cloud storage services.

All methods return anyhow::Result to allow implementations to use their own error types while providing a consistent interface for callers. Implementations should handle their specific error conditions and convert them to appropriate error messages.

§Thread Safety

This trait requires implementations to be thread-safe (Send + Sync), meaning:

  • Send: The storage implementation can be moved between threads
  • Sync: The storage implementation can be safely accessed from multiple threads simultaneously

This is essential for async applications where the storage might be accessed from different async tasks running on different threads. Implementations should use appropriate synchronization primitives (like Arc<Mutex<>>, RwLock, or database connection pools) to ensure thread safety.

§Usage

Implementors of this trait can provide storage for AT Protocol DID documents in any backend:

use atproto_identity::storage::DidDocumentStorage;
use atproto_identity::model::Document;
use anyhow::Result;
use std::sync::Arc;
use tokio::sync::RwLock;
use std::collections::HashMap;

// Thread-safe in-memory storage using Arc<RwLock<>>
#[derive(Clone)]
struct InMemoryStorage {
    data: Arc<RwLock<HashMap<String, Document>>>, // DID -> Document mapping
}

#[async_trait::async_trait]
impl DidDocumentStorage for InMemoryStorage {
    async fn get_document_by_did(&self, did: &str) -> Result<Option<Document>> {
        let data = self.data.read().await;
        Ok(data.get(did).cloned())
    }
     
    async fn store_document(&self, document: Document) -> Result<()> {
        let mut data = self.data.write().await;
        data.insert(document.id.clone(), document);
        Ok(())
    }
     
    async fn delete_document_by_did(&self, did: &str) -> Result<()> {
        let mut data = self.data.write().await;
        data.remove(did);
        Ok(())
    }
}

// Database storage with thread-safe connection pool
struct DatabaseStorage {
    pool: sqlx::Pool<sqlx::Postgres>, // Thread-safe connection pool
}

#[async_trait::async_trait]
impl DidDocumentStorage for DatabaseStorage {
    async fn get_document_by_did(&self, did: &str) -> Result<Option<Document>> {
        // Database connection pools are thread-safe
        let row: Option<(serde_json::Value,)> = sqlx::query_as(
            "SELECT document FROM did_documents WHERE did = $1"
        )
        .bind(did)
        .fetch_optional(&self.pool)
        .await?;
         
        if let Some((doc_json,)) = row {
            let document: Document = serde_json::from_value(doc_json)?;
            Ok(Some(document))
        } else {
            Ok(None)
        }
    }
     
    async fn store_document(&self, document: Document) -> Result<()> {
        let doc_json = serde_json::to_value(&document)?;
        sqlx::query("INSERT INTO did_documents (did, document) VALUES ($1, $2) ON CONFLICT (did) DO UPDATE SET document = $2")
            .bind(&document.id)
            .bind(doc_json)
            .execute(&self.pool)
            .await?;
        Ok(())
    }
     
    async fn delete_document_by_did(&self, did: &str) -> Result<()> {
        sqlx::query("DELETE FROM did_documents WHERE did = $1")
            .bind(did)
            .execute(&self.pool)
            .await?;
        Ok(())
    }
}

Required Methods§

Source

fn get_document_by_did<'life0, 'life1, 'async_trait>( &'life0 self, did: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<Option<Document>>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Retrieves a DID document associated with the given DID.

This method looks up the complete DID document that is currently stored for the provided DID (Decentralized Identifier). The document contains services, verification methods, and other identity information for the DID.

§Arguments
  • did - The DID (Decentralized Identifier) to look up. Should be in the format did:method:identifier (e.g., “did:plc:bv6ggog3tya2z3vxsub7hnal”)
§Returns
  • Ok(Some(document)) - If a document is found for the given DID
  • Ok(None) - If no document is currently stored for the DID
  • Err(error) - If an error occurs during retrieval (storage failure, invalid DID format, etc.)
§Examples
let storage = MyStorage::new();
let document = storage.get_document_by_did("did:plc:bv6ggog3tya2z3vxsub7hnal").await?;
match document {
    Some(doc) => {
        println!("Found document for DID: {}", doc.id);
        if let Some(handle) = doc.handles() {
            println!("Primary handle: {}", handle);
        }
    },
    None => println!("No document found for this DID"),
}
Source

fn store_document<'life0, 'async_trait>( &'life0 self, document: Document, ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Stores or updates a DID document.

This method creates a new DID document entry or updates an existing one. In the AT Protocol ecosystem, this operation typically occurs when a DID document is resolved from the network, updated by the identity owner, or cached for performance.

Implementations should ensure that:

  • The document’s DID (document.id) is used as the key for storage
  • The operation is atomic (either fully succeeds or fully fails)
  • Any existing document for the same DID is properly replaced
  • The complete document structure is preserved
§Arguments
  • document - The complete DID document to store. The document’s id field will be used as the storage key.
§Returns
  • Ok(()) - If the document was successfully stored or updated
  • Err(error) - If an error occurs during the operation (storage failure, serialization failure, constraint violation, etc.)
§Examples
let storage = MyStorage::new();
let document = Document {
    id: "did:plc:bv6ggog3tya2z3vxsub7hnal".to_string(),
    also_known_as: vec!["at://alice.bsky.social".to_string()],
    service: vec![/* services */],
    verification_method: vec![/* verification methods */],
    extra: HashMap::new(),
};
storage.store_document(document).await?;
println!("Document successfully stored");
Source

fn delete_document_by_did<'life0, 'life1, 'async_trait>( &'life0 self, did: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Deletes a DID document by its DID.

This method removes a DID document from storage using the DID as the identifier. This operation is typically used when cleaning up expired cache entries, removing invalid documents, or when an identity is deactivated.

Implementations should:

  • Handle the case where the DID doesn’t exist gracefully (return Ok(()))
  • Ensure the deletion is atomic
  • Clean up any related data or indexes
  • Preserve referential integrity if applicable
§Arguments
  • did - The DID identifying the document to delete. Should be in the format did:method:identifier (e.g., “did:plc:bv6ggog3tya2z3vxsub7hnal”)
§Returns
  • Ok(()) - If the document was successfully deleted or didn’t exist
  • Err(error) - If an error occurs during deletion (storage failure, etc.)
§Examples
let storage = MyStorage::new();
storage.delete_document_by_did("did:plc:bv6ggog3tya2z3vxsub7hnal").await?;
println!("Document deleted");

Implementors§