13 KiB
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
- Setup and Configuration
- Basic API Calls
- Stripe Payment Integration
- Error Handling Patterns
- Advanced Usage
- 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
- Always handle errors gracefully - Use try/catch blocks for all API calls
- Validate inputs - Check required fields before making API calls
- Use meaningful metadata - Add context to help with debugging and analytics
- Configure once, use many - Set up API clients once and reuse them
- Implement retry logic - Handle transient network failures
- Monitor performance - Track API response times and success rates
- Secure credentials - Use environment variables for API keys
- 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.