Rust SDK

The official Rust SDK for Locci Scheduler provides a type-safe, async API for scheduling webhook tasks using the Tokio runtime.

Installation

Add to your Cargo.toml:

[dependencies]
locci-scheduler = "0.1.0"
tokio = { version = "1", features = ["full"] }
chrono = "0.4"
serde_json = "1.0"

Or add with Cargo toolchain

cargo add locci-scheduler

Quick Start

use locci_scheduler::{LocciScheduler, SchedulerConfig, WebhookConfig, HttpMethod};
use chrono::Utc;

#[tokio::main]
async fn main() -> locci_scheduler::Result<()> {
    let config = SchedulerConfig {
        base_url: "https://api.scheduler.locci.cloud".to_string(),
        api_token: "your-api-key".to_string(),
        timeout: 30000,
        retries: 3,
    };

    let scheduler = LocciScheduler::new(config)?;

    // Create a one-time task
    let webhook = WebhookConfig {
        url: "https://your-app.com/webhook".to_string(),
        method: Some(HttpMethod::Post),
        headers: None,
        payload: Some(serde_json::json!({ "message": "Hello!" })),
        timeout_seconds: Some(30),
        retry_config: None,
        authentication: None,
        webhook_secret: None,
    };

    let execute_at = Utc::now() + chrono::Duration::minutes(5);
    let task = scheduler.schedule_once(
        "My First Task".to_string(),
        webhook,
        execute_at,
        Some("A test task".to_string()),
        None,
    ).await?;

    println!("Task created: {}", task.id);
    Ok(())
}

Configuration

#![allow(unused)]
fn main() {
use locci_scheduler::SchedulerConfig;

let config = SchedulerConfig {
    base_url: "https://api.scheduler.locci.cloud".to_string(),
    api_token: "your-api-key".to_string(),
    timeout: 30000,  // Request timeout in milliseconds
    retries: 3,      // Number of retry attempts
};

let scheduler = LocciScheduler::new(config)?;
}

Environment Variables

For production, use environment variables:

#![allow(unused)]
fn main() {
use std::env;

let config = SchedulerConfig {
    base_url: env::var("LOCCI_SCHEDULER_URL")
        .unwrap_or_else(|_| "https://api.scheduler.locci.cloud".to_string()),
    api_token: env::var("LOCCI_SCHEDULER_TOKEN")
        .expect("LOCCI_SCHEDULER_TOKEN must be set"),
    timeout: 30000,
    retries: 3,
};
}

Task Management

Create a Task

#![allow(unused)]
fn main() {
use locci_scheduler::{CreateTaskOptions, WebhookConfig, ScheduleConfig, HttpMethod};
use chrono::Utc;
use std::collections::HashMap;

let webhook = WebhookConfig {
    url: "https://your-app.com/api/reports".to_string(),
    method: Some(HttpMethod::Post),
    headers: Some({
        let mut h = HashMap::new();
        h.insert("X-Custom-Header".to_string(), "value".to_string());
        h
    }),
    payload: Some(serde_json::json!({
        "reportType": "sales",
        "period": "daily"
    })),
    timeout_seconds: Some(30),
    retry_config: None,
    authentication: None,
    webhook_secret: None,
};

let task = scheduler.create_task(CreateTaskOptions {
    name: "Daily Report".to_string(),
    description: Some("Send daily sales report".to_string()),
    webhook,
    schedule: ScheduleConfig::Recurring {
        cron_expression: "0 9 * * *".to_string(),
        timezone: Some("Africa/Nairobi".to_string()),
        end_date: None,
    },
    metadata: Some({
        let mut m = HashMap::new();
        m.insert("department".to_string(), serde_json::json!("sales"));
        m
    }),
}).await?;
}

Get a Task

#![allow(unused)]
fn main() {
let task = scheduler.get_task("task-id-here").await?;
println!("Task: {} - Status: {:?}", task.name, task.status);
}

List Tasks

#![allow(unused)]
fn main() {
use locci_scheduler::PaginationOptions;

let task_list = scheduler.list_tasks(PaginationOptions {
    page: Some(1),
    per_page: Some(10),
}).await?;

println!("Total tasks: {}", task_list.total);
for task in &task_list.tasks {
    println!("- {} ({:?})", task.name, task.status);
}
}

Update a Task

#![allow(unused)]
fn main() {
use locci_scheduler::UpdateTaskOptions;

let updated_task = scheduler.update_task("task-id", UpdateTaskOptions {
    name: Some("Updated Task Name".to_string()),
    description: Some("New description".to_string()),
    ..Default::default()
}).await?;
}

