Skip to content

Conversation

@sameerank
Copy link

@sameerank sameerank commented Nov 5, 2025

What does this PR do?

Implements an feature flag evaluator in Ruby that aligns as closely as possible with with the libdatadog FFE (Feature Flag Evaluation) interface contract, according to what's in DataDog/libdatadog#1282. This is one layer of stacked PRs:

main
^- #4998 (Add OpenFeature component)
^- #5024 (Add feature flags events exposure)
^- #5022 (Add InternalEvaluator) <-- you are here!
^- #5007 (Add NativeEvaluator -- binding for the libdatadog FFI)

Motivation:

Eventually, we want to use a C binding with datadog-ffe-ffi for flag evaluations. In this PR, I aimed to implement an evaluator in Ruby that can be easily swapped out to use the binding when it is ready.

Change log entry

Additional Notes:

Ran tests with

docker run --rm -v $PWD:/app -w /app ruby:3.3 bash -c "
export BUNDLE_GEMFILE=gemfiles/ruby_3.3_openfeature_latest.gemfile &&
bundle install &&
bundle exec rake spec:open_feature
"

How to test the change?

@pr-commenter
Copy link

pr-commenter bot commented Nov 5, 2025

Benchmarks

Benchmark execution time: 2025-11-11 09:18:25

Comparing candidate commit f8d364f in PR branch FFL-1361-Evaluation-in-binding-in-ruby with baseline commit 2221c34 in branch ffl-1319-add-agent-communication-for-openfeature.

Found 0 performance improvements and 1 performance regressions! Performance is the same for 43 metrics, 2 unstable metrics.

scenario:tracing - Propagation - Datadog

  • 🟥 throughput [-2737.259op/s; -2660.093op/s] or [-8.748%; -8.502%]

@sameerank sameerank force-pushed the FFL-1361-Evaluation-in-binding-in-ruby branch 2 times, most recently from da05c91 to 1857c1f Compare November 6, 2025 16:20
@github-actions github-actions bot added core Involves Datadog core libraries appsec Application Security monitoring product labels Nov 6, 2025
@sameerank sameerank changed the base branch from add-openfeature-component to ffl-1319-add-agent-communication-for-openfeature November 6, 2025 16:21
@github-actions
Copy link

github-actions bot commented Nov 6, 2025

👋 Hey @DataDog/ruby-guild, please fill "Change log entry" section in the pull request description.

If changes need to be present in CHANGELOG.md you can state it this way

**Change log entry**

Yes. A brief summary to be placed into the CHANGELOG.md

(possible answers Yes/Yep/Yeah)

Or you can opt out like that

**Change log entry**

None.

(possible answers No/Nope/None)

Visited at: 2025-11-11 09:18:47 UTC

module Datadog
module OpenFeature
module Binding
# Flat result structure matching NativeEvaluator ResolutionDetails interface
Copy link
Author

Choose a reason for hiding this comment

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

Basically I found in #5007 that this is the output format from datadog-ffe-ffi

NativeEvaluator Successful Result:

context = Datadog::OpenFeature::Binding::EvaluationContext.new('test_user')
native = Datadog::OpenFeature::Binding::NativeEvaluator.new(libdatadog_config)
result = native.get_assignment('test_flag', context)

# Output:
result.class            # => Datadog::OpenFeature::Binding::ResolutionDetails
result.value            # => "control_value"
result.variant          # => "control"
result.error_code       # => nil
result.error_message    # => nil
result.reason           # => :static
result.allocation_key   # => "rollout"
result.do_log          # => false

NativeEvaluator Error Result:

result = native.get_assignment('missing_flag', context)

# Output:
result.value            # => nil               (no default value handling)
result.error_code       # => :flag_not_found
result.error_message    # => "flag is missing in configuration, it is either unrecognized or disabled"
result.reason           # => :error

And I adjusted this to match that format. It's not required to stick to this format so it's something we can discuss and modify, but we should coordinate with FFI in libdatadog

