rhailib/docs/API_INTEGRATION_GUIDE.md

13 KiB

API Integration Guide for RhaiLib

Quick Start

This guide shows you how to integrate external APIs with Rhai scripts using RhaiLib's async architecture.

Table of Contents

  1. Setup and Configuration
  2. Basic API Calls
  3. Stripe Payment Integration
  4. Error Handling Patterns
  5. Advanced Usage
  6. Extending to Other APIs

Setup and Configuration

1. Environment Variables

Create a .env file in your project:

# .env
STRIPE_SECRET_KEY=sk_test_your_stripe_key_here
STRIPE_PUBLISHABLE_KEY=pk_test_your_publishable_key_here

2. Rust Setup

use rhailib_dsl::payment::register_payment_rhai_module;
use rhai::{Engine, EvalAltResult, Scope};
use std::env;

fn main() -> Result<(), Box<EvalAltResult>> {
    // Load environment variables
    dotenv::from_filename(".env").ok();
    
    // Create Rhai engine and register payment module
    let mut engine = Engine::new();
    register_payment_rhai_module(&mut engine);
    
    // Set up scope with API credentials
    let mut scope = Scope::new();
    let stripe_key = env::var("STRIPE_SECRET_KEY").unwrap();
    scope.push("STRIPE_API_KEY", stripe_key);
    
    // Execute your Rhai script
    let script = std::fs::read_to_string("payment_script.rhai")?;
    engine.eval_with_scope::<()>(&mut scope, &script)?;
    
    Ok(())
}

3. Rhai Script Configuration

// Configure the API client
let config_result = configure_stripe(STRIPE_API_KEY);
print(`Configuration: ${config_result}`);

Basic API Calls

Simple Product Creation

// Create a basic product
let product = new_product()
    .name("My Product")
    .description("A great product");

try {
    let product_id = product.create();
    print(`✅ Created product: ${product_id}`);
} catch(error) {
    print(`❌ Error: ${error}`);
}

Price Configuration

// One-time payment price
let one_time_price = new_price()
    .amount(1999)  // $19.99 in cents
    .currency("usd")
    .product(product_id);

let price_id = one_time_price.create();

// Subscription price
let monthly_price = new_price()
    .amount(999)   // $9.99 in cents
    .currency("usd")
    .product(product_id)
    .recurring("month");

let monthly_price_id = monthly_price.create();

Stripe Payment Integration

Complete Payment Workflow

// 1. Configure Stripe
configure_stripe(STRIPE_API_KEY);

// 2. Create Product
let product = new_product()
    .name("Premium Software License")
    .description("Professional software solution")
    .metadata("category", "software")
    .metadata("tier", "premium");

let product_id = product.create();

// 3. Create Pricing Options
let monthly_price = new_price()
    .amount(2999)  // $29.99
    .currency("usd")
    .product(product_id)
    .recurring("month")
    .metadata("billing", "monthly");

let annual_price = new_price()
    .amount(29999) // $299.99 (save $60)
    .currency("usd")
    .product(product_id)
    .recurring("year")
    .metadata("billing", "annual")
    .metadata("discount", "save_60");

let monthly_price_id = monthly_price.create();
let annual_price_id = annual_price.create();

// 4. Create Discount Coupons
let welcome_coupon = new_coupon()
    .duration("once")
    .percent_off(25)
    .metadata("campaign", "welcome_offer");

let coupon_id = welcome_coupon.create();

// 5. Create Payment Intent for One-time Purchase
let payment_intent = new_payment_intent()
    .amount(2999)
    .currency("usd")
    .customer("cus_customer_id")
    .description("Monthly subscription payment")
    .add_payment_method_type("card")
    .metadata("price_id", monthly_price_id);

let intent_id = payment_intent.create();

// 6. Create Subscription
let subscription = new_subscription()
    .customer("cus_customer_id")
    .add_price(monthly_price_id)
    .trial_days(14)
    .coupon(coupon_id)
    .metadata("source", "website");

let subscription_id = subscription.create();

Builder Pattern Examples

Product with Metadata

