Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion api/public-api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -156,16 +156,17 @@ pub fn posthog_rs::ClientOptionsBuilder::local_evaluation_only(&mut self, bool)
pub fn posthog_rs::ClientOptionsBuilder::max_batch_size(&mut self, usize) -> &mut Self
pub fn posthog_rs::ClientOptionsBuilder::max_capture_attempts(&mut self, u32) -> &mut Self
pub fn posthog_rs::ClientOptionsBuilder::max_queue_size(&mut self, usize) -> &mut Self
pub fn posthog_rs::ClientOptionsBuilder::personal_api_key<VALUE: core::convert::Into<alloc::string::String>>(&mut self, VALUE) -> &mut Self

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we keep this the same?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — restored personal_api_key to its original <VALUE: Into<String>> signature, so its public API is unchanged; only secret_key is added (41652f0).

pub fn posthog_rs::ClientOptionsBuilder::poll_interval_seconds(&mut self, u64) -> &mut Self
pub fn posthog_rs::ClientOptionsBuilder::request_timeout_seconds(&mut self, u64) -> &mut Self
pub fn posthog_rs::ClientOptionsBuilder::retry_initial_backoff_ms(&mut self, u64) -> &mut Self
pub fn posthog_rs::ClientOptionsBuilder::retry_max_backoff_ms(&mut self, u64) -> &mut Self
pub fn posthog_rs::ClientOptionsBuilder::secret_key<VALUE: core::convert::Into<alloc::string::String>>(&mut self, VALUE) -> &mut Self
pub fn posthog_rs::ClientOptionsBuilder::shutdown_timeout_ms(&mut self, u64) -> &mut Self
impl posthog_rs::ClientOptionsBuilder
pub fn posthog_rs::ClientOptionsBuilder::before_send<F>(&mut self, F) -> &mut Self where F: core::ops::function::FnMut(posthog_rs::Event) -> core::option::Option<posthog_rs::Event> + core::marker::Send + 'static
pub fn posthog_rs::ClientOptionsBuilder::build(&self) -> core::result::Result<posthog_rs::ClientOptions, posthog_rs::ClientOptionsBuilderError>
pub fn posthog_rs::ClientOptionsBuilder::on_error<F>(&mut self, F) -> &mut Self where F: core::ops::function::Fn(&posthog_rs::PostHogError<'_>) + core::marker::Send + core::marker::Sync + 'static
pub fn posthog_rs::ClientOptionsBuilder::personal_api_key<VALUE: core::convert::Into<alloc::string::String>>(&mut self, VALUE) -> &mut Self
impl core::default::Default for posthog_rs::ClientOptionsBuilder
pub fn posthog_rs::ClientOptionsBuilder::default() -> Self
pub struct posthog_rs::Cohort
Expand Down
2 changes: 1 addition & 1 deletion examples/advanced_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("5. High-performance with local evaluation:");
let performance_config = ClientOptionsBuilder::default()
.api_key("phc_project_key".to_string())
.personal_api_key("phx_personal_key") // Required for local eval
.secret_key("phx_personal_key") // Required for local eval
.enable_local_evaluation(true) // Cache flags locally
.poll_interval_seconds(30) // Update cache every 30s
.feature_flags_request_timeout_seconds(3)
Expand Down
2 changes: 1 addition & 1 deletion examples/local_evaluation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ async fn main() {
let local_client = {
let options = ClientOptionsBuilder::default()
.api_key(api_key.clone())
.personal_api_key(personal_key)
.secret_key(personal_key)
.enable_local_evaluation(true)
.poll_interval_seconds(30) // Poll for updates every 30 seconds
.build()
Expand Down
49 changes: 25 additions & 24 deletions src/client/async_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,32 +130,33 @@ pub async fn client<C: Into<ClientOptions>>(options: C) -> Client {
.build()
.unwrap(); // Unwrap here is as safe as `HttpClient::new`

let (local_evaluator, flag_poller) = if options.enable_local_evaluation
&& !options.is_disabled()
{
if let Some(ref personal_key) = options.personal_api_key {
let cache = FlagCache::new();

let config = LocalEvaluationConfig {
personal_api_key: personal_key.clone(),
project_api_key: options.api_key.clone(),
api_host: options.endpoints().api_host(),
poll_interval: Duration::from_secs(options.poll_interval_seconds),
request_timeout: Duration::from_secs(options.request_timeout_seconds),
};

let mut poller = AsyncFlagPoller::new(config, cache.clone());
poller.set_on_error(options.on_error.clone());
poller.start().await;

(Some(LocalEvaluator::new(cache)), Some(poller))
let (local_evaluator, flag_poller) =
if options.enable_local_evaluation && !options.is_disabled() {
if let Some(ref secret_key) = options.secret_key {
let cache = FlagCache::new();

let config = LocalEvaluationConfig {
personal_api_key: secret_key.clone(),
project_api_key: options.api_key.clone(),
api_host: options.endpoints().api_host(),
poll_interval: Duration::from_secs(options.poll_interval_seconds),
request_timeout: Duration::from_secs(options.request_timeout_seconds),
};

let mut poller = AsyncFlagPoller::new(config, cache.clone());
poller.set_on_error(options.on_error.clone());
poller.start().await;

(Some(LocalEvaluator::new(cache)), Some(poller))
} else {
warn!(
"Local evaluation enabled but secret_key not set, falling back to API evaluation"
);
(None, None)
}
} else {
warn!("Local evaluation enabled but personal_api_key not set, falling back to API evaluation");
(None, None)
}
} else {
(None, None)
};
};

let transport = if options.is_disabled() {
None
Expand Down
49 changes: 25 additions & 24 deletions src/client/blocking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,32 +134,33 @@ pub fn client<C: Into<ClientOptions>>(options: C) -> Client {
.build()
.unwrap(); // Unwrap here is as safe as `HttpClient::new`

let (local_evaluator, flag_poller) = if options.enable_local_evaluation
&& !options.is_disabled()
{
if let Some(ref personal_key) = options.personal_api_key {
let cache = FlagCache::new();

let config = LocalEvaluationConfig {
personal_api_key: personal_key.clone(),
project_api_key: options.api_key.clone(),
api_host: options.endpoints().api_host(),
poll_interval: Duration::from_secs(options.poll_interval_seconds),
request_timeout: Duration::from_secs(options.request_timeout_seconds),
};

let mut poller = FlagPoller::new(config, cache.clone());
poller.set_on_error(options.on_error.clone());
poller.start();

(Some(LocalEvaluator::new(cache)), Some(poller))
let (local_evaluator, flag_poller) =
if options.enable_local_evaluation && !options.is_disabled() {
if let Some(ref secret_key) = options.secret_key {
let cache = FlagCache::new();

let config = LocalEvaluationConfig {
personal_api_key: secret_key.clone(),
project_api_key: options.api_key.clone(),
api_host: options.endpoints().api_host(),
poll_interval: Duration::from_secs(options.poll_interval_seconds),
request_timeout: Duration::from_secs(options.request_timeout_seconds),
};

let mut poller = FlagPoller::new(config, cache.clone());
poller.set_on_error(options.on_error.clone());
poller.start();

(Some(LocalEvaluator::new(cache)), Some(poller))
} else {
warn!(
"Local evaluation enabled but secret_key not set, falling back to API evaluation"
);
(None, None)
}
} else {
warn!("Local evaluation enabled but personal_api_key not set, falling back to API evaluation");
(None, None)
}
} else {
(None, None)
};
};

let transport = if options.is_disabled() {
None
Expand Down
61 changes: 54 additions & 7 deletions src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,12 @@ pub struct ClientOptions {
#[builder(default = "30")]
request_timeout_seconds: u64,

/// Personal API key for fetching flag definitions. Required when
/// `enable_local_evaluation` is `true`.
/// Secret key used for local feature flag evaluation and remote config.
///
/// Accepts either a Personal API Key (`phx_...`) or a Project Secret API
/// Key (`phs_...`). Required when `enable_local_evaluation` is `true`.
#[builder(setter(into, strip_option), default)]
personal_api_key: Option<String>,
secret_key: Option<String>,

/// Enable local evaluation of feature flags using a background definitions
/// poller.
Expand Down Expand Up @@ -321,8 +323,8 @@ impl ClientOptions {
}
None => DEFAULT_HOST.to_string(),
});
self.personal_api_key = self.personal_api_key.and_then(|personal_api_key| {
let normalized = personal_api_key.trim().to_string();
self.secret_key = self.secret_key.and_then(|secret_key| {
let normalized = secret_key.trim().to_string();
if normalized.is_empty() {
None
} else {
Expand Down Expand Up @@ -398,6 +400,18 @@ impl ClientOptionsBuilder {
pub fn build(&self) -> Result<ClientOptions, ClientOptionsBuilderError> {
Ok(self.build_unchecked()?.sanitize())
}

/// Deprecated alias for [`secret_key`](Self::secret_key).
///
/// Kept for backwards compatibility; forwards to `secret_key`. The last
/// builder call wins if both are set.
#[deprecated(
note = "use `secret_key` instead; it accepts a Personal API Key or a Project Secret API Key"
)]
pub fn personal_api_key<VALUE: Into<String>>(&mut self, value: VALUE) -> &mut Self {
self.secret_key = Some(Some(value.into()));
self
}
}

impl From<&str> for ClientOptions {
Expand Down Expand Up @@ -431,16 +445,49 @@ mod tests {
let options = ClientOptionsBuilder::default()
.api_key(" \n test-api-key\t ".to_string())
.host(" \nhttps://eu.posthog.com/\t ")
.personal_api_key(" \n\t ")
.secret_key(" \n\t ")
.build()
.unwrap();

assert_eq!(options.api_key, "test-api-key");
assert_eq!(options.host.as_deref(), Some("https://eu.posthog.com/"));
assert_eq!(options.personal_api_key, None);
assert_eq!(options.secret_key, None);
assert_eq!(options.endpoints().api_host(), EU_INGESTION_ENDPOINT);
}

#[test]
#[allow(deprecated)]
fn personal_api_key_forwards_to_secret_key_last_call_wins() {
let resolve = |calls: &[(&str, &str)]| {
let mut builder = ClientOptionsBuilder::default();
builder.api_key("test-api-key".to_string());
for (which, val) in calls {
match *which {
"secret" => builder.secret_key(*val),
_ => builder.personal_api_key(*val),
};
}
builder.build().unwrap().secret_key
};

assert_eq!(
resolve(&[("secret", "phs_secret")]).as_deref(),
Some("phs_secret")
);
assert_eq!(
resolve(&[("personal", "phx_personal")]).as_deref(),
Some("phx_personal")
);
assert_eq!(
resolve(&[("personal", "phx_personal"), ("secret", "phs_secret")]).as_deref(),
Some("phs_secret")
);
assert_eq!(
resolve(&[("secret", "phs_secret"), ("personal", "phx_personal")]).as_deref(),
Some("phx_personal")
);
}

#[test]
fn defaults_blank_host_after_trimming_whitespace() {
let options = ClientOptionsBuilder::default()
Expand Down
2 changes: 1 addition & 1 deletion tests/test_local_evaluation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ async fn test_local_evaluation_with_mock_server() {
let options = ClientOptionsBuilder::default()
.host(server.base_url())
.api_key("test_project_key".to_string())
.personal_api_key("test_personal_key".to_string())
.secret_key("test_personal_key".to_string())
.enable_local_evaluation(true)
.poll_interval_seconds(60)
.build()
Expand Down
Loading