Delete a Task

#![allow(unused)]
fn main() {
scheduler.delete_task("task-id").await?;
}

Task Lifecycle

Pause a Task

#![allow(unused)]
fn main() {
let task = scheduler.pause_task("task-id").await?;
println!("Task status: {:?}", task.status); // Paused
}

Resume a Task

#![allow(unused)]
fn main() {
let task = scheduler.resume_task("task-id").await?;
println!("Task status: {:?}", task.status); // Active
}

Cancel a Task

#![allow(unused)]
fn main() {
let task = scheduler.cancel_task("task-id").await?;
println!("Task status: {:?}", task.status); // Cancelled
}

Manually Trigger a Task

#![allow(unused)]
fn main() {
scheduler.trigger_task("task-id").await?;
}

Scheduling Methods

One-Time Execution

#![allow(unused)]
fn main() {
use chrono::{Utc, TimeZone};

let webhook = WebhookConfig {
    url: "https://your-app.com/api/emails/welcome".to_string(),
    method: Some(HttpMethod::Post),
    headers: None,
    payload: Some(serde_json::json!({ "userId": "user123" })),
    timeout_seconds: Some(30),
    retry_config: None,
    authentication: None,
    webhook_secret: None,
};

let execute_at = Utc.with_ymd_and_hms(2024, 12, 25, 9, 0, 0).unwrap();

let task = scheduler.schedule_once(
    "Send Welcome Email".to_string(),
    webhook,
    execute_at,
    Some("Send welcome email to new user".to_string()),
    Some({
        let mut m = HashMap::new();
        m.insert("userId".to_string(), serde_json::json!("user123"));
        m
    }),
).await?;
}

Recurring with Cron Expression

#![allow(unused)]
fn main() {
let task = scheduler.schedule_recurring(
    "Weekly Backup".to_string(),
    webhook,
    "0 2 * * 0".to_string(), // Sundays at 2 AM
    Some("UTC".to_string()),
    Some(Utc.with_ymd_and_hms(2025, 12, 31, 23, 59, 59).unwrap()), // End date
    Some("Weekly database backup".to_string()),
    None,
).await?;
}

Interval-Based

#![allow(unused)]
fn main() {
let task = scheduler.schedule_interval(
    "Health Check".to_string(),
    webhook,
    300, // Every 5 minutes (300 seconds)
    Some(Utc::now()), // Start immediately
    None, // No end date
    Some("Monitor API health".to_string()),
    None,
).await?;
}

Daily at Specific Time

#![allow(unused)]
fn main() {
let task = scheduler.schedule_daily(
    "Daily Sales Report".to_string(),
    webhook,
    "09:00", // 9:00 AM (24-hour format)
    Some("Africa/Nairobi".to_string()),
    Some("Generate and send daily sales report".to_string()),
    None,
).await?;
}

Weekly

#![allow(unused)]
fn main() {
let task = scheduler.schedule_weekly(
    "Weekly Team Standup Reminder".to_string(),
    webhook,
    1, // Monday (0 = Sunday, 1 = Monday, etc.)
    "09:45",
    Some("Africa/Nairobi".to_string()),
    Some("Remind team about standup".to_string()),
    None,
).await?;
}

Convenience Methods

#![allow(unused)]
fn main() {
// Every minute
let task = scheduler.schedule_every_minute(
    "Minute Task".to_string(),
    webhook.clone(),
    None,
    None,
).await?;

// Every hour
let task = scheduler.schedule_every_hour(
    "Hourly Task".to_string(),
    webhook,
    None,
    None,
).await?;
}

Webhook Configuration

Basic Webhook

#![allow(unused)]
fn main() {
use std::collections::HashMap;

let webhook = WebhookConfig {
    url: "https://your-app.com/webhook".to_string(),
    method: Some(HttpMethod::Post),
    headers: Some({
        let mut h = HashMap::new();
        h.insert("Content-Type".to_string(), "application/json".to_string());
        h.insert("X-Custom-Header".to_string(), "value".to_string());
        h
    }),
    payload: Some(serde_json::json!({ "key": "value" })),
    timeout_seconds: Some(30),
    retry_config: None,
    authentication: None,
    webhook_secret: None,
};
}

With Retry Configuration

#![allow(unused)]
fn main() {
use locci_scheduler::RetryConfig;

let webhook = WebhookConfig {
    url: "https://your-app.com/webhook".to_string(),
    method: Some(HttpMethod::Post),
    headers: None,
    payload: None,
    timeout_seconds: Some(30),
    retry_config: Some(RetryConfig {
        max_attempts: 5,
        backoff_seconds: 2,
        backoff_multiplier: 2.0,
        max_backoff_seconds: 300,
    }),
    authentication: None,
    webhook_secret: None,
};
}

Authentication Types

API Key

#![allow(unused)]
fn main() {
use locci_scheduler::AuthConfig;

let webhook = WebhookConfig {
    url: "https://your-app.com/webhook".to_string(),
    method: Some(HttpMethod::Post),
    headers: None,
    payload: None,
    timeout_seconds: Some(30),
    retry_config: None,
    authentication: Some(AuthConfig::ApiKey {
        header_name: "X-API-Key".to_string(),
        key: "your-api-key-value".to_string(),
    }),
    webhook_secret: None,
};
}

Bearer Token

#![allow(unused)]
fn main() {
let webhook = WebhookConfig {
    url: "https://your-app.com/webhook".to_string(),
    method: Some(HttpMethod::Post),
    headers: None,
    payload: None,
    timeout_seconds: Some(30),
    retry_config: None,
    authentication: Some(AuthConfig::BearerToken {
        token: "your-bearer-token".to_string(),
    }),
    webhook_secret: None,
};
}

Basic Auth

#![allow(unused)]
fn main() {
let webhook = WebhookConfig {
    url: "https://your-app.com/webhook".to_string(),
    method: Some(HttpMethod::Post),
    headers: None,
    payload: None,
    timeout_seconds: Some(30),
    retry_config: None,
    authentication: Some(AuthConfig::BasicAuth {
        username: "user".to_string(),
        password: "password".to_string(),
    }),
    webhook_secret: None,
};
}

HMAC Webhook Signature

For secure webhook verification:

#![allow(unused)]
fn main() {
let webhook = WebhookConfig {
    url: "https://your-app.com/webhook".to_string(),
    method: Some(HttpMethod::Post),
    headers: None,
    payload: Some(serde_json::json!({ "data": "sensitive" })),
    timeout_seconds: Some(30),
    retry_config: None,
    authentication: None,
    webhook_secret: Some("your-webhook-secret".to_string()),
};
}

The scheduler will include an X-Locci-Signature header with an HMAC-SHA256 signature. Verify on your server:

#![allow(unused)]
fn main() {
use hmac::{Hmac, Mac};
use sha2::Sha256;

type HmacSha256 = Hmac<Sha256>;

fn verify_signature(payload: &str, signature: &str, secret: &str) -> bool {
    let mut mac = HmacSha256::new_from_slice(secret.as_bytes())
        .expect("HMAC can take key of any size");
    mac.update(payload.as_bytes());

    let expected = hex::encode(mac.finalize().into_bytes());
    signature == expected
}
}

Execution History

#![allow(unused)]
fn main() {
let history = scheduler.get_task_history("task-id").await?;

println!("Total executions: {}", history.total);
for exec in &history.executions {
    println!("- {:?}: {:?}", exec.executed_at, exec.status);
    if let Some(ref error) = exec.error_message {
        println!("  Error: {}", error);
    }
}
}

Billing & Subscription

Get Current Subscription

#![allow(unused)]
fn main() {
let subscription = scheduler.get_subscription().await?;
println!("Plan: {:?}", subscription.plan);
println!("Status: {}", subscription.status);
}

Subscribe to a Plan

#![allow(unused)]
fn main() {
use locci_scheduler::SubscriptionPlan;

let subscription = scheduler.subscribe(
    SubscriptionPlan::Pro,
    "254712345678".to_string(), // M-Pesa phone number
).await?;
}

Get Usage

#![allow(unused)]
fn main() {
let usage = scheduler.get_usage().await?;
println!("Tasks created: {}", usage.current_usage.tasks_created);
println!("Webhook executions: {}", usage.current_usage.webhook_executions);
println!("API calls: {}", usage.current_usage.api_calls);
println!("Max tasks: {:?}", usage.limits.max_tasks);
}

Cancel Subscription

#![allow(unused)]
fn main() {
let subscription = scheduler.cancel_subscription().await?;
}

Error Handling

The SDK provides a comprehensive error type:

#![allow(unused)]
fn main() {
use locci_scheduler::SchedulerError;

match scheduler.create_task(options).await {
    Ok(task) => println!("Task created: {}", task.id),
    Err(SchedulerError::AuthenticationError(msg)) => {
        eprintln!("Authentication failed: {}", msg);
    }
    Err(SchedulerError::ValidationError(msg)) => {
        eprintln!("Invalid configuration: {}", msg);
    }
    Err(SchedulerError::NotFoundError(msg)) => {
        eprintln!("Resource not found: {}", msg);
    }
    Err(SchedulerError::RateLimitError(msg)) => {
        eprintln!("Rate limit exceeded: {}", msg);
    }
    Err(SchedulerError::ApiError { status, message }) => {
        eprintln!("API error ({}): {}", status, message);
    }
    Err(e) => eprintln!("Unexpected error: {}", e),
}
}