Copy link
Author

Choose a reason for hiding this comment

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

I'm not too familiar with specifying .rbs files so please let me know if there's anything I can improve here.

require_relative 'binding/configuration'

# Define alias for backward compatibility after InternalEvaluator is loaded
Datadog::OpenFeature::Binding::Evaluator = Datadog::OpenFeature::Binding::InternalEvaluator
Copy link
Author

Choose a reason for hiding this comment

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

The aspiration is that in #5007 when it is ready, we can just switch this to

Datadog::OpenFeature::Binding::Evaluator = Datadog::OpenFeature::Binding::NativeEvaluator

And then we're using the libdatadog FFE powered evaluator

@sameerank sameerank marked this pull request as ready for review November 7, 2025 09:14
@sameerank sameerank requested a review from a team as a code owner November 7, 2025 09:14
@Strech Strech force-pushed the ffl-1319-add-agent-communication-for-openfeature branch from 0a25c37 to d5bbcd5 Compare November 7, 2025 09:27
@Strech Strech requested a review from a team as a code owner November 7, 2025 09:27
module Datadog
module OpenFeature
module Binding
# Variation types supported by UFC
Copy link
Member

Choose a reason for hiding this comment

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

sorry, what does UFC stand for?

Copy link
Author

Choose a reason for hiding this comment

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

Whoops sorry it's old terminology (Universal Flag Configuration), refers to the format of this file: https://github.com/DataDog/dd-trace-rb/blob/47869fc74f64c8bb3d357393365e5ddca6afcb25/spec/fixtures/ufc/flags-v1.json

Universal because this approach of representing targeting rules in terms of splits with shard ranges and salts is flexible and accommodates most targeting use cases. I can take this comment out if it's confusing

Copy link
Member

Choose a reason for hiding this comment

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

ufc also figures in fixture file names, I believe it also should be fixed

Copy link
Member

Choose a reason for hiding this comment

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

personally I find this comment confusing, as it includes an outdated and unknown abbreviature

Copy link
Author

@sameerank sameerank Nov 10, 2025

Choose a reason for hiding this comment

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

Ah I miscommunicated. "Old" means it has been carried over from an older system, so the term has continuity with the other places this same format is used, e.g. this is the model for the generated UFC and the term is also used in other SDKs.

So it's not quite outdated, but you're right it's not widely known outside of those of us who have worked on it. But changing the name here would make terminology here inconsistent with the other places that use the UFC. I imagine spelling out the acronym in more places might help, so I can start with that.

Copy link
Author

Choose a reason for hiding this comment

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

Added some comments and a readme bf1fbb4

@allocations = allocations || []
end

def self.from_json(key, flag_data)
Copy link
Member

Choose a reason for hiding this comment

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

I find this function name confusing - it actually builds a new Flag from a Hash, not JSON. Another thing is that if it builds the flag from flag_data, imo flag_data should be the first argument

Copy link
Author

Choose a reason for hiding this comment

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

Agreed 4290ae1

def self.from_json(key, flag_data)
new(
key: key,
enabled: flag_data['enabled'] || false,
Copy link
Member

Choose a reason for hiding this comment

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

This is more ruby idiomatic:

flag_data.fetch('enabled', false)

Copy link
Member

Choose a reason for hiding this comment

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

and when using flag_data['enabled'] || false, the || will also be evaluated if flag_data['enabled'] is actually set to false

Copy link
Author

Choose a reason for hiding this comment

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

Comment on lines 52 to 53
variations: parse_variations(flag_data['variations'] || {}),
allocations: parse_allocations(flag_data['allocations'] || [])
Copy link
Member

Choose a reason for hiding this comment

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

same here - please use .fetch

Copy link
Author

Choose a reason for hiding this comment

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

Also in 25a2e91

new(
key: key,
enabled: flag_data['enabled'] || false,
variation_type: flag_data['variationType'],
Copy link
Member

Choose a reason for hiding this comment

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

if we want to ensure variation_type presence, it's better to use fetch here as well

Copy link
Author

Choose a reason for hiding this comment

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


def self.parse_variations(variations_data)
variations_data.transform_values do |variation_data|
Variation.from_json(variation_data)
Copy link
Member

Choose a reason for hiding this comment

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

variation_data is Hash, not a JSON string, correct?

Copy link
Author

Choose a reason for hiding this comment

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

Updated in 4290ae1

Variation.from_hash(variation_data)

Comment on lines 152 to 154
shards: parse_shards(split_data['shards'] || []),
variation_key: split_data['variationKey'],
extra_logging: split_data['extraLogging'] || {}
Copy link
Member

Choose a reason for hiding this comment

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

.fetch might be better here too

Copy link
Author

@sameerank sameerank Nov 10, 2025

Choose a reason for hiding this comment

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

25a2e91, 73820ab

shards: parse_shards(split_data.fetch('shards', [])),
variation_key: split_data.fetch('variationKey'),
extra_logging: split_data.fetch('extraLogging', {})

end

# Represents a shard configuration for traffic splitting
class Shard
Copy link
Member

Choose a reason for hiding this comment

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

I am not sure about this name - it's called Shard, but has a property called total_shards?

Copy link
Author

Choose a reason for hiding this comment

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

Another case of exactly matching the key name in the JSON

Comment on lines 206 to 207
# Alias for backward compatibility
def end
Copy link
Member

Choose a reason for hiding this comment

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

this could be an alias method

Copy link
Author

Choose a reason for hiding this comment

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

end

# Represents a targeting rule
class Rule
Copy link
Member

Choose a reason for hiding this comment

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

TargetingRule?

Copy link
Author

Choose a reason for hiding this comment

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

Also matching the field name in the universal flag configuration


private

def self.parse_condition_value(value_data)
Copy link
Member

Choose a reason for hiding this comment

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

what is the purpose of this method? It doesn't seem that it parses anything - in any case it simply returns the value_data, but for some reason it checks it's type first.

Copy link
Author

Choose a reason for hiding this comment

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

I think you're right. We don't need it 7a4ffe0

end

# Assignment reasons returned in ResolutionDetails
module AssignmentReason
Copy link
Member

Choose a reason for hiding this comment

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

I think it also would improve readability and code navigation, if those modules would be extracted as separate files

Copy link
Author

Choose a reason for hiding this comment

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

@Strech Strech force-pushed the ffl-1319-add-agent-communication-for-openfeature branch from e7a0762 to 47383e2 Compare November 7, 2025 14:22
@sameerank sameerank force-pushed the FFL-1361-Evaluation-in-binding-in-ruby branch from 6f4c1fd to 94c24c2 Compare November 10, 2025 02:42
@sameerank sameerank requested a review from a team as a code owner November 10, 2025 03:38
@sameerank sameerank marked this pull request as draft November 10, 2025 03:39
@sameerank sameerank force-pushed the FFL-1361-Evaluation-in-binding-in-ruby branch from 4e749c5 to bf1fbb4 Compare November 10, 2025 04:11
@sameerank sameerank marked this pull request as ready for review November 10, 2025 07:16
@Strech Strech force-pushed the ffl-1319-add-agent-communication-for-openfeature branch from b1a978f to 9e7efb0 Compare November 10, 2025 10:02
Copy link
Member

@ivoanjo ivoanjo left a comment

Choose a reason for hiding this comment

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

I've been summoned by you asking a review of profiling-rb... although I'm not sure why? Were you looking for an extra review?
I've given it a pass, but I stopped short at some of the more deeper domain classes.

Ok so this is a lot code!. The PR description states

Eventually, we want to use a C binding with datadog-ffe-ffi for flag evaluations. In this PR, I aimed to implement an evaluator in Ruby that can be easily swapped out to use the binding when it is ready.

and then you reply at some point with

Much of this is temporary. It will be replaced by a C binding to an evaluator in libdatadog and then the Ruby evaluation code can be deleted.

So this again gets me thinking: What's the plan here again? Are we actually planning on ever shipping the pure-Ruby version to customers? Or is this just for internal testing?

Because, again, this being a lot of code, the answers to those questions are very important in determining how much time we should spend on making sure this PR is solid. Do my doubts here make sense?

"DD_ENV" => {version: ["A"]},
"DD_ERROR_TRACKING_HANDLED_ERRORS" => {version: ["A"]},
"DD_ERROR_TRACKING_HANDLED_ERRORS_INCLUDE" => {version: ["A"]},
"DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED" => {version: ["A"]},
Copy link
Member

Choose a reason for hiding this comment

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

Usually the EXPERIMENTAL part comes after the feature name -- consider maybe DD_FLAGGING_PROVIDER_EXPERIMENTAL_ENABLED or something like that?

Copy link
Member

Choose a reason for hiding this comment

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

I didn't pick the name, it's in align with other libs 😥

Copy link
Member

Choose a reason for hiding this comment

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

If we're still on time to change that, may be worth it; if not, well, it's experimental anyway 😭

Copy link
Member

Choose a reason for hiding this comment

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

This is a lot of copy-paste to reuse the current transport code... I know there's already a lot going on in this PR, but I would gently suggest considering just writing a new transport from scratch rather than copying multiple really confusingly-written files.

Copy link
Member

@Strech Strech Nov 10, 2025

Choose a reason for hiding this comment

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

👋🏼 I'm who wrote copy/paste transport. To be honest I wasn't feeling comfortable to go with self-written due to the limitations of my time on this project. At the same time I don't see much a different approach in most components, all of them use Core to write the transport, as did I.

I don't mind a better transport if I could, but that's not the case. Do you think it's a blocker?

Copy link
Member

Choose a reason for hiding this comment

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

(We've chatted privately in more detail, but just recording it here that the answer is not at all ;D)

@Strech
Copy link
Member

Strech commented Nov 10, 2025

@ivoanjo @y9v I had a plan to split the work to component and then transport and binding as a separate PRs, but by the accident you got pinged with a full blast PR.

I would apply certain suggestions to my dummy binding PR and open it for review, in addition I will extract certain pieces already suggested.

Sorry that you get though this and thanks for lots of suggestions! ❤️

@Strech Strech force-pushed the ffl-1319-add-agent-communication-for-openfeature branch from d7424f9 to 2221c34 Compare November 10, 2025 15:05
@sameerank
Copy link
Author

Thanks for the reviews @ivoanjo @y9v ! I didn't know opening a PR onto another stacked branch to consolidate our work would trigger a wider request for reviews. I will convert this PR back to draft so I can rebase this work on the base branch again

@sameerank sameerank marked this pull request as draft November 10, 2025 17:25
- Remove redundant manual test cases now covered by fixture-based tests
- Add parametrized tests that iterate through all test case files in spec/fixtures/ufc/test_cases/
- Cover all edge cases including boolean logic, numeric comparisons, regex matching, null handling, disabled flags, type validation, and complex targeting rules
- Use aggregate_failures for detailed error reporting on test failures
- Maintain essential unit tests for initialization, type mapping, and error handling
…nding

- Add class documentation to InternalEvaluator explaining UFC format
- Spell out "Universal Flag Configuration" in comments and variable definitions
- Create README in spec/fixtures/ufc/ directory explaining UFC purpose
- Enhance parameter documentation for UFC-related methods
- Rename Flag.from_json to from_hash and reorder parameters (flag_data first)
- Rename all other from_json methods to from_hash across all classes in configuration.rb
- Remove flagsV1 key support from Configuration parsing
- Remove libdatadog format detection and extraction methods
- Remove flagsV1 test case (no fixtures use this format)
- Simplify configuration validation to only check for 'flags' key
- Clean up unused format handling code paths
- Raises KeyError if variationType is missing from flag configuration
- Clarify that key is used for logging and value for the application
- Remove misleading "variation value for a flag" description
- Replace `|| []` with `Array()` for nil-safe array initialization
- Replace `|| {}` with `Hash()` for nil-safe hash initialization
- More idiomatic Ruby that handles nil values gracefully
…ures

- Remove parse_condition_value method that returns input unchanged
- Use condition_data.fetch('value') directly for cleaner code
- Update RBS signatures: from_json → from_hash across all classes
- Fix Flag parameter order in RBS: (flag_data, key) instead of (key, flag_data)
- Remove parse_condition_value method signature from RBS
@sameerank sameerank force-pushed the FFL-1361-Evaluation-in-binding-in-ruby branch from b78312e to c7a1c00 Compare November 10, 2025 19:52
- Restore evaluation_engine.rb with proper binding evaluator integration
- Restore remote.rb with proper remote configuration handling
- Update component.rb to instantiate evaluation engine and exposures
- Add engine method to main OpenFeature module
- Restore evaluation_engine_spec.rb with comprehensive test coverage
Remove unnecessary changes that were introduced during rebase:
- Revert lib/datadog/open_feature/configuration/settings.rb to base branch (comment, quotes, default value)
- Remove lib/datadog/open_feature/extensions.rb (not in base branch)
- Remove spec/datadog/open_feature/evaluator_spec.rb (orphaned spec file)
- Restore spec/datadog/open_feature/remote_spec.rb (exists in base branch)
- Revert vendor/rbs/openfeature-sdk/0/openfeature-sdk.rbs to base branch
- Fix lib/datadog/core/remote/client/capabilities.rb condition check
- Remove default_value parameter from engine.fetch_value call in provider.rb
- Revert sig/datadog/open_feature/provider.rbs parameter types and constants
… method

Simplifies the evaluator interface by removing unnecessary parameters:
- Removes default_value parameter from get_assignment method signature
- Removes time argument from evaluator invocation in evaluation engine
- Updates error handling to always return nil value in error cases
- Updates all RBS type signatures to match new method signatures

Error handling is now properly delegated to when engine.fetch_value is invoked with error_code.
…tation

Eliminates code duplication by removing the custom ResolutionDetails class from internal_evaluator.rb and using the shared Struct-based implementation from resolution_details.rb.

Key changes:
- Remove duplicate ResolutionDetails class definition from internal_evaluator.rb
- Add require for shared resolution_details.rb
- Update create_success_result and create_error_result to populate all fields
- Add flag_metadata with allocation_key (aligned with libdatadog assignment.rs)
- Add extra_logging field for future extensibility
- Remove duplicate ResolutionDetails definition from internal_evaluator.rbs
- Maintain compatibility with OpenFeature and libdatadog expectations
Remove the default_value argument from Internal Evaluator's get_assignment method and update all evaluator interfaces to align with libdatadog evaluation format. This change standardizes the evaluator API and ensures consistency with the underlying libdatadog FFI implementation.

Key Changes:
* Remove default_value parameter from get_assignment method signatures
* Update method signatures from 4 parameters to 3 parameters across all evaluators
* Convert ResolutionDetails from Struct to regular class to resolve superclass conflicts
* Update error code mapping to use :Ok for successful cases (matches libdatadog ErrorCode::Ok)
* Fix flag_metadata structure to use Hash access instead of method calls
* Update disabled flag handling to return :Ok with nil value (matches libdatadog behavior)
* Fix empty JSON handling to return :provider_not_ready instead of :parse_error
* Update all RBS type signatures to match new method signatures
* Update test expectations to align with libdatadog interface patterns

Breaking Changes:
* get_assignment method now accepts 3 parameters instead of 4
* ResolutionDetails is now a regular class instead of Struct
* Error codes now return :Ok for successful evaluations instead of nil
* Flag metadata accessed via Hash keys instead of object methods
- Change error_message from nil to empty string for Ok cases (disabled flags, successful evaluations)
- Update convert_reason_to_symbol to return SCREAMING_SNAKE_CASE strings matching libdatadog serde format
- Fix test expectations for disabled flag handling (nil value, empty metadata)
- Align error code expectations with libdatadog ErrorCode::Ok for success cases
…all tests

- Add support for camelCase 'targetingKey' in evaluation context (resolves most evaluation failures)
- Fix DEFAULT_ALLOCATION_NULL error mapping by using proper constant instead of string
- Update test expectations to match libdatadog FFI conventions:
  - flag_metadata always returns hash (empty {} or populated) instead of nil
  - Allow appropriate error codes for missing flags and other error conditions
  - Expect nil values for default allocation cases (upstream handles default_value)
The provider's `if result.error_code` check was always truthy when we returned :Ok for successful evaluations, causing all results to use default_value instead of evaluated values.

Key fixes:
- Successful evaluations now return error_code: nil (provider returns evaluated value)
- FlagDisabled/DefaultAllocationNull return :ok symbol (provider returns default_value)
- Convert symbols to OpenFeature strings at provider boundary
- Revert ResolutionDetails to Struct for cleaner data handling
- Standardize flag_metadata keys to consistent snake_case
- Update targeting key handling for OpenFeature SDK compatibility
* Add DEFAULT and DISABLED constants to AssignmentReason module
* Update error handling to use specific assignment reasons:
  - DEFAULT_ALLOCATION_NULL errors → AssignmentReason::DEFAULT
- FLAG_DISABLED errors → AssignmentReason::DISABLED
- Other errors → AssignmentReason::ERROR
* Align Ruby binding with libdatadog's complete FFI Reason enum
* Maintain backward compatibility with existing constants
* All 31 internal evaluator tests continue passing
* Remove duplicate UFC test runner (test_cases_spec.rb)
* Consolidate table-driven UFC tests into internal_evaluator_spec.rb
* Add missing DEFAULT and DISABLED constants to AssignmentReason
* Streamline rule_evaluation_spec.rb by removing UFC-covered scenarios
* Update error handling to use specific assignment reasons
* Preserve all UFC test case compatibility (~200 test cases across 19 files)
* Reduce test code by ~400+ lines while maintaining coverage
* All 40 core binding tests passing

Aligns Ruby binding with complete libdatadog FFI Reason enum and eliminates test redundancy without losing compatibility validation.
Remove convert_error_code method and SYMBOL_TO_OPENFEATURE_ERROR_CODE mapping from provider.rb as they are not required for the internal evaluator implementation task.

The provider should pass through error codes directly from the engine without additional conversion logic.
Comment on lines 84 to 85
error_code: :Ok, # :Ok indicates success (matches libdatadog ErrorCode::Ok)
error_message: '', # Empty string for Ok cases (matches libdatadog FFI)
Copy link
Author

Choose a reason for hiding this comment

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

Even though, a disabled flag code includes a message in the errors file ("flag is disabled")

https://github.com/DataDog/libdatadog/blob/3d9e641016f3a6c05bfdb1786c4b1a84342d4bbf/datadog-ffe/src/rules_based/error.rs#L39-L41

It is ignored in this line: let error_message = value.as_ref().err().map(|err| err.to_string()); The .err means we don't use non-error messages and a disabled flag is considered ok. At this point the error message is None

But then, this line in the FFI converts it to an empty string: None => BorrowedStr::empty(),

https://github.com/DataDog/libdatadog/blob/3d9e641016f3a6c05bfdb1786c4b1a84342d4bbf/datadog-ffe-ffi/src/assignment.rs#L436

Comment on lines 7 to 11
module AssignmentReason
TARGETING_MATCH = 'TARGETING_MATCH'
SPLIT = 'SPLIT'
STATIC = 'STATIC'
end
Copy link
Author

Choose a reason for hiding this comment

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

Copy link
Author

Choose a reason for hiding this comment

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

https://github.com/DataDog/libdatadog/blob/3d9e641016f3a6c05bfdb1786c4b1a84342d4bbf/datadog-ffe-ffi/src/assignment.rs#L144

pub enum Reason {
    Static,
    Default,
    TargetingMatch,
    Split,
    Disabled,
    Error,
}

https://github.com/DataDog/libdatadog/blob/3d9e641016f3a6c05bfdb1786c4b1a84342d4bbf/datadog-ffe-ffi/src/assignment.rs#L373-L391

impl From<&ResolutionDetails> for Reason {
    fn from(value: &ResolutionDetails) -> Self {
        match value.as_ref() {
            Ok(assignment) => assignment.reason.into(),
            Err(EvaluationError::FlagDisabled) => Reason::Disabled,
            Err(EvaluationError::DefaultAllocationNull) => Reason::Default,
            Err(_) => Reason::Error,
        }
    }
}
impl From<AssignmentReason> for Reason {
    fn from(value: AssignmentReason) -> Self {
        match value {
            AssignmentReason::TargetingMatch => Reason::TargetingMatch,
            AssignmentReason::Split => Reason::Split,
            AssignmentReason::Static => Reason::Static,
        }
    }
}

Comment on lines +12 to +14
TYPE_MISMATCH = 'TYPE_MISMATCH'
PARSE_ERROR = 'PARSE_ERROR'
FLAG_NOT_FOUND = 'FLAG_NOT_FOUND'
Copy link
Author

Choose a reason for hiding this comment

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

PARSE_ERROR = 'PARSE_ERROR'
FLAG_NOT_FOUND = 'FLAG_NOT_FOUND'

# Rust EvaluationError enum values (internal error codes)
Copy link
Author

Choose a reason for hiding this comment

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

attr_accessor variant: ::String?

attr_accessor error_code: ::String?
attr_accessor error_code: ::Symbol?
Copy link
Author

Choose a reason for hiding this comment

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

libdatadog returns an ErrorCode enum so a symbol felt a little more appropriate, but I can change it back to a string if that's preferred

* Fix configuration_spec.rb path issues and method calls
  - Correct fixture path from ../../../../ to ../../../
  - Replace undefined from_json with from_hash method
  - Replace be_in matcher with include matcher for RSpec compatibility

* Fix allocation matching test method signatures and expectations
  - Remove extra parameter from get_assignment calls (4 params → 3 params)
  - Update error expectations from :Ok to nil for successful evaluations
  - Fix flag_metadata access to use hash keys instead of method calls
  - Update test descriptions to reflect internal evaluator behavior

* Fix flag lookup logic in Configuration#get_flag
  - Search by flag.key property instead of hash key for proper UFC compatibility
  - Enables correct flag resolution where hash key != flag.key

* Update assignment reason expectations in provider tests
  - Change TARGETING_MATCH to STATIC for simple allocations without rules
  - Aligns with libdatadog assignment reason logic (no rules + single empty shard = STATIC)

All binding tests now pass (54 examples, 0 failures).
Remaining provider/engine integration issues need separate investigation.
* Fix OpenFeature.engine returning nil by enabling remote configuration
  - Component requires both open_feature.enabled and remote.enabled
  - Update test to configure both settings for proper initialization

* Fix provider test expected values to match flag configurations
  - Update integer expectation: 42 → 21 to match actual config value
  - Update number expectation: 9000 → 1000 to match actual config value
  - Update float expectation: 36.6 → 12.5 to match actual config value
  - Update object expectation: [1,2,3] → {"key":"value"} to match actual config

* Fix variation type mismatches in provider test configurations
  - Change "variationType": "NUMBER" → "NUMERIC" for UFC compliance
  - Change "variationType": "FLOAT" → "NUMERIC" for UFC compliance
  - Change "variationType": "OBJECT" → "JSON" for UFC compliance
  - Aligns with VariationType constants and internal evaluator type mapping

All OpenFeature tests now pass: 128 examples, 0 failures, 1 pending.
Complete end-to-end integration working from provider → engine → internal evaluator.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

appsec Application Security monitoring product core Involves Datadog core libraries

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants