530 lines
13 KiB
Markdown
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. |