areev 0.2.0

Rust SDK for the Areev knowledge database — gRPC and HTTP transports
Documentation
//! `Imports` resource — bulk file/document import + export.
//!
//! Mirrors the Python SDK's `client.imports.*` surface.
//!
//! [`Imports::import_file`], [`Imports::import_document`], and
//! [`Imports::extract_document`] are `multipart/form-data` uploads;
//! [`Imports::export_file`] is a download.

use serde_json::{Map, Value};

use crate::error::Result;
use crate::http::HttpClient;

/// An in-memory file to upload as a multipart part.
///
/// Construct with [`FilePart::new`]; set an explicit MIME type with
/// [`FilePart::mime`] (defaults to `application/octet-stream`).
pub struct FilePart {
    file_name: String,
    bytes: Vec<u8>,
    mime: String,
}

impl FilePart {
    /// A file part with the given name + raw bytes (octet-stream).
    pub fn new(file_name: impl Into<String>, bytes: Vec<u8>) -> Self {
        Self {
            file_name: file_name.into(),
            bytes,
            mime: "application/octet-stream".to_string(),
        }
    }

    /// Override the part's MIME type (e.g. `application/pdf`).
    pub fn mime(mut self, mime: impl Into<String>) -> Self {
        self.mime = mime.into();
        self
    }

    fn into_form(self, extra: Option<&Map<String, Value>>) -> Result<reqwest::multipart::Form> {
        let part = reqwest::multipart::Part::bytes(self.bytes)
            .file_name(self.file_name)
            .mime_str(&self.mime)
            .map_err(crate::error::AreevError::from)?;
        let mut form = reqwest::multipart::Form::new().part("file", part);
        if let Some(fields) = extra {
            for (k, v) in fields {
                let text = match v {
                    Value::String(s) => s.clone(),
                    other => other.to_string(),
                };
                form = form.text(k.clone(), text);
            }
        }
        Ok(form)
    }
}

/// File / document import + export.
///
/// Access via [`crate::Areev::imports`].
pub struct Imports<'a> {
    http: &'a HttpClient,
    memory_id: String,
}

impl<'a> Imports<'a> {
    /// Internal constructor — use [`crate::Areev::imports`].
    pub(crate) fn new(http: &'a HttpClient, memory_id: String) -> Self {
        Self { http, memory_id }
    }

    fn p(&self, suffix: &str) -> String {
        format!("/memories/{}/{}", self.memory_id, suffix)
    }

    /// Import a structured grain file (`.mg` / NDJSON) as a multipart
    /// upload. `fields` are extra form fields. Emits an audit event.
    pub async fn import_file(
        &self,
        file: FilePart,
        fields: Option<&Map<String, Value>>,
    ) -> Result<Value> {
        let form = file.into_form(fields)?;
        self.http
            ._post_multipart(&self.p("import-file"), form)
            .await
    }

    /// Import a document (PDF / DOCX / etc.) — the server extracts
    /// grains. `fields` are extra form fields. Emits an audit event.
    pub async fn import_document(
        &self,
        file: FilePart,
        fields: Option<&Map<String, Value>>,
    ) -> Result<Value> {
        let form = file.into_form(fields)?;
        self.http
            ._post_multipart(&self.p("import-document"), form)
            .await
    }

    /// Extract structured content from a document without storing it.
    /// Read-only — nothing is persisted.
    pub async fn extract_document(
        &self,
        file: FilePart,
        fields: Option<&Map<String, Value>>,
    ) -> Result<Value> {
        let form = file.into_form(fields)?;
        self.http
            ._post_multipart(&self.p("extract-document"), form)
            .await
    }

    /// Export the memory's grains as a downloadable file.
    ///
    /// Data-portability operation (GDPR Art. 20) — always issued, never
    /// gated.
    ///
    /// The endpoint responds with a binary attachment
    /// (`application/x-mg-stream` — a length-framed `.mg` blob, or NDJSON
    /// for the JSON export format), **not** JSON, so this returns the raw
    /// bytes verbatim. Persist them to a file or feed them to an `.mg`
    /// reader; do **not** assume UTF-8 / JSON.
    pub async fn export_file(
        &self,
        namespace: Option<&str>,
        grain_type: Option<&str>,
    ) -> Result<Vec<u8>> {
        let mut params = Map::new();
        if let Some(n) = namespace {
            params.insert("namespace".into(), Value::String(n.to_string()));
        }
        if let Some(g) = grain_type {
            params.insert("grain_type".into(), Value::String(g.to_string()));
        }
        let query = if params.is_empty() {
            None
        } else {
            Some(Value::Object(params))
        };
        self.http
            ._get_bytes(&self.p("export-file"), query.as_ref())
            .await
    }
}