Skip to content

Commit

Permalink
feat: Add implementation of Hooks
Browse files Browse the repository at this point in the history
Signed-off-by: Maxim Fischuk <mfischuk@vareger.com>
  • Loading branch information
MaximFischuk committed Dec 17, 2024
1 parent 40431aa commit 1d86d02
Show file tree
Hide file tree
Showing 19 changed files with 1,450 additions and 88 deletions.
12 changes: 7 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "open-feature"
version = "0.2.4"
edition = "2021"
rust-version = "1.71.1" # MSRV
rust-version = "1.71.1" # MSRV
description = "The official OpenFeature Rust SDK."
documentation = "https://docs.rs/open-feature"
readme = "README.md"
Expand All @@ -21,13 +21,15 @@ lazy_static = "1.5"
mockall = { version = "0.13.0", optional = true }
serde_json = { version = "1.0.116", optional = true }
time = "0.3.36"
tokio = { version = "1.40", features = [ "full" ] }
tokio = { version = "1.40", features = ["full"] }
typed-builder = "0.20.0"
log = "0.4.14"

[dev-dependencies]
env_logger = "0.11.5"
spec = { path = "spec" }

[features]
default = [ "test-util" ]
test-util = [ "dep:mockall" ]
serde_json = [ "dep:serde_json" ]
default = ["test-util"]
test-util = ["dep:mockall"]
serde_json = ["dep:serde_json"]
150 changes: 150 additions & 0 deletions examples/hooks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
use open_feature::{
provider::{FeatureProvider, ProviderMetadata, ProviderStatus, ResolutionDetails},
EvaluationContext, EvaluationDetails, EvaluationError, EvaluationOptions, EvaluationResult,
Hook, HookContext, HookHints, OpenFeature, StructValue, Value,
};

struct DummyProvider(ProviderMetadata);

impl Default for DummyProvider {
fn default() -> Self {
Self(ProviderMetadata::new("Dummy Provider"))
}
}

#[async_trait::async_trait]
impl FeatureProvider for DummyProvider {
fn metadata(&self) -> &ProviderMetadata {
&self.0
}

fn status(&self) -> ProviderStatus {
ProviderStatus::Ready
}

async fn resolve_bool_value(
&self,
_flag_key: &str,
_evaluation_context: &EvaluationContext,
) -> EvaluationResult<ResolutionDetails<bool>> {
Ok(ResolutionDetails::new(true))
}

async fn resolve_int_value(
&self,
_flag_key: &str,
_evaluation_context: &EvaluationContext,
) -> EvaluationResult<ResolutionDetails<i64>> {
unimplemented!()
}

async fn resolve_float_value(
&self,
_flag_key: &str,
_evaluation_context: &EvaluationContext,
) -> EvaluationResult<ResolutionDetails<f64>> {
unimplemented!()
}

async fn resolve_string_value(
&self,
_flag_key: &str,
_evaluation_context: &EvaluationContext,
) -> EvaluationResult<ResolutionDetails<String>> {
unimplemented!()
}

async fn resolve_struct_value(
&self,
_flag_key: &str,
_evaluation_context: &EvaluationContext,
) -> Result<ResolutionDetails<StructValue>, EvaluationError> {
unimplemented!()
}
}

struct DummyLoggingHook(String);

#[async_trait::async_trait]
impl Hook for DummyLoggingHook {
async fn before<'a>(
&self,
context: &HookContext<'a>,
_hints: Option<&'a HookHints>,
) -> Result<Option<EvaluationContext>, EvaluationError> {
log::info!(
"Evaluating({}) flag {} of type {}",
self.0,
context.flag_key,
context.flag_type
);

Ok(None)
}

async fn after<'a>(
&self,
context: &HookContext<'a>,
details: &EvaluationDetails<Value>,
_hints: Option<&'a HookHints>,
) -> Result<(), EvaluationError> {
log::info!(
"Flag({}) {} of type {} evaluated to {:?}",
self.0,
context.flag_key,
context.flag_type,
details.value
);

Ok(())
}

async fn error<'a>(
&self,
context: &HookContext<'a>,
error: &EvaluationError,
_hints: Option<&'a HookHints>,
) {
log::error!(
"Error({}) evaluating flag {} of type {}: {:?}",
self.0,
context.flag_key,
context.flag_type,
error
);
}

async fn finally<'a>(&self, context: &HookContext<'a>, _hints: Option<&'a HookHints>) {
log::info!(
"Finally({}) evaluating flag {} of type {}",
self.0,
context.flag_key,
context.flag_type
);
}
}

#[tokio::main]
async fn main() {
env_logger::builder()
.filter_level(log::LevelFilter::Info)
.init();

let mut api = OpenFeature::singleton_mut().await;
api.set_provider(DummyProvider::default()).await;
api.add_hook(DummyLoggingHook("global".to_string())).await;
drop(api);

let client = OpenFeature::singleton()
.await
.create_client()
.with_hook(DummyLoggingHook("client".to_string())); // Add a client-level hook

let eval = EvaluationOptions::default().with_hook(DummyLoggingHook("eval".to_string()));
let feature = client
.get_bool_details("my_feature", None, Some(&eval))
.await
.unwrap();

println!("Feature value: {}", feature.value);
}
14 changes: 12 additions & 2 deletions src/api/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};

use crate::{
provider::{FeatureProvider, ProviderMetadata},
Client, EvaluationContext,
Client, EvaluationContext, Hook, HookWrapper,
};

use super::{
global_evaluation_context::GlobalEvaluationContext, provider_registry::ProviderRegistry,
global_evaluation_context::GlobalEvaluationContext, global_hooks::GlobalHooks,
provider_registry::ProviderRegistry,
};

lazy_static! {
Expand All @@ -21,6 +22,7 @@ lazy_static! {
#[derive(Default)]
pub struct OpenFeature {
evaluation_context: GlobalEvaluationContext,
hooks: GlobalHooks,

provider_registry: ProviderRegistry,
}
Expand Down Expand Up @@ -54,6 +56,12 @@ impl OpenFeature {
self.provider_registry.set_named(name, provider).await;
}

/// Add a new hook to the global list of hooks.
pub async fn add_hook<T: Hook>(&mut self, hook: T) {
let mut lock = self.hooks.get_mut().await; // TODO: Remove async context to avoid deadlock and enforce initialization before usage.
lock.push(HookWrapper::new(hook));
}

/// Return the metadata of default (unnamed) provider.
pub async fn provider_metadata(&self) -> ProviderMetadata {
self.provider_registry
Expand All @@ -77,6 +85,7 @@ impl OpenFeature {
Client::new(
String::default(),
self.evaluation_context.clone(),
self.hooks.clone(),
self.provider_registry.clone(),
)
}
Expand All @@ -87,6 +96,7 @@ impl OpenFeature {
Client::new(
name.to_string(),
self.evaluation_context.clone(),
self.hooks.clone(),
self.provider_registry.clone(),
)
}
Expand Down
Loading

0 comments on commit 1d86d02

Please sign in to comment.