areev 0.2.0

Rust SDK for the Areev knowledge database — gRPC and HTTP transports
Documentation
//! `Authz` resource — ReBAC relationship tuples (OpenFGA-backed).
//!
//! Mirrors the Python SDK's `client.authz.*` surface.

use serde_json::{json, Map, Value};

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

/// Relationship-based access control.
///
/// Access via [`crate::Areev::authz`]. Requires: admin scope for
/// [`Authz::grant`] and [`Authz::revoke`].
pub struct Authz<'a> {
    http: &'a HttpClient,
    memory_id: String,
}

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

    /// Check whether `principal` has `permission` on the resource addressed
    /// by `resource_type` + `resource_id`.
    pub async fn check(
        &self,
        principal: &str,
        permission: &str,
        resource_type: &str,
        resource_id: &str,
    ) -> Result<Value> {
        let query = json!({
            "principal": principal,
            "permission": permission,
            "resource_type": resource_type,
            "resource_id": resource_id,
        });
        let path = format!("/memories/{}/authz/check", self.memory_id);
        self.http._get(&path, Some(&query)).await
    }

    /// Grant `subject` the `relation` on `object`.
    ///
    /// Requires: admin scope. Emits an audit event.
    pub async fn grant(
        &self,
        subject: &str,
        relation: &str,
        object: &str,
        pseudonymized: Option<bool>,
    ) -> Result<Value> {
        let body = tuple_body(subject, relation, object, pseudonymized);
        let path = format!("/memories/{}/authz/grant", self.memory_id);
        self.http._post(&path, Some(&Value::Object(body))).await
    }

    /// Revoke a relationship tuple.
    ///
    /// Requires: admin scope. Emits an audit event. **Not auto-retried**
    /// — a revoke is a one-shot authorization change; the caller decides
    /// whether to re-issue on a transport / 5xx failure.
    pub async fn revoke(
        &self,
        subject: &str,
        relation: &str,
        object: &str,
        pseudonymized: Option<bool>,
    ) -> Result<Value> {
        let body = tuple_body(subject, relation, object, pseudonymized);
        let path = format!("/memories/{}/authz/revoke", self.memory_id);
        self.http
            ._post_no_retry(&path, Some(&Value::Object(body)))
            .await
    }
}

fn tuple_body(
    subject: &str,
    relation: &str,
    object: &str,
    pseudonymized: Option<bool>,
) -> Map<String, Value> {
    let mut body = Map::new();
    body.insert("subject".into(), Value::String(subject.to_string()));
    body.insert("relation".into(), Value::String(relation.to_string()));
    body.insert("object".into(), Value::String(object.to_string()));
    if let Some(p) = pseudonymized {
        body.insert("pseudonymized".into(), Value::Bool(p));
    }
    body
}