billdogeng 1.0.0-beta.1

Official BilldogEng server SDK for Rust — Analytics, Feature Flags (remote + local eval), Surveys, Messaging, and LLM observability.
Documentation
//! The BilldogEng server SDK client.

use std::collections::HashMap;

use serde_json::Value;

use crate::analytics::Analytics;
use crate::error::{BilldogEngError, Result};
use crate::flags::Flags;
use crate::llm::Llm;
use crate::messaging::Messaging;
use crate::surveys::Surveys;
use crate::transport::{Transport, TransportConfig};
use crate::types::{
    BilldogEngOptions, DispatchParams, FlagEvalOptions, FlagValue, Groups, LlmTraceParams,
    Properties,
};

/// The BilldogEng server SDK client.
///
/// Engagement suite for server-side use: Analytics, Feature Flags (remote +
/// local evaluation), Surveys (data API), Messaging dispatch, and LLM tracing.
///
/// # Example
/// ```no_run
/// use billdogeng::{BilldogEng, BilldogEngOptions};
///
/// let bd = BilldogEng::new("bd_test_xxx", BilldogEngOptions::default());
/// bd.capture("user-123", "order_completed", Default::default(), Default::default());
/// bd.shutdown().ok();
/// ```
pub struct BilldogEng {
    /// Resolved configuration.
    pub options: BilldogEngOptions,

    analytics: Analytics,
    flags: Flags,

    /// Surveys data API.
    pub surveys: Surveys,
    /// Messaging dispatch.
    pub messaging: Messaging,
    /// LLM observability.
    pub llm: Llm,
}

impl BilldogEng {
    /// Construct a new client. Panics if `api_key` is empty.
    pub fn new(api_key: &str, options: BilldogEngOptions) -> Self {
        assert!(!api_key.is_empty(), "BilldogEng: apiKey is required");

        let transport = Transport::new(TransportConfig {
            host: options.host.clone(),
            request_timeout_ms: options.request_timeout_ms,
            gzip: options.gzip,
            max_retries: options.max_retries,
            enable_logging: options.enable_logging,
        });

        let analytics = Analytics::new(
            transport.clone(),
            api_key.to_string(),
            options.flush_at,
            options.flush_interval_ms,
            options.max_queue_size,
            options.enable_logging,
            options.group_type_index.clone(),
        );
        let flags = Flags::new(
            transport.clone(),
            api_key.to_string(),
            options.local_evaluation,
            options.enable_logging,
        );
        let surveys = Surveys::new(transport.clone(), api_key.to_string());
        let messaging = Messaging::new(transport.clone());
        let llm = Llm::new(transport, api_key.to_string());

        Self {
            options,
            analytics,
            flags,
            surveys,
            messaging,
            llm,
        }
    }

    // ─── Analytics ────────────────────────────────────────────────────────────

    /// Capture an event for a user. Batched; flushed per the configured policy.
    pub fn capture(
        &self,
        distinct_id: &str,
        event: &str,
        properties: Properties,
        groups: Groups,
    ) {
        self.analytics.capture(distinct_id, event, properties, groups);
    }

    /// Set person properties. Emits `$identify`.
    pub fn identify(&self, distinct_id: &str, properties: Properties) {
        self.analytics.identify(distinct_id, properties);
    }

    /// Set group properties. Emits `$groupidentify`.
    pub fn group_identify(&self, group_type: &str, group_key: &str, properties: Properties) {
        self.analytics.group_identify(group_type, group_key, properties);
    }

    /// Alias one distinct id to another. Emits `$create_alias`.
    pub fn alias(&self, distinct_id: &str, alias: &str) {
        self.analytics.alias(distinct_id, alias);
    }

    /// Flush queued analytics events now.
    pub fn flush(&self) -> Result<()> {
        self.analytics.flush()
    }

    /// Number of events currently queued (test/observability aid).
    pub fn queue_length(&self) -> usize {
        self.analytics.queue_length()
    }

    // ─── Feature flags ──────────────────────────────────────────────────────

    /// Get a flag value: bool, variant key string, or `None` if unknown.
    pub fn get_feature_flag(
        &self,
        key: &str,
        distinct_id: &str,
        opts: &FlagEvalOptions,
    ) -> Result<Option<FlagValue>> {
        self.flags.get_feature_flag(key, distinct_id, opts)
    }

    /// Whether a flag is enabled for the user.
    pub fn is_feature_enabled(
        &self,
        key: &str,
        distinct_id: &str,
        opts: &FlagEvalOptions,
    ) -> Result<bool> {
        self.flags.is_feature_enabled(key, distinct_id, opts)
    }

    /// Get a flag's payload (variant config), local mode.
    pub fn get_feature_flag_payload(
        &self,
        key: &str,
        distinct_id: &str,
        opts: &FlagEvalOptions,
    ) -> Result<Option<Value>> {
        self.flags.get_feature_flag_payload(key, distinct_id, opts)
    }

    /// Evaluate all known flags for a user.
    pub fn get_all_flags(
        &self,
        distinct_id: &str,
        opts: &FlagEvalOptions,
    ) -> Result<HashMap<String, FlagValue>> {
        self.flags.get_all_flags(distinct_id, opts)
    }

    /// Reload local flag definitions (local mode).
    pub fn reload_feature_flag_definitions(&self) -> Result<()> {
        self.flags.reload_feature_flag_definitions()
    }

    /// Direct access to the flags client (advanced: `set_definitions`, etc.).
    pub fn feature_flags(&self) -> &Flags {
        &self.flags
    }

    // ─── Messaging / LLM convenience ─────────────────────────────────────────

    /// Dispatch a message (see [`Messaging::dispatch`]).
    pub fn dispatch_message(&self, params: &DispatchParams) -> Result<Value> {
        self.messaging.dispatch(params)
    }

    /// Capture an LLM trace span (see [`Llm::capture_trace`]).
    pub fn capture_trace(&self, params: &LlmTraceParams) -> Result<Value> {
        self.llm.capture_trace(params)
    }

    // ─── Lifecycle ────────────────────────────────────────────────────────────

    /// Flush remaining events and stop the background flush timer.
    pub fn shutdown(&self) -> Result<()> {
        self.analytics.shutdown()
    }
}

impl Drop for BilldogEng {
    fn drop(&mut self) {
        // Best-effort: stop the background thread and drain the queue.
        let _: std::result::Result<(), BilldogEngError> = self.analytics.shutdown();
    }
}