norman-ai 0.1.1

Norman AI Core SDK for Rust
Documentation
use serde::{Deserialize, Serialize};
use reqwest::Client;
use anyhow::{anyhow, Result};


/// Base API URL
const BASE_URL: &str = "https://api.dev.amit.public.norman-ai.com/v0";

/// Account model
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Account {
    pub id: String,
    pub creation_time: String,
    pub name: String,
}

/// Authentication factors
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccountAuthenticationFactors {
    pub account_id: String,
    pub api_key_count: u32,
    pub password_count: u32,
    pub verified_email_count: u32,
}

impl AccountAuthenticationFactors {
    pub fn has_authentication_factor(&self) -> bool {
        self.api_key_count > 0 || self.password_count > 0 || self.verified_email_count > 0
    }
}

/// Login response
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoginResponse {
    pub account: Account,
    pub access_token: String,
    pub id_token: String,
}

/// Norman API service
pub struct AccountService {
    client: Client,
}

impl AccountService {
    pub fn new() -> Self {
        Self {
            client: Client::new(),
        }
    }

    /// Fetch a single account by ID
    pub async fn get_account_by_id(&self, account_id: &str) -> Result<Account> {
        let payload = serde_json::json!({
            "table": "Accounts",
            "filters": { "id": account_id }
        });

        let resp = self.client
            .post(format!("{BASE_URL}/authenticate/accounts/get"))
            .json(&payload)
            .send()
            .await?;

        let map: serde_json::Value = resp.json().await?;
        if let Some(obj) = map.as_object().and_then(|o| o.values().next()) {
            Ok(serde_json::from_value(obj.clone())?)
        } else {
            Err(anyhow!("No account found for id: {account_id}"))
        }
    }

    /// Fetch multiple accounts with optional filters
    pub async fn get_accounts(
        &self,
        filters: Option<serde_json::Value>,
    ) -> Result<Vec<Account>, reqwest::Error> {
        let payload = if let Some(f) = filters {
            serde_json::json!({ "filters": f })
        } else {
            serde_json::json!({})
        };

        let resp = self.client
            .post(format!("{BASE_URL}/authenticate/accounts/get"))
            .json(&payload)
            .send()
            .await?;

        let map: serde_json::Value = resp.json().await?;
        let mut accounts = vec![];
        if let Some(obj) = map.as_object() {
            for v in obj.values() {
                if let Ok(a) = serde_json::from_value::<Account>(v.clone()) {
                    accounts.push(a);
                }
            }
        }
        Ok(accounts)
    }

    /// Retrieve authentication factors for an account
    pub async fn get_authentication_factors(
        &self,
        account_id: &str,
    ) -> Result<AccountAuthenticationFactors, reqwest::Error> {
        let url = format!("{BASE_URL}/authenticate/register/get/authentication/factors/{account_id}");
        let resp = self.client.get(url).send().await?;
        let data = resp.json::<AccountAuthenticationFactors>().await?;
        Ok(data)
    }

    /// Update account name
    pub async fn update_account_name(
        &self,
        account_id: &str,
        name: &str,
    ) -> Result<(), reqwest::Error> {
        let payload = serde_json::json!({
            "account": { "name": name },
            "filters": { "id": account_id }
        });

        self.client
            .patch(format!("{BASE_URL}/authenticate/accounts"))
            .json(&payload)
            .send()
            .await?
            .error_for_status()?;

        Ok(())
    }
}