Togglr SDK for Elixir
Official Elixir SDK for Togglr feature flag management system. This SDK provides a simple and efficient way to evaluate feature flags in your Elixir applications with built-in caching, retry logic, and error handling.
Features
- Simple API: Easy-to-use interface for evaluating feature flags
- Context-aware: Support for user attributes, country, and custom properties
- Built-in Caching: LRU cache with TTL for improved performance
- Retry Logic: Configurable exponential backoff for failed requests
- Error Handling: Specific exceptions for different error scenarios
- Telemetry: Built-in telemetry events for monitoring and observability
- Type Safety: Comprehensive type specifications and documentation
Installation
Add togglr_sdk to your list of dependencies in mix.exs:
def deps do
[
{:togglr_sdk, "~> 1.0"}
]
end
Then run mix deps.get to install the dependencies.
Quick Start
# Create a client
{:ok, client} = TogglrSdk.new_client("your-api-key")
# Create a context
context = TogglrSdk.RequestContext.new()
|> TogglrSdk.RequestContext.with_user_id("123")
|> TogglrSdk.RequestContext.with_country("US")
# Evaluate a feature
{:ok, result} = TogglrSdk.Client.evaluate(client, "new_ui", context)
IO.puts("Feature enabled: #{result.enabled}")
# Check if feature is enabled
{:ok, enabled} = TogglrSdk.Client.is_enabled(client, "new_ui", context)
IO.puts("Feature enabled: #{enabled}")
# Check with default value
{:ok, enabled} = TogglrSdk.Client.is_enabled_or_default(client, "new_ui", context, false)
IO.puts("Feature enabled: #{enabled}")
# Health check
{:ok, healthy} = TogglrSdk.Client.health_check(client)
IO.puts("API healthy: #{healthy}")
# Clean up
:ok = TogglrSdk.Client.close(client)Configuration
The SDK can be configured with various options:
# Using the convenience function
opts = [
base_url: "https://api.togglr.com",
timeout: 60000,
retries: 5,
cache_enabled: true,
cache_max_size: 2000,
cache_ttl: 120,
insecure: true # Skip SSL verification for self-signed certificates
]
{:ok, client} = TogglrSdk.new_client("your-api-key", opts)
# Or using the configuration module directly
config = TogglrSdk.Config.default("your-api-key")
|> TogglrSdk.Config.with_base_url("https://api.togglr.com")
|> TogglrSdk.Config.with_timeout(60000)
|> TogglrSdk.Config.with_retries(5)
|> TogglrSdk.Config.with_cache(true, 2000, 120)
|> TogglrSdk.Config.with_logger(MyLogger)
{:ok, client} = TogglrSdk.Client.new(config)Configuration Options
api_key- Your Togglr API key (required)base_url- API base URL (default: “http://localhost:8090”)timeout- Request timeout in milliseconds (default: 30000)retries- Number of retries for failed requests (default: 3)cache_enabled- Enable caching (default: true)cache_max_size- Maximum number of entries in cache (default: 1000)cache_ttl- Cache TTL in seconds (default: 60)logger- Logger module (default: Logger)insecure- Skip SSL verification (default: false)client_cert- Path to client certificate fileclient_key- Path to client private key fileca_cert- Path to CA certificate file
TLS Configuration
The SDK supports TLS client certificates and custom CA certificates for secure connections:
# Basic TLS with client certificate
{:ok, client} = TogglrSdk.new_client("your-api-key", [
base_url: "https://api.togglr.com",
client_cert: "/path/to/client.crt",
client_key: "/path/to/client.key"
])
# TLS with custom CA certificate
{:ok, client} = TogglrSdk.new_client("your-api-key", [
base_url: "https://api.togglr.com",
ca_cert: "/path/to/ca.crt"
])
# Full TLS configuration
{:ok, client} = TogglrSdk.new_client("your-api-key", [
base_url: "https://api.togglr.com",
client_cert: "/path/to/client.crt",
client_key: "/path/to/client.key",
ca_cert: "/path/to/ca.crt"
])
# Using configuration builder pattern
config = TogglrSdk.Config.default("your-api-key")
|> TogglrSdk.Config.with_base_url("https://api.togglr.com")
|> TogglrSdk.Config.with_client_cert_and_key("/path/to/client.crt", "/path/to/client.key")
|> TogglrSdk.Config.with_ca_cert("/path/to/ca.crt")
{:ok, client} = TogglrSdk.Client.new(config)
# Insecure mode (skip TLS verification) - use with caution
{:ok, client} = TogglrSdk.new_client("your-api-key", [
base_url: "https://api.togglr.com",
insecure: true
])Available TLS configuration methods:
TogglrSdk.Config.with_client_cert/2- Set client certificate file pathTogglrSdk.Config.with_client_key/2- Set client private key file pathTogglrSdk.Config.with_client_cert_and_key/3- Set both client certificate and keyTogglrSdk.Config.with_ca_cert/2- Set CA certificate file path for server verificationTogglrSdk.Config.with_insecure/1- Skip TLS verification (not recommended for production)
Usage
Request Context
The request context allows you to provide user attributes and other data for feature evaluation. The SDK provides many convenient with_* methods for common attributes:
# Basic user information
context = TogglrSdk.RequestContext.new()
|> TogglrSdk.RequestContext.with_user_id("123")
|> TogglrSdk.RequestContext.with_user_email("user@example.com")
|> TogglrSdk.RequestContext.with_anonymous(false)
# Location information
|> TogglrSdk.RequestContext.with_country("US")
|> TogglrSdk.RequestContext.with_region("us-west")
|> TogglrSdk.RequestContext.with_city("San Francisco")
# Device information
|> TogglrSdk.RequestContext.with_device_type("mobile")
|> TogglrSdk.RequestContext.with_manufacturer("Apple")
|> TogglrSdk.RequestContext.with_os("iOS")
|> TogglrSdk.RequestContext.with_os_version("15.0")
# Browser information
|> TogglrSdk.RequestContext.with_browser("Safari")
|> TogglrSdk.RequestContext.with_browser_version("15.0")
# User demographics
|> TogglrSdk.RequestContext.with_age(25)
|> TogglrSdk.RequestContext.with_gender("female")
# Technical details
|> TogglrSdk.RequestContext.with_language("en-US")
|> TogglrSdk.RequestContext.with_connection_type("wifi")
|> TogglrSdk.RequestContext.with_ip("192.168.1.1")
|> TogglrSdk.RequestContext.with_app_version("1.2.3")
|> TogglrSdk.RequestContext.with_platform("ios")
# Custom attributes
|> TogglrSdk.RequestContext.set("plan", "premium")
|> TogglrSdk.RequestContext.set_many(%{
"user.role" => "admin",
"user.team" => "engineering"
})Available Context Methods
User Information:
with_user_id/2- Set user IDwith_user_email/2- Set user emailwith_anonymous/2- Set anonymous flagwith_age/2- Set user agewith_gender/2- Set user gender
Location Information:
with_country/2- Set country codewith_region/2- Set regionwith_city/2- Set citywith_ip/2- Set IP address
Device Information:
with_device_type/2- Set device type (mobile, desktop, tablet)with_manufacturer/2- Set device manufacturerwith_os/2- Set operating systemwith_os_version/2- Set OS versionwith_platform/2- Set platform
Browser Information:
with_browser/2- Set browser namewith_browser_version/2- Set browser version
Technical Details:
with_language/2- Set language codewith_connection_type/2- Set connection typewith_app_version/2- Set application version
Custom Attributes:
set/3- Set custom key-value pairset_many/2- Set multiple custom attributes
Feature Evaluation
# Evaluate a feature
{:ok, result} = TogglrSdk.Client.evaluate(client, "new_ui", context)
# result = %{value: "enabled", enabled: true, found: true}
# Check if feature is enabled
{:ok, enabled} = TogglrSdk.Client.is_enabled(client, "new_ui", context)
# enabled = true
# Check with default value
{:ok, enabled} = TogglrSdk.Client.is_enabled_or_default(client, "new_ui", context, false)
# enabled = trueError Reporting and Auto-Disable
The SDK supports reporting errors for features, which can trigger automatic disabling based on error rates:
# Report an error for a feature
:ok = TogglrSdk.Client.report_error(
client,
"feature_key",
"timeout",
"Service did not respond in 5s",
%{service: "payment-gateway", timeout_ms: 5000}
)
IO.puts("Error reported successfully - queued for processing")Error Types
Supported error types:
timeout- Service timeoutvalidation- Data validation errorservice_unavailable- External service unavailablerate_limit- Rate limit exceedednetwork- Network connectivity issueinternal- Internal application error
Context Data
You can provide additional context with error reports:
context = %{
service: "payment-gateway",
timeout_ms: 5000,
user_id: "user123",
region: "us-east-1"
}
:ok = TogglrSdk.Client.report_error(
client,
"feature_key",
"timeout",
"Service timeout",
context
)Feature Health Monitoring
Monitor the health status of features:
# Get detailed health information
{:ok, health} = TogglrSdk.Client.get_feature_health(client, "feature_key")
IO.puts("Feature: #{health.feature_key}")
IO.puts("Enabled: #{health.enabled}")
IO.puts("Auto Disabled: #{health.auto_disabled}")
IO.puts("Error Rate: #{health.error_rate}")
IO.puts("Threshold: #{health.threshold}")
IO.puts("Last Error At: #{health.last_error_at}")
# Simple health check
{:ok, is_healthy} = TogglrSdk.Client.is_feature_healthy(client, "feature_key")
IO.puts("Feature is healthy: #{is_healthy}")FeatureHealth Model
The TogglrSdk.Models.FeatureHealth struct provides:
feature_key- The feature identifierenvironment_key- The environment identifierenabled- Whether the feature is enabledauto_disabled- Whether the feature was auto-disabled due to errorserror_rate- Current error rate (0.0 to 1.0)threshold- Error rate threshold for auto-disablelast_error_at- Timestamp of the last errorhealthy?/1- Function to check if feature is healthy
ErrorReport Model
The TogglrSdk.Models.ErrorReport struct provides:
error_type- Type of errorerror_message- Human-readable error messagecontext- Additional context data
# Create an error report
error_report = TogglrSdk.Models.ErrorReport.new(
"timeout",
"Service timeout",
%{service: "api", timeout_ms: 5000}
)
# Convert to map for API requests
error_data = TogglrSdk.Models.ErrorReport.to_map(error_report)Error Handling
The SDK provides specific exceptions for different error scenarios:
case TogglrSdk.Client.evaluate(client, "feature", context) do
{:ok, result} ->
# Handle success
process_result(result)
{:error, %TogglrSdk.Exceptions.FeatureNotFoundException{feature_key: feature_key}} ->
# Handle feature not found
IO.puts("Feature '#{feature_key}' not found")
{:error, %TogglrSdk.Exceptions.UnauthorizedException{}} ->
# Handle authentication error
IO.puts("Authentication failed")
{:error, %TogglrSdk.Exceptions.BadRequestException{}} ->
# Handle bad request
IO.puts("Invalid request")
{:error, reason} ->
# Handle other errors
IO.puts("Error: #{inspect(reason)}")
endCaching
The SDK includes built-in LRU caching with TTL for improved performance:
# Enable caching with custom settings
config = TogglrSdk.Config.default("your-api-key")
|> TogglrSdk.Config.with_cache(true, 2000, 120) # enabled, max_size, ttl_seconds
{:ok, client} = TogglrSdk.Client.new(config)Cache statistics are available through the cache module:
# Get cache statistics
stats = TogglrSdk.Cache.stats()
# stats = %{size: 150, hits: 1200, misses: 300}Retry Logic
Configure exponential backoff for retries:
# Create custom backoff configuration
backoff = TogglrSdk.BackoffConfig.new(0.5, 10.0, 1.5) # base_delay, max_delay, multiplier
config = TogglrSdk.Config.default("your-api-key")
|> TogglrSdk.Config.with_backoff(backoff)
|> TogglrSdk.Config.with_retries(5)
{:ok, client} = TogglrSdk.Client.new(config)Telemetry
The SDK emits telemetry events for monitoring and observability:
defmodule MyTelemetry do
def handle_event([:togglr_sdk, :evaluate, :start], measurements, metadata, _config) do
# Handle evaluation start
IO.puts("Starting feature evaluation: #{metadata.feature_key}")
end
def handle_event([:togglr_sdk, :evaluate, :stop], measurements, metadata, _config) do
# Handle evaluation complete
IO.puts("Feature evaluation completed in #{measurements.duration}ms")
end
def handle_event([:togglr_sdk, :evaluate, :exception], measurements, metadata, _config) do
# Handle evaluation error
IO.puts("Feature evaluation failed: #{inspect(metadata.kind)}")
end
end
# Attach telemetry handler
:telemetry.attach("togglr-sdk", [:togglr_sdk, :evaluate], &MyTelemetry.handle_event/4, nil)Development
Prerequisites
- Elixir 1.14 or later
- Mix 1.14 or later
- OpenAPI Generator CLI (for client generation)
Setup
# Clone the repository
git clone https://github.com/togglr/togglr-sdk-elixir.git
cd togglr-sdk-elixir
# Install dependencies
mix deps.get
# Run tests
mix test
# Run linting
mix credo --strict
mix dialyzer
# Format code
mix format
# Generate documentation
mix docsOpenAPI Client Generation
This project automatically generates the API client from OpenAPI specifications. See DEVELOPMENT.md for detailed information about the generation process.
Quick commands:
# Generate API client
make generate
# Full regeneration (clean + generate)
make regenerate
# Apply modifications only
make post-generateBuilding
# Build the package
mix hex.build
# Publish to Hex (requires authentication)
mix hex.publishExamples
See the examples/ directory for complete examples:
simple_example.exs- Basic usage exampleadvanced_example.exs- Advanced configuration and error handlingtls_example.exs- TLS certificate configuration example
Run examples with:
elixir examples/simple_example.exs
elixir examples/tls_example.exsRequirements
- Elixir 1.14+
- OTP 24+
- Tesla 1.4+
- Jason 1.4+
- Cachex 3.6+
- Telemetry 1.0+
License
This project is licensed under the MIT License - see the LICENSE file for details.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Support
For support, please open an issue on GitHub or contact the Togglr team.
Changelog
See CHANGELOG.md for a list of changes and version history.