# FerrisKey SDK FerrisKey SDK is a production-grade Rust SDK for interacting with the FerrisKey API, a comprehensive identity and access management platform. The SDK is built on `tower::Service` for middleware composition, uses TypeState patterns for compile-time safety, and generates typed operation descriptors from the FerrisKey OpenAPI contract. It provides full coverage of all 107 documented API operations across 12 tag groups including authentication, realms, clients, users, roles, federation, identity providers, webhooks, and analytics. The SDK follows a descriptor-driven architecture where all operation metadata is generated at build time from the OpenAPI document. This ensures the SDK and CLI remain in sync with the API contract, enables automated contract validation through Prism mock server integration, and provides type-safe request/response handling without runtime reflection overhead. ## SDK Configuration Configure the SDK with a base URL and optional authentication strategy using a fluent builder pattern with compile-time validation. ```rust use ferriskey_sdk::prelude::*; use std::time::Duration; // Simple configuration with bearer authentication let config = SdkConfig::new( "https://api.ferriskey.example.com", AuthStrategy::Bearer("your-jwt-token".to_string()), ); // Fluent builder pattern with additional options let config = SdkConfig::builder("https://api.ferriskey.example.com") .auth(AuthStrategy::Bearer("your-jwt-token".into())) .timeout(Duration::from_secs(30)) .user_agent("my-app/1.0") .build(); // Check if authentication is configured assert!(config.auth().is_configured()); assert_eq!(config.base_url(), "https://api.ferriskey.example.com"); ``` ## Creating the SDK Client Create an SDK instance with the HpxTransport (default HTTP transport) or any custom tower::Service implementation. ```rust use ferriskey_sdk::prelude::*; // Create SDK with default HpxTransport let config = SdkConfig::new("https://api.ferriskey.example.com", AuthStrategy::None); let sdk = FerriskeySdk::new(config.clone(), HpxTransport::default()); // Using the TypeState builder pattern (compile-time validated) let sdk = FerriskeySdk::builder(config) .transport(HpxTransport::default()) .build(); // Access SDK properties let operations = sdk.operations(); // Get all 107 operation descriptors let realm_ops = sdk.tag("realm"); // Get tag-scoped client for realm operations ``` ## Tower Middleware Composition Compose middleware layers (retry, timeout, rate-limiting) using the tower ecosystem. ```rust use ferriskey_sdk::prelude::*; use tower::ServiceBuilder; use std::time::Duration; // The SDK accepts any tower::Service let transport = ServiceBuilder::new() .timeout(Duration::from_secs(30)) // .retry(ExponentialBackoff::default()) // Add retry logic // .rate_limit(100, Duration::from_secs(60)) // Add rate limiting .service(HpxTransport::default()); let config = SdkConfig::new("https://api.ferriskey.example.com", AuthStrategy::None); let sdk = FerriskeySdk::builder(config) .transport(transport) .build(); ``` ## Executing Operations by ID Execute any documented API operation using its operation ID with typed input parameters. ```rust use ferriskey_sdk::prelude::*; async fn get_realm(sdk: &FerriskeySdk) -> Result<(), SdkError> { // Build operation input with path parameters let input = OperationInput::builder() .path_param("realm", "master") .build(); // Execute by operation ID - returns raw SdkResponse let response = sdk.execute_operation("get_realm", input).await?; println!("Status: {}", response.status); println!("Body: {}", String::from_utf8_lossy(&response.body)); Ok(()) } async fn create_user(sdk: &FerriskeySdk) -> Result<(), SdkError> { // Operation with path params, query params, and JSON body let input = OperationInput::builder() .path_param("realm", "master") .query_param_single("send_verification", "true") .body(br#"{"username": "newuser", "email": "user@example.com"}"#.to_vec()) .header("x-request-id", "req-123") .build(); let response = sdk.execute_operation("create_user", input).await?; Ok(()) } ``` ## Using Tag-Scoped Clients Access operations grouped by API tag for better organization and discoverability. ```rust use ferriskey_sdk::prelude::*; async fn realm_operations(sdk: &FerriskeySdk) -> Result<(), SdkError> { // Get tag-scoped client let realm_client = sdk.tag("realm"); // List all operations in this tag for descriptor in realm_client.descriptors() { println!("Operation: {} - {} {}", descriptor.operation_id, descriptor.method, descriptor.path ); } // Resolve and execute a specific operation within the tag if let Some(operation) = realm_client.operation("get_realm") { let input = OperationInput::builder() .path_param("realm", "master") .build(); let response = operation.execute(input).await?; println!("Realm data: {}", String::from_utf8_lossy(&response.body)); } Ok(()) } ``` ## Decoded Response Handling Execute operations with automatic response decoding based on OpenAPI contract schemas. ```rust use ferriskey_sdk::prelude::*; use ferriskey_sdk::DecodedResponse; async fn get_decoded_response(sdk: &FerriskeySdk) -> Result<(), SdkError> { let operation = sdk.operation("get_realm") .ok_or_else(|| SdkError::UnknownOperation { operation_id: "get_realm".to_string() })?; let input = OperationInput::builder() .path_param("realm", "master") .build(); // Execute with automatic decoding let decoded: DecodedResponse = operation.execute_decoded(input).await?; println!("Status: {}", decoded.status); println!("Schema: {:?}", decoded.schema_name); // Access parsed JSON body if let Some(json) = decoded.json_body() { println!("JSON response: {}", serde_json::to_string_pretty(json).unwrap()); } Ok(()) } ``` ## Typed JSON Response Deserialization Execute requests and deserialize JSON responses into typed Rust structs. ```rust use ferriskey_sdk::prelude::*; use serde::Deserialize; #[derive(Debug, Deserialize)] struct Realm { id: String, name: String, enabled: bool, } async fn get_typed_realm(sdk: &FerriskeySdk) -> Result { let request = SdkRequest::builder("GET", "/realms/master") .auth_required(true) .header("accept", "application/json") .build(); // Execute and deserialize to typed struct let realm: Realm = sdk.execute_json(request, 200).await?; println!("Realm: {} (enabled: {})", realm.name, realm.enabled); Ok(realm) } ``` ## Building SDK Requests Manually Construct low-level SDK requests with full control over method, path, headers, and body. ```rust use ferriskey_sdk::prelude::*; async fn manual_request(sdk: &FerriskeySdk) -> Result<(), SdkError> { // Simple request construction let request = SdkRequest::new("GET", "/realms/master"); // Full builder pattern with all options let request = SdkRequest::builder("POST", "/realms") .header("content-type", "application/json") .header("x-request-id", "req-456") .body(br#"{"name": "new-realm", "enabled": true}"#.to_vec()) .auth_required(true) .build(); // Execute through SDK (handles auth injection and URL resolution) let response = sdk.execute(request).await?; println!("Status: {}", response.status); println!("Headers: {:?}", response.headers); Ok(()) } ``` ## CLI Usage The FerrisKey CLI provides command-line access to all API operations through auto-generated subcommands. ```bash # Basic invocation pattern ferriskey-cli --base-url https://api.ferriskey.example.com \ --bearer-token "your-jwt-token" \ [options] # Get a realm (realm tag, get-realm operation) ferriskey-cli --base-url https://api.ferriskey.example.com \ realm get-realm --realm master # Create a user with JSON body from file ferriskey-cli --base-url https://api.ferriskey.example.com \ --bearer-token "$TOKEN" \ user create-user --realm master --body @user.json # Create user with inline JSON body ferriskey-cli --base-url https://api.ferriskey.example.com \ --bearer-token "$TOKEN" \ user create-user --realm master \ --body '{"username": "newuser", "email": "user@example.com"}' # Pretty-printed JSON output ferriskey-cli --base-url https://api.ferriskey.example.com \ --output pretty \ realm get-realm --realm master # List available operations (help) ferriskey-cli --help ferriskey-cli realm --help ``` ## CLI Programmatic Usage Use the CLI module programmatically for testing or custom integrations. ```rust use ferriskey_sdk::cli::{parse_args, execute_with_transport, CliConfig, OutputFormat}; use ferriskey_sdk::HpxTransport; async fn run_cli() -> Result<(), ferriskey_sdk::cli::CliError> { // Parse command-line arguments let args = [ "ferriskey-cli", "--base-url", "https://api.ferriskey.example.com", "--bearer-token", "secret-token", "--output", "pretty", "realm", "get-realm", "--realm", "master", ]; let invocation = parse_args(args)?; // Execute with default transport let output = execute_with_transport(invocation, HpxTransport::default()).await?; println!("{}", output); Ok(()) } // Get CLI help text programmatically fn show_help() { let help = ferriskey_sdk::cli::render_help(); println!("{}", help); } ``` ## Custom Transport Implementation Implement custom transports by implementing tower::Service for testing or alternative HTTP clients. ```rust use ferriskey_sdk::prelude::*; use ferriskey_sdk::TransportError; use std::collections::BTreeMap; use std::future::Future; use std::pin::Pin; use std::sync::{Arc, Mutex}; use tower::Service; #[derive(Clone)] struct MockTransport { responses: Arc>>, captured: Arc>>, } impl MockTransport { fn new(responses: Vec) -> Self { Self { responses: Arc::new(Mutex::new(responses)), captured: Arc::new(Mutex::new(Vec::new())), } } fn captured_requests(&self) -> Vec { self.captured.lock().unwrap().clone() } } impl Service for MockTransport { type Response = SdkResponse; type Error = TransportError; type Future = Pin> + Send>>; fn poll_ready(&mut self, _cx: &mut std::task::Context<'_>) -> std::task::Poll> { std::task::Poll::Ready(Ok(())) } fn call(&mut self, request: SdkRequest) -> Self::Future { self.captured.lock().unwrap().push(request); let response = self.responses.lock().unwrap().remove(0); Box::pin(async move { Ok(response) }) } } #[tokio::test] async fn test_with_mock_transport() { let mock_response = SdkResponse { status: 200, body: br#"{"id": "master", "name": "Master Realm"}"#.to_vec(), headers: BTreeMap::new(), }; let transport = MockTransport::new(vec![mock_response]); let config = SdkConfig::new("https://api.test", AuthStrategy::None); let sdk = FerriskeySdk::new(config, transport.clone()); let request = SdkRequest::new("GET", "/realms/master"); let response = sdk.execute(request).await.unwrap(); assert_eq!(response.status, 200); assert_eq!(transport.captured_requests().len(), 1); } ``` ## Error Handling Handle SDK errors comprehensively with typed error variants for different failure modes. ```rust use ferriskey_sdk::prelude::*; async fn handle_errors(sdk: &FerriskeySdk) { let input = OperationInput::builder() .path_param("realm", "nonexistent") .build(); match sdk.execute_operation("get_realm", input).await { Ok(response) => { println!("Success: status {}", response.status); } Err(SdkError::MissingAuth) => { eprintln!("Error: Bearer token required but not configured"); } Err(SdkError::MissingParameter { location, name, operation_id }) => { eprintln!("Error: Missing {} parameter '{}' for {}", location, name, operation_id); } Err(SdkError::ApiResponse { status, operation_id, body, .. }) => { eprintln!("API Error {}: {} returned {:?}", status, operation_id, body); } Err(SdkError::UnexpectedStatus { expected, actual }) => { eprintln!("Unexpected status: got {} expected {}", actual, expected); } Err(SdkError::Decode(e)) => { eprintln!("JSON decode error: {}", e); } Err(SdkError::Transport(e)) => { eprintln!("Transport error: {}", e); } Err(e) => { eprintln!("Other error: {}", e); } } } ``` ## Contract Generation and Verification Use the contract module to generate and validate OpenAPI artifacts at build time. ```rust use ferriskey_sdk::contract::{ generate_artifacts, load_contract, normalize_contract, build_registry, source_contract_path, }; use std::path::Path; fn generate_sdk_artifacts() -> Result<(), ferriskey_sdk::contract::ContractError> { let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); // Generate all artifacts from OpenAPI document let artifacts = generate_artifacts(manifest_dir)?; // Access registry metadata println!("Operations: {}", artifacts.registry.operation_count); println!("Schemas: {}", artifacts.registry.schema_count); println!("Tags: {:?}", artifacts.registry.tags); // Iterate over operation descriptors for op in &artifacts.registry.operations { println!("{}: {} {} (auth: {})", op.operation_id, op.method, op.path, op.requires_auth ); } Ok(()) } // Access generated constants at runtime fn inspect_generated_metadata() { use ferriskey_sdk::generated::{OPERATION_COUNT, PATH_COUNT, SCHEMA_COUNT, TAG_NAMES}; println!("Total operations: {}", OPERATION_COUNT); println!("Total paths: {}", PATH_COUNT); println!("Total schemas: {}", SCHEMA_COUNT); println!("API tags: {:?}", TAG_NAMES); } ``` ## Cart and Checkout (Demo) The SDK includes a simple cart/checkout demo for testing and examples. ```rust use ferriskey_sdk::{Cart, CartItem, checkout_cart, greeting}; fn cart_example() { // Create cart items let item1 = CartItem::new("Widget", 1000, 3); // $10.00 x 3 let item2 = CartItem::new("Gadget", 2500, 1); // $25.00 x 1 // Calculate line item total assert_eq!(item1.total_cents(), 3000); // $30.00 // Use cart let mut cart = Cart::new(); cart.add_item(item1); cart.add_item(item2.clone()); assert!(!cart.is_empty()); assert_eq!(cart.total_cents(), 5500); // $55.00 // Checkout creates order and returns empty cart let result = checkout_cart(&[ CartItem::new("Tea", 450, 2), CartItem::new("Cake", 350, 1), ]); assert_eq!(result.order.total_cents, 1250); // $12.50 assert!(result.cart.is_empty()); // Simple greeting function assert_eq!(greeting("FerrisKey"), "Hello, FerrisKey!"); } ``` The FerrisKey SDK provides a complete, type-safe interface for building applications that integrate with the FerrisKey identity platform. The primary use cases include building authentication flows, managing realms and clients, user administration, role-based access control, federation with external identity providers, and monitoring through webhooks and analytics endpoints. The SDK's tower-based architecture makes it ideal for high-performance async applications with sophisticated middleware requirements. Integration patterns typically involve creating a single SDK instance per application with the appropriate authentication configuration, then using either the operation-based API for dynamic use cases or the tag-scoped clients for domain-focused code organization. For testing, the tower::Service abstraction enables easy mocking by implementing a custom transport, while the contract generation system ensures SDK updates stay synchronized with API changes through Prism-based contract verification.