let product = new_product()
    .name("Enterprise Software")
    .description("Full-featured business solution")
    .metadata("category", "enterprise")
    .metadata("support_level", "premium")
    .metadata("deployment", "cloud");

Complex Pricing

let tiered_price = new_price()
    .amount(4999)  // $49.99
    .currency("usd")
    .product(product_id)
    .recurring_with_count("month", 12)  // 12 monthly payments
    .metadata("tier", "professional")
    .metadata("features", "advanced");

Multi-item Subscription

let enterprise_subscription = new_subscription()
    .customer("cus_enterprise_customer")
    .add_price_with_quantity(user_license_price_id, 50)  // 50 user licenses
    .add_price(support_addon_price_id)                   // Premium support
    .add_price(analytics_addon_price_id)                 // Analytics addon
    .trial_days(30)
    .metadata("plan", "enterprise")
    .metadata("contract_length", "annual");

Error Handling Patterns

Basic Error Handling

try {
    let result = some_api_call();
    print(`Success: ${result}`);
} catch(error) {
    print(`Error occurred: ${error}`);
    // Continue with fallback logic
}

Graceful Degradation

// Try to create with coupon, fallback without coupon
let subscription_id;
try {
    subscription_id = new_subscription()
        .customer(customer_id)
        .add_price(price_id)
        .coupon(coupon_id)
        .create();
} catch(error) {
    print(`Coupon failed: ${error}, creating without coupon`);
    subscription_id = new_subscription()
        .customer(customer_id)
        .add_price(price_id)
        .create();
}

Validation Before API Calls

// Validate inputs before making API calls
if customer_id == "" {
    print("❌ Customer ID is required");
    return;
}

if price_id == "" {
    print("❌ Price ID is required");
    return;
}

// Proceed with API call
let subscription = new_subscription()
    .customer(customer_id)
    .add_price(price_id)
    .create();

Advanced Usage

Conditional Logic

// Different pricing based on customer type
let price_id;
if customer_type == "enterprise" {
    price_id = enterprise_price_id;
} else if customer_type == "professional" {
    price_id = professional_price_id;
} else {
    price_id = standard_price_id;
}

let subscription = new_subscription()
    .customer(customer_id)
    .add_price(price_id);

// Add trial for new customers
if is_new_customer {
    subscription = subscription.trial_days(14);
}

let subscription_id = subscription.create();

Dynamic Metadata

// Build metadata dynamically
let product = new_product()
    .name(product_name)
    .description(product_description);

// Add metadata based on conditions
if has_support {
    product = product.metadata("support", "included");
}

if is_premium {
    product = product.metadata("tier", "premium");
}

if region != "" {
    product = product.metadata("region", region);
}

let product_id = product.create();

Bulk Operations

// Create multiple prices for a product
let price_configs = [
    #{amount: 999, interval: "month", name: "Monthly"},
    #{amount: 9999, interval: "year", name: "Annual"},
    #{amount: 19999, interval: "", name: "Lifetime"}
];

let price_ids = [];
for config in price_configs {
    let price = new_price()
        .amount(config.amount)
        .currency("usd")
        .product(product_id)
        .metadata("plan_name", config.name);
    
    if config.interval != "" {
        price = price.recurring(config.interval);
    }
    
    let price_id = price.create();
    price_ids.push(price_id);
    print(`Created ${config.name} price: ${price_id}`);
}

Extending to Other APIs

Adding New API Support

To extend the architecture to other APIs, follow this pattern:

1. Define Configuration Structure

#[derive(Debug, Clone)]
pub struct CustomApiConfig {
    pub api_key: String,
    pub base_url: String,
    pub client: Client,
}

2. Implement Request Handler

async fn handle_custom_api_request(
    config: &CustomApiConfig, 
    request: &AsyncRequest
) -> Result<String, String> {
    let url = format!("{}/{}", config.base_url, request.endpoint);
    
    let response = config.client
        .request(Method::from_str(&request.method).unwrap(), &url)
        .header("Authorization", format!("Bearer {}", config.api_key))
        .json(&request.data)
        .send()
        .await
        .map_err(|e| format!("Request failed: {}", e))?;
    
    let response_text = response.text().await
        .map_err(|e| format!("Failed to read response: {}", e))?;
    
    Ok(response_text)
}

3. Register Rhai Functions

#[rhai_fn(name = "custom_api_call", return_raw)]
pub fn custom_api_call(
    endpoint: String, 
    data: rhai::Map
) -> Result<String, Box<EvalAltResult>> {
    let registry = CUSTOM_API_REGISTRY.lock().unwrap();
    let registry = registry.as_ref().ok_or("API not configured")?;
    
    let form_data: HashMap<String, String> = data.into_iter()
        .map(|(k, v)| (k.to_string(), v.to_string()))
        .collect();
    
    registry.make_request(endpoint, "POST".to_string(), form_data)
        .map_err(|e| e.to_string().into())
}

Example: GitHub API Integration

// Hypothetical GitHub API integration
configure_github_api(GITHUB_TOKEN);

// Create a repository
let repo_data = #{
    name: "my-new-repo",
    description: "Created via Rhai script",
    private: false
};

let repo_result = github_api_call("user/repos", repo_data);
print(`Repository created: ${repo_result}`);

// Create an issue
let issue_data = #{
    title: "Initial setup",
    body: "Setting up the repository structure",
    labels: ["enhancement", "setup"]
};

let issue_result = github_api_call("repos/user/my-new-repo/issues", issue_data);
print(`Issue created: ${issue_result}`);

Performance Tips

1. Batch Operations

// Instead of creating items one by one, batch when possible
let items_to_create = [item1, item2, item3];
let created_items = [];

for item in items_to_create {
    try {
        let result = item.create();
        created_items.push(result);
    } catch(error) {
        print(`Failed to create item: ${error}`);
    }
}

2. Reuse Configuration

// Configure once, use multiple times
configure_stripe(STRIPE_API_KEY);

// Multiple operations use the same configuration
let product1_id = new_product().name("Product 1").create();
let product2_id = new_product().name("Product 2").create();
let price1_id = new_price().product(product1_id).amount(1000).create();
let price2_id = new_price().product(product2_id).amount(2000).create();

3. Error Recovery

// Implement retry logic for transient failures
let max_retries = 3;
let retry_count = 0;
let success = false;

while retry_count < max_retries && !success {
    try {
        let result = api_operation();
        success = true;
        print(`Success: ${result}`);
    } catch(error) {
        retry_count += 1;
        print(`Attempt ${retry_count} failed: ${error}`);
        if retry_count < max_retries {
            print("Retrying...");
        }
    }
}

if !success {
    print("❌ All retry attempts failed");
}

Debugging and Monitoring

Enable Detailed Logging

// The architecture automatically logs key operations:
// 🔧 Configuring Stripe...
// 🚀 Async worker thread started
// 🔄 Processing POST request to products
// 📥 Stripe response: {...}
// ✅ Request successful with ID: prod_xxx

Monitor Request Performance

// Time API operations
let start_time = timestamp();
let result = expensive_api_operation();
let end_time = timestamp();
print(`Operation took ${end_time - start_time}ms`);

Handle Rate Limits

// Implement backoff for rate-limited APIs
try {
    let result = api_call();
} catch(error) {
    if error.contains("rate limit") {
        print("Rate limited, waiting before retry...");
        // In a real implementation, you'd add delay logic
    }
}

Best Practices Summary

  1. Always handle errors gracefully - Use try/catch blocks for all API calls
  2. Validate inputs - Check required fields before making API calls
  3. Use meaningful metadata - Add context to help with debugging and analytics
  4. Configure once, use many - Set up API clients once and reuse them
  5. Implement retry logic - Handle transient network failures
  6. Monitor performance - Track API response times and success rates
  7. Secure credentials - Use environment variables for API keys
  8. Test with demo data - Use test API keys during development

This architecture provides a robust foundation for integrating any HTTP-based API with Rhai scripts while maintaining the simplicity and safety that makes Rhai attractive for domain-specific scripting.