2 releases

new 0.0.4 Feb 20, 2025
0.0.3 Feb 20, 2025

#48 in Caching

Apache-2.0

170KB
3.5K SLoC

flagd Provider for OpenFeature

A Rust implementation of the OpenFeature provider for flagd, enabling dynamic feature flag evaluation in your applications.

This provider supports multiple evaluation modes, advanced targeting rules, caching strategies, and connection management. It is designed to work seamlessly with the OpenFeature SDK and the flagd service.

Core Features

  • Multiple Evaluation Modes

    • RPC Resolver (Remote Evaluation): Uses gRPC to perform flag evaluations remotely at a flagd instance. Supports bi-directional streaming, retry backoff, and custom name resolution (including Envoy support).
    • REST Resolver: Uses the OpenFeature Remote Evaluation Protocol (OFREP) over HTTP to evaluate flags.
    • In-Process Resolver: Performs evaluations locally using an embedded evaluation engine. Flag configurations can be retrieved via gRPC (sync mode).
    • File Resolver: Operates entirely from a flag definition file, updating on file changes in a best-effort manner.
  • Advanced Targeting

    • Fractional Rollouts: Uses consistent hashing (implemented via murmurhash3) to split traffic between flag variants in configurable proportions.
    • Semantic Versioning: Compare values using common operators such as '=', '!=', '<', '<=', '>', '>=', '^', and '~'.
    • String Operations: Custom operators for performing “starts_with” and “ends_with” comparisons.
    • Complex Targeting Rules: Leverages JSONLogic and custom operators to support nested conditions and dynamic evaluation.
  • Caching Strategies

    • Built-in support for LRU caching as well as an in-memory alternative. Flag evaluation results can be cached and later returned with a “CACHED” reason until the configuration updates.
  • Connection Management

    • Automatic connection establishment with configurable retries, timeout settings, and custom TLS or Unix-socket options.
    • Support for upstream name resolution including a custom resolver for Envoy proxy integration.

Installation

Add the dependency in your Cargo.toml:

[dependencies]
open-feature-flagd = "0.0.4"
open-feature = "0.2"

Then integrate it into your application:

use open_feature_flagd::{FlagdOptions, FlagdProvider, ResolverType};
use open_feature::provider::FeatureProvider;
use open_feature::EvaluationContext;

#[tokio::main]
async fn main() {
    // Example using the REST resolver mode.
    let provider = FlagdProvider::new(FlagdOptions {
        host: "localhost".to_string(),
        port: 8016,
        resolver_type: ResolverType::Rest,
        ..Default::default()
    }).await.unwrap();

    let context = EvaluationContext::default().with_targeting_key("user-123");
    let result = provider.resolve_bool_value("bool-flag", &context).await.unwrap();
    println!("Flag value: {}", result.value);
}

Evaluation Modes

Remote Resolver (RPC)

In RPC mode, the provider communicates with flagd via gRPC. It supports features like streaming updates, retry mechanisms, and name resolution (including Envoy).

use open_feature_flagd::{FlagdOptions, FlagdProvider, ResolverType};
use open_feature::provider::FeatureProvider;
use open_feature::EvaluationContext;

#[tokio::main]
async fn main() {
    let provider = FlagdProvider::new(FlagdOptions {
        host: "localhost".to_string(),
        port: 8013,
        resolver_type: ResolverType::Rpc,
        ..Default::default()
    }).await.unwrap();

    let context = EvaluationContext::default().with_targeting_key("user-123");
    let bool_result = provider.resolve_bool_value("feature-enabled", &context).await.unwrap();
    println!("Feature enabled: {}", bool_result.value);
}

REST Resolver

In REST mode the provider uses the OpenFeature Remote Evaluation Protocol (OFREP) over HTTP. It is useful when gRPC is not an option.

use open_feature_flagd::{FlagdOptions, FlagdProvider, ResolverType};
use open_feature::provider::FeatureProvider;
use open_feature::EvaluationContext;

#[tokio::main]
async fn main() {
    let provider = FlagdProvider::new(FlagdOptions {
        host: "localhost".to_string(),
        port: 8016,
        resolver_type: ResolverType::Rest,
        ..Default::default()
    }).await.unwrap();

    let context = EvaluationContext::default().with_targeting_key("user-456");
    let result = provider.resolve_string_value("feature-variant", &context).await.unwrap();
    println!("Variant: {}", result.value);
}

In-Process Resolver

In-process evaluation is performed locally. Flag configurations are sourced via gRPC sync stream. This mode supports advanced targeting operators (fractional, semver, string comparisons) using the built-in evaluation engine.

use open_feature_flagd::{CacheSettings, FlagdOptions, FlagdProvider, ResolverType};
use open_feature::provider::FeatureProvider;
use open_feature::EvaluationContext;

#[tokio::main]
async fn main() {
    let provider = FlagdProvider::new(FlagdOptions {
        host: "localhost".to_string(),
        port: 8015,
        resolver_type: ResolverType::InProcess,
        selector: Some("my-service".to_string()),
        cache_settings: Some(CacheSettings::default()),
        ..Default::default()
    }).await.unwrap();

    let context = EvaluationContext::default()
        .with_targeting_key("user-abc")
        .with_custom_field("environment", "production")
        .with_custom_field("semver", "2.1.0");

    let dark_mode = provider.resolve_bool_value("dark-mode", &context).await.unwrap();
    println!("Dark mode enabled: {}", dark_mode.value);
}

File Mode

File mode is an in-process variant where flag configurations are read from a file. This is useful for development or environments without network access.

use open_feature_flagd::{FlagdOptions, FlagdProvider, ResolverType};
use open_feature::provider::FeatureProvider;
use open_feature::EvaluationContext;

#[tokio::main]
async fn main() {
    let file_path = "./path/to/flagd-config.json".to_string();
    let provider = FlagdProvider::new(FlagdOptions {
        host: "localhost".to_string(),
        resolver_type: ResolverType::File,
        source_configuration: Some(file_path),
        ..Default::default()
    }).await.unwrap();

    let context = EvaluationContext::default();
    let result = provider.resolve_int_value("rollout-percentage", &context).await.unwrap();
    println!("Rollout percentage: {}", result.value);
}

Configuration Options

Configurations can be provided as constructor options or via environment variables (with constructor options taking priority). The following options are supported:

Option Env Variable Type / Supported Value Default Compatible Resolver
Host FLAGD_HOST string "localhost" RPC, REST, In-Process, File
Port FLAGD_PORT number 8013 (RPC), 8016 (REST) RPC, REST, In-Process, File
Target URI FLAGD_TARGET_URI string "" RPC, In-Process
TLS FLAGD_TLS boolean false RPC, In-Process
Socket Path FLAGD_SOCKET_PATH string "" RPC
Certificate Path FLAGD_SERVER_CERT_PATH string "" RPC, In-Process
Cache Type (LRU / In-Memory / Disabled) FLAGD_CACHE string ("lru", "mem", "disabled") lru RPC, In-Process, File
Cache TTL (Seconds) FLAGD_CACHE_TTL number 60 RPC, In-Process, File
Max Cache Size FLAGD_MAX_CACHE_SIZE number 1000 RPC, In-Process, File
Offline File Path FLAGD_OFFLINE_FLAG_SOURCE_PATH string "" File
Retry Backoff (ms) FLAGD_RETRY_BACKOFF_MS number 1000 RPC, In-Process
Retry Backoff Maximum (ms) FLAGD_RETRY_BACKOFF_MAX_MS number 120000 RPC, In-Process
Retry Grace Period FLAGD_RETRY_GRACE_PERIOD number 5 RPC, In-Process
Event Stream Deadline (ms) FLAGD_STREAM_DEADLINE_MS number 600000 RPC
Offline Poll Interval (ms) FLAGD_OFFLINE_POLL_MS number 5000 File
Source Selector FLAGD_SOURCE_SELECTOR string "" In-Process

License

Apache 2.0 - See LICENSE for more information.

Dependencies

~22–36MB
~639K SLoC