invoance 0.2.0

Official Rust SDK for the Invoance compliance API
Documentation
//! Documents resource — `client.documents()`.

use std::sync::Arc;

use base64::Engine;
use serde_json::{json, Map, Value};
use sha2::{Digest, Sha256};

use crate::error::Error;
use crate::http::{HttpTransport, QueryParams};
use crate::models::documents::*;
use crate::validate::assert_sha256_hex;

/// Handle for the `/document` endpoints.
#[derive(Clone)]
pub struct DocumentsResource {
    t: Arc<HttpTransport>,
}

impl DocumentsResource {
    pub(crate) fn new(t: Arc<HttpTransport>) -> Self {
        Self { t }
    }

    /// `POST /document/anchor` — anchor a document hash.
    pub async fn anchor(
        &self,
        params: AnchorDocumentParams,
    ) -> Result<AnchorDocumentResponse, Error> {
        assert_sha256_hex("document_hash", &params.document_hash)?;
        let mut body = Map::new();
        body.insert("document_hash".into(), json!(params.document_hash));
        if let Some(v) = &params.document_ref {
            body.insert("document_ref".into(), json!(v));
        }
        if let Some(v) = &params.event_type {
            body.insert("event_type".into(), json!(v));
        }
        if let Some(v) = &params.original_bytes_b64 {
            body.insert("original_bytes_b64".into(), json!(v));
        }
        if let Some(v) = params.metadata {
            body.insert("metadata".into(), Value::Object(v));
        }
        if let Some(v) = &params.trace_id {
            body.insert("trace_id".into(), json!(v));
        }
        self.t
            .post(
                "/document/anchor",
                Some(&Value::Object(body)),
                params.idempotency_key.as_deref(),
            )
            .await
    }

    /// Convenience helper — reads a file (path or bytes), computes the SHA-256
    /// hash, base64-encodes the bytes, and calls [`anchor`](Self::anchor).
    pub async fn anchor_file(
        &self,
        params: AnchorFileParams,
    ) -> Result<AnchorDocumentResponse, Error> {
        let (content, default_ref): (Vec<u8>, Option<String>) = match &params.file {
            FileSource::Path(p) => {
                let bytes = std::fs::read(p).map_err(|e| {
                    Error::validation(format!("failed to read file {}: {e}", p.display()))
                })?;
                let base = p.file_name().and_then(|s| s.to_str()).map(str::to_string);
                (bytes, base)
            }
            FileSource::Bytes(b) => (b.clone(), None),
        };

        let mut hasher = Sha256::new();
        hasher.update(&content);
        let document_hash = hex::encode(hasher.finalize());

        let document_ref = params.document_ref.or(default_ref);
        let original_bytes_b64 = if params.skip_original {
            None
        } else {
            Some(base64::engine::general_purpose::STANDARD.encode(&content))
        };

        self.anchor(AnchorDocumentParams {
            document_hash,
            document_ref,
            event_type: params.event_type,
            original_bytes_b64,
            metadata: params.metadata,
            idempotency_key: params.idempotency_key,
            trace_id: params.trace_id,
        })
        .await
    }

    /// `GET /document` — paginated document listing.
    pub async fn list(&self, params: ListDocumentsParams) -> Result<ListDocumentsResponse, Error> {
        let q: QueryParams = vec![
            ("page", params.page.map(|v| v.to_string())),
            ("limit", params.limit.map(|v| v.to_string())),
            ("date_from", params.date_from),
            ("date_to", params.date_to),
            ("document_ref", params.document_ref),
        ];
        self.t.get("/document", Some(q)).await
    }

    /// `GET /document/{event_id}` — retrieve a single document.
    pub async fn get(&self, event_id: &str) -> Result<DocumentEvent, Error> {
        self.t.get(&format!("/document/{event_id}"), None).await
    }

    /// `GET /document/{event_id}/original` — download the original bytes.
    pub async fn get_original(&self, event_id: &str) -> Result<Vec<u8>, Error> {
        self.t
            .get_bytes(&format!("/document/{event_id}/original"))
            .await
    }

    /// `POST /document/{event_id}/verify` — hash verification.
    pub async fn verify(
        &self,
        event_id: &str,
        params: VerifyDocumentParams,
    ) -> Result<VerifyDocumentResponse, Error> {
        assert_sha256_hex("document_hash", &params.document_hash)?;
        let body = json!({ "document_hash": params.document_hash });
        self.t
            .post(&format!("/document/{event_id}/verify"), Some(&body), None)
            .await
    }
}