diff --git a/contract-tests/client_entity.rb b/contract-tests/client_entity.rb index 871c6b2b..dc932691 100644 --- a/contract-tests/client_entity.rb +++ b/contract-tests/client_entity.rb @@ -3,6 +3,7 @@ require 'net/http' require 'launchdarkly-server-sdk' require './big_segment_store_fixture' +require './hook' require 'http' class ClientEntity @@ -62,6 +63,12 @@ def initialize(log, config) } end + if config[:hooks] + opts[:hooks] = config[:hooks][:hooks].map do |hook| + Hook.new(hook[:name], hook[:callbackUri], hook[:data] || {}) + end + end + startWaitTimeMs = config[:startWaitTimeMs] || 5_000 @client = LaunchDarkly::LDClient.new( diff --git a/contract-tests/hook.rb b/contract-tests/hook.rb new file mode 100644 index 00000000..89690ce9 --- /dev/null +++ b/contract-tests/hook.rb @@ -0,0 +1,68 @@ +require 'ldclient-rb' + +class Hook + include LaunchDarkly::Interfaces::Hooks::Hook + + # + # @param name [String] + # @param callback_uri [String] + # @param data [Hash] + # + def initialize(name, callback_uri, data) + @metadata = LaunchDarkly::Interfaces::Hooks::Metadata.new(name) + @callback_uri = callback_uri + @data = data + @context_filter = LaunchDarkly::Impl::ContextFilter.new(false, []) + end + + def metadata + @metadata + end + + # + # @param evaluation_series_context [LaunchDarkly::Interfaces::Hooks::EvaluationSeriesContext] + # @param data [Hash] + # + def before_evaluation(evaluation_series_context, data) + payload = { + evaluationSeriesContext: { + flagKey: evaluation_series_context.key, + context: @context_filter.filter(evaluation_series_context.context), + defaultValue: evaluation_series_context.default_value, + method: evaluation_series_context.method, + }, + evaluationSeriesData: data, + stage: 'beforeEvaluation', + } + result = HTTP.post(@callback_uri, json: payload) + + (data || {}).merge(@data[:beforeEvaluation] || {}) + end + + + # + # @param evaluation_series_context [LaunchDarkly::Interfaces::Hooks::EvaluationSeriesContext] + # @param data [Hash] + # @param detail [LaunchDarkly::EvaluationDetail] + # + def after_evaluation(evaluation_series_context, data, detail) + payload = { + evaluationSeriesContext: { + flagKey: evaluation_series_context.key, + context: @context_filter.filter(evaluation_series_context.context), + defaultValue: evaluation_series_context.default_value, + method: evaluation_series_context.method, + }, + evaluationSeriesData: data, + evaluationDetail: { + value: detail.value, + variationIndex: detail.variation_index, + reason: detail.reason, + }, + stage: 'afterEvaluation', + } + HTTP.post(@callback_uri, json: payload) + + (data || {}).merge(@data[:afterEvaluation] || {}) + end +end diff --git a/contract-tests/service.rb b/contract-tests/service.rb index 10d66e16..5f252970 100644 --- a/contract-tests/service.rb +++ b/contract-tests/service.rb @@ -39,6 +39,7 @@ 'polling-gzip', 'inline-context', 'anonymous-redaction', + 'evaluation-hooks', ], }.to_json end diff --git a/lib/ldclient-rb/interfaces.rb b/lib/ldclient-rb/interfaces.rb index c76683a9..956a5f6a 100644 --- a/lib/ldclient-rb/interfaces.rb +++ b/lib/ldclient-rb/interfaces.rb @@ -916,7 +916,7 @@ def metadata # @return [Hash] Data to use when executing the next state of the hook in the evaluation series. # def before_evaluation(evaluation_series_context, data) - {} + data end # @@ -953,19 +953,19 @@ def initialize(name) class EvaluationSeriesContext attr_reader :key attr_reader :context - attr_reader :value + attr_reader :default_value attr_reader :method # # @param key [String] # @param context [LaunchDarkly::LDContext] - # @param value [any] + # @param default_value [any] # @param method [Symbol] # - def initialize(key, context, value, method) + def initialize(key, context, default_value, method) @key = key @context = context - @value = value + @default_value = default_value @method = method end end diff --git a/lib/ldclient-rb/ldclient.rb b/lib/ldclient-rb/ldclient.rb index 927ef1f5..57e0c52b 100644 --- a/lib/ldclient-rb/ldclient.rb +++ b/lib/ldclient-rb/ldclient.rb @@ -245,6 +245,7 @@ def variation(key, context, default) # @return [EvaluationDetail] an object describing the result # def variation_detail(key, context, default) + context = Impl::Context::make_context(context) detail, _, _ = evaluate_with_hooks(key, context, default, :variation_detail) do evaluate_internal(key, context, default, true) end @@ -264,8 +265,8 @@ def variation_detail(key, context, default) # ``` # # @param key [String] - # @param context [String] - # @param default [String] + # @param context [LDContext] + # @param default [any] # @param method [Symbol] # @param &block [#call] Implicit passed block # @@ -633,6 +634,7 @@ def create_default_data_source(sdk_key, config, diagnostic_accumulator) # @return [Array] # def variation_with_flag(key, context, default) + context = Impl::Context::make_context(context) evaluate_with_hooks(key, context, default, :variation_detail) do evaluate_internal(key, context, default, false) end @@ -640,7 +642,7 @@ def variation_with_flag(key, context, default) # # @param key [String] - # @param context [Hash, LDContext] + # @param context [LDContext] # @param default [Object] # @param with_reasons [Boolean] # @@ -657,7 +659,6 @@ def evaluate_internal(key, context, default, with_reasons) return detail, nil, "no context provided" end - context = Impl::Context::make_context(context) unless context.valid? @config.logger.error { "[LDClient] Context was invalid for evaluation of flag '#{key}' (#{context.error}); returning default value" } detail = Evaluator.error_result(EvaluationReason::ERROR_USER_NOT_SPECIFIED, default)