Skip to content

fix(openfeature): Rust provider improvements#1094

Open
ayushjain17 wants to merge 1 commit into
mainfrom
rust/openfeature
Open

fix(openfeature): Rust provider improvements#1094
ayushjain17 wants to merge 1 commit into
mainfrom
rust/openfeature

Conversation

@ayushjain17

@ayushjain17 ayushjain17 commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Change log

Improvements and fixes in rust provider to make it more consistent with openfeature spec and other languages in parallel

Summary by CodeRabbit

  • New Features

    • Added typed array flag resolution for OpenFeature integrations.
    • Added millisecond-based polling and on-demand refresh configuration.
    • Providers now track refresh health and recover status after successful refreshes.
  • Bug Fixes

    • Null fields in object-valued flags are ignored instead of causing failures.
    • Invalid top-level flag types now return clearer type-mismatch errors.
    • Missing targeting keys no longer prevent applicable-variant evaluation.
  • Documentation

    • Improved guidance for typed flag-resolution errors.

Copilot AI review requested due to automatic review settings July 14, 2026 14:24
@ayushjain17
ayushjain17 requested a review from a team as a code owner July 14, 2026 14:24
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2eac4494-466a-47af-9fa7-c393f4133492

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

Refresh configuration now uses millisecond-based constructors and accessors. Provider refreshes support timeouts and stale-status recovery, value conversion handles null object fields, typed array resolution is added, and provider error variants are refined.

Changes

Provider refresh semantics