Error Types

#![allow(unused)]
fn main() {
pub enum SchedulerError {
    AuthenticationError(String),
    ValidationError(String),
    NotFoundError(String),
    RateLimitError(String),
    ApiError { status: u16, message: String },
    NetworkError(reqwest::Error),
    JsonError(serde_json::Error),
}
}

Types Reference

Task Status

#![allow(unused)]
fn main() {
pub enum TaskStatus {
    Active,
    Paused,
    Completed,
    Failed,
    Cancelled,
}
}

Execution Status

#![allow(unused)]
fn main() {
pub enum ExecutionStatus {
    Success,
    Failed,
    Retrying,
    TimedOut,
}
}

HTTP Methods

#![allow(unused)]
fn main() {
pub enum HttpMethod {
    Get,
    Post,
    Put,
    Patch,
    Delete,
}
}

Schedule Config

#![allow(unused)]
fn main() {
pub enum ScheduleConfig {
    Once {
        execute_at: DateTime<Utc>,
    },
    Recurring {
        cron_expression: String,
        timezone: Option<String>,
        end_date: Option<DateTime<Utc>>,
    },
    Interval {
        interval_seconds: u64,
        start_at: Option<DateTime<Utc>>,
        end_date: Option<DateTime<Utc>>,
    },
}
}

Subscription Plans

#![allow(unused)]
fn main() {
pub enum SubscriptionPlan {
    Free,
    Starter,
    Pro,
    Business,
}
}

Complete Example

use locci_scheduler::{
    LocciScheduler, SchedulerConfig, WebhookConfig, HttpMethod,
    PaginationOptions, SchedulerError,
};
use chrono::Utc;
use std::collections::HashMap;
use std::env;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize client
    let config = SchedulerConfig {
        base_url: env::var("LOCCI_SCHEDULER_URL")
            .unwrap_or_else(|_| "https://api.scheduler.locci.cloud".to_string()),
        api_token: env::var("LOCCI_SCHEDULER_TOKEN")
            .expect("LOCCI_SCHEDULER_TOKEN must be set"),
        timeout: 30000,
        retries: 3,
    };

    let scheduler = LocciScheduler::new(config)?;

    // Create webhook config
    let webhook = WebhookConfig {
        url: "https://httpbin.org/post".to_string(),
        method: Some(HttpMethod::Post),
        headers: Some({
            let mut h = HashMap::new();
            h.insert("Content-Type".to_string(), "application/json".to_string());
            h
        }),
        payload: Some(serde_json::json!({
            "message": "Hello from Locci Scheduler!",
            "timestamp": Utc::now().to_rfc3339()
        })),
        timeout_seconds: Some(30),
        retry_config: None,
        authentication: None,
        webhook_secret: None,
    };

    // Schedule a task
    let task = scheduler.schedule_interval(
        "Test Task".to_string(),
        webhook,
        60, // Every minute
        Some(Utc::now()),
        None,
        Some("Test task for demonstration".to_string()),
        None,
    ).await?;

    println!("Created task: {}", task.id);

    // List all tasks
    let tasks = scheduler.list_tasks(PaginationOptions {
        page: Some(1),
        per_page: Some(10),
    }).await?;

    println!("\nAll tasks ({}):", tasks.total);
    for t in &tasks.tasks {
        println!("  - {} ({:?})", t.name, t.status);
    }

    // Get usage information
    match scheduler.get_usage().await {
        Ok(usage) => {
            println!("\nUsage:");
            println!("  Tasks created: {}", usage.current_usage.tasks_created);
            println!("  Webhook executions: {}", usage.current_usage.webhook_executions);
            println!("  Plan: {:?}", usage.subscription.plan);
        }
        Err(e) => eprintln!("Failed to get usage: {}", e),
    }

    // Pause the task
    let paused_task = scheduler.pause_task(&task.id).await?;
    println!("\nTask paused: {:?}", paused_task.status);

    // Resume the task
    let resumed_task = scheduler.resume_task(&task.id).await?;
    println!("Task resumed: {:?}", resumed_task.status);

    // Delete the task
    scheduler.delete_task(&task.id).await?;
    println!("Task deleted");

    Ok(())
}

Resources