rhailib/docs/API_INTEGRATION_GUIDE.md

530 lines
13 KiB
Markdown

# 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](#setup-and-configuration)
2. [Basic API Calls](#basic-api-calls)
3. [Stripe Payment Integration](#stripe-payment-integration)
4. [Error Handling Patterns](#error-handling-patterns)
5. [Advanced Usage](#advanced-usage)
6. [Extending to Other APIs](#extending-to-other-apis)
## Setup and Configuration
### 1. Environment Variables
Create a `.env` file in your project:
```bash
# .env
STRIPE_SECRET_KEY=sk_test_your_stripe_key_here
STRIPE_PUBLISHABLE_KEY=pk_test_your_publishable_key_here
```
### 2. Rust Setup
```rust
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
```rhai
// Configure the API client
let config_result = configure_stripe(STRIPE_API_KEY);
print(`Configuration: ${config_result}`);
```
## Basic API Calls
### Simple Product Creation
```rhai
// 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
```rhai
// 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
```rhai
// 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
```rhai
let product = new_product()
.name("Enterprise Software")
.description("Full-featured business solution")
.metadata("category", "enterprise")
.metadata("support_level", "premium")
.metadata("deployment", "cloud");
```
#### Complex Pricing
```rhai
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
```rhai
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
```rhai
try {
let result = some_api_call();
print(`Success: ${result}`);
} catch(error) {
print(`Error occurred: ${error}`);
// Continue with fallback logic
}
```
### Graceful Degradation
```rhai
// 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
```rhai
// 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
```rhai
// 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
```rhai
// 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
```rhai
// 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
```rust
#[derive(Debug, Clone)]
pub struct CustomApiConfig {
pub api_key: String,
pub base_url: String,
pub client: Client,
}
```
#### 2. Implement Request Handler
```rust
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
```rust
#[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
```rhai
// 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
```rhai
// 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
```rhai
// 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
```rhai
// 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
```rhai
// 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
```rhai
// 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
```rhai
// 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.