Layer / File(s) Summary
Configuration contracts
crates/superposition_provider/src/types.rs, crates/superposition_provider/src/provider.rs, crates/superposition_provider/src/lib.rs
Refresh strategies use millisecond-first fields and constructors; cache-related options and serialization derives are removed.
Value conversion and typed resolution
crates/superposition_provider/src/conversions.rs, crates/superposition_provider/src/traits.rs
Object conversion drops null fields and rejects non-object top-level values; resolve_array resolves top-level JSON arrays into OpenFeature values.
Refresh execution and timing
crates/superposition_provider/src/local_provider.rs, crates/superposition_provider/src/client.rs, crates/superposition_provider/examples/*
Polling and on-demand refresh use millisecond intervals and TTLs; local refreshes apply timeouts and record Ready or STALE outcomes.
Errors and provider integrations
crates/superposition_provider/src/data_source/file.rs, crates/superposition_provider/src/local_provider.rs, crates/superposition_provider/src/remote_provider.rs
Data-source failures use DataSourceError, and missing remote targeting keys use an empty identifier.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: copilot

Poem

A rabbit tunes the clock to glow,
In milliseconds, streams now flow.
Nulls hop out, arrays resolve,
Stale turns ready as problems dissolve.
“Refresh!” I cheer, with ears held high—
Clean provider paths race by!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is broadly related to the Rust OpenFeature provider changes, but it is too generic to convey the main update. Make the title specific to the primary change, such as the millisecond-based refresh and provider behavior updates.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch rust/openfeature

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR refines the Rust OpenFeature provider implementation to better align behavior and ergonomics with the OpenFeature spec and the other language providers, especially around refresh strategies, typed resolution, and error reporting.

Changes:

  • Reworked refresh strategy durations to be millisecond-based (with deprecated seconds fields preserved) and added refresh timeout handling + staleness tracking on refresh failure.
  • Improved typed resolution by adding resolve_array and tightening JSON-to-OpenFeature conversions (object-only StructValue, null handling).
  • Simplified/streamlined provider option types and error taxonomy (e.g., DataSourceError, RefreshError), and updated examples accordingly.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
crates/superposition_provider/src/types.rs Introduces ms-based refresh strategy APIs with deprecated seconds fields; expands error enum; removes evaluation-cache-related option structs; adds unit tests.
crates/superposition_provider/src/traits.rs Adds typed resolve_array and documents typed-resolution semantics vs other SDKs.
crates/superposition_provider/src/remote_provider.rs Changes experiment-variant lookup request identifier handling.
crates/superposition_provider/src/provider.rs Updates configuration construction to match removed evaluation-cache parameter.
crates/superposition_provider/src/local_provider.rs Uses ms durations, adds refresh timeout bounding, and records provider staleness on refresh failure.
crates/superposition_provider/src/lib.rs Updates tests for the new ConfigurationOptions::new signature.
crates/superposition_provider/src/data_source/file.rs Reclassifies file-source failures as DataSourceError.
crates/superposition_provider/src/conversions.rs Makes StructValue conversion object-only; drops null object fields; adds tests.
crates/superposition_provider/src/client.rs Updates polling/on-demand logic to ms-based durations and renames TTL params accordingly.
crates/superposition_provider/examples/polling_example.rs Updates example to use ms-based polling env var and PollingStrategy::new.
crates/superposition_provider/examples/local_with_fallback_example.rs Updates example to use PollingStrategy::new (ms).
crates/superposition_provider/examples/local_http_example.rs Updates example to use PollingStrategy::new (ms).
crates/superposition_provider/examples/local_file_example.rs Updates example to use OnDemandStrategy::new (ms).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 125 to 129
.workspace_id(&self.options.workspace_id)
.org_id(&self.options.org_id)
.set_context(Some(query_data))
.identifier(targeting_key)
.identifier(targeting_key.unwrap_or_default())
.set_prefix(prefix_filter)
Comment on lines 448 to 450
let is_elapsed = |cached_at: DateTime<Utc>| {
(chrono::Utc::now() - cached_at).num_seconds() > ttl as i64
(chrono::Utc::now() - cached_at).num_milliseconds() > ttl as i64
};
Comment on lines 150 to 153
let should_refresh = match *last_updated {
Some(last) => (now - last).num_seconds() > ttl as i64,
Some(last) => (now - last).num_milliseconds() > ttl_ms as i64,
None => true,
};
Comment on lines 441 to 444
let should_refresh = match *last_updated {
Some(last) => (now - last).num_seconds() > ttl as i64,
Some(last) => (now - last).num_milliseconds() > ttl_ms as i64,
None => true,
};

@coderabbitai coderabbitai Bot left a comment

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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/superposition_provider/src/client.rs (2)

385-484: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Same missing-timeout gap as CacConfig (lines 109-184).

ExperimentationConfig::start_polling/on_demand_config call get_experiments_static/get_experiment_groups_static directly with no timeout bound at all (not even logged here). Same fix applies: thread timeout_ms() through and wrap the fetch with tokio::time::timeout.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/superposition_provider/src/client.rs` around lines 385 - 484, Update
ExperimentationConfig::start_polling and on_demand_config to obtain the
configured timeout via timeout_ms(), pass it through the
get_experiments_static/get_experiment_groups_static fetch path, and wrap each
fetch with tokio::time::timeout. Handle timeout errors consistently with
existing fetch errors while preserving the current cache update and stale-data
behavior.

109-184: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Enforce the refresh timeout around each fetch. The timeout values are only logged here; start_polling and on_demand_config still await the fetch directly, so a hung request can stall polling or block an on-demand caller indefinitely. Apply the same tokio::time::timeout around the config fetch in both CAC and experimentation refresh paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/superposition_provider/src/client.rs` around lines 109 - 184, Wrap
each config fetch in start_polling and on_demand_config with
tokio::time::timeout using the configured refresh timeout, handling timeout
errors through the existing polling and stale-config/error paths. Apply the same
timeout behavior to the corresponding CAC and experimentation refresh
implementations, preserving successful cache updates and on-demand return
behavior.
🧹 Nitpick comments (1)
crates/superposition_provider/src/types.rs (1)

69-79: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

No lower-bound validation on millisecond durations in PollingStrategy::new/OnDemandStrategy::new.

Both constructors accept an arbitrary u64 for interval_milliseconds/ttl_milliseconds with no minimum-value guard. A 0 (reachable e.g. via POLL_INTERVAL_MS=0 in examples/polling_example.rs, which parses straight into u64) flows into start_polling's sleep(Duration::from_millis(interval_ms)) (local_provider.rs) producing an effectively unthrottled polling loop against the origin endpoint; similarly a 0 TTL makes ensure_fresh_data treat cache as expired on almost every call. A small validation/clamp in the constructors would close this gap cheaply.

♻️ Example guard
 pub fn new(interval_milliseconds: u64, timeout_milliseconds: Option<u64>) -> Self {
+    debug_assert!(interval_milliseconds > 0, "polling interval must be > 0ms");
     #[allow(deprecated)]
     Self {

Also applies to: 127-142

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/superposition_provider/src/types.rs` around lines 69 - 79, Update
PollingStrategy::new and OnDemandStrategy::new to validate or clamp
interval_milliseconds and ttl_milliseconds so zero values cannot be stored.
Ensure both constructors enforce a positive minimum before these durations reach
start_polling or ensure_fresh_data, while preserving existing handling of valid
nonzero values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@crates/superposition_provider/src/client.rs`:
- Around line 385-484: Update ExperimentationConfig::start_polling and
on_demand_config to obtain the configured timeout via timeout_ms(), pass it
through the get_experiments_static/get_experiment_groups_static fetch path, and
wrap each fetch with tokio::time::timeout. Handle timeout errors consistently
with existing fetch errors while preserving the current cache update and
stale-data behavior.
- Around line 109-184: Wrap each config fetch in start_polling and
on_demand_config with tokio::time::timeout using the configured refresh timeout,
handling timeout errors through the existing polling and stale-config/error
paths. Apply the same timeout behavior to the corresponding CAC and
experimentation refresh implementations, preserving successful cache updates and
on-demand return behavior.

---

Nitpick comments:
In `@crates/superposition_provider/src/types.rs`:
- Around line 69-79: Update PollingStrategy::new and OnDemandStrategy::new to
validate or clamp interval_milliseconds and ttl_milliseconds so zero values
cannot be stored. Ensure both constructors enforce a positive minimum before
these durations reach start_polling or ensure_fresh_data, while preserving
existing handling of valid nonzero values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ef121c14-1263-4493-a10a-877406f26180

📥 Commits

Reviewing files that changed from the base of the PR and between b71e629 and fc9e435.

📒 Files selected for processing (13)
  • crates/superposition_provider/examples/local_file_example.rs
  • crates/superposition_provider/examples/local_http_example.rs
  • crates/superposition_provider/examples/local_with_fallback_example.rs
  • crates/superposition_provider/examples/polling_example.rs
  • crates/superposition_provider/src/client.rs
  • crates/superposition_provider/src/conversions.rs
  • crates/superposition_provider/src/data_source/file.rs
  • crates/superposition_provider/src/lib.rs
  • crates/superposition_provider/src/local_provider.rs
  • crates/superposition_provider/src/provider.rs
  • crates/superposition_provider/src/remote_provider.rs
  • crates/superposition_provider/src/traits.rs
  • crates/superposition_provider/src/types.rs
💤 Files with no reviewable changes (2)
  • crates/superposition_provider/src/lib.rs
  • crates/superposition_provider/src/provider.rs

@ayushjain17
ayushjain17 force-pushed the rust/openfeature branch 2 times, most recently from b9c1249 to e93f729 Compare July 15, 2026 08:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants