Class: Shipeasy::OpenFeature::Provider

Inherits:
Object
  • Object
show all
Defined in:
lib/shipeasy/sdk/openfeature.rb

Overview

Shipeasy OpenFeature provider (server paradigm). Wraps a ‘Shipeasy::Engine`; evaluation is local against the cached blob, so resolution is effectively synchronous.

Constant Summary collapse

OF =
::OpenFeature::SDK::Provider
REASON_MAP =

Shipeasy ‘FlagDetail#reason` → [OpenFeature reason, optional error_code]. Per the cross-SDK contract (doc 20):

RULE_MATCH        TARGETING_MATCH
DEFAULT           DEFAULT
OFF               DISABLED
OVERRIDE          STATIC
FLAG_NOT_FOUND    ERROR (error_code FLAG_NOT_FOUND)
CLIENT_NOT_READY  ERROR (error_code PROVIDER_NOT_READY)
{
  Shipeasy::Engine::REASON_RULE_MATCH       => [OF::Reason::TARGETING_MATCH, nil],
  Shipeasy::Engine::REASON_DEFAULT          => [OF::Reason::DEFAULT, nil],
  Shipeasy::Engine::REASON_OFF              => [OF::Reason::DISABLED, nil],
  Shipeasy::Engine::REASON_OVERRIDE         => [OF::Reason::STATIC, nil],
  Shipeasy::Engine::REASON_FLAG_NOT_FOUND   => [OF::Reason::ERROR, OF::ErrorCode::FLAG_NOT_FOUND],
  Shipeasy::Engine::REASON_CLIENT_NOT_READY => [OF::Reason::ERROR, OF::ErrorCode::PROVIDER_NOT_READY],
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(client = nil) ⇒ Provider

Construct the provider. With no argument it resolves the global engine configured via ‘Shipeasy.configure(…)`, so callers never build an Engine themselves — construct it AFTER your `Shipeasy.configure` call. Pass an explicit engine only for advanced/multi-key setups.



74
75
76
77
78
79
80
81
82
83
# File 'lib/shipeasy/sdk/openfeature.rb', line 74

def initialize(client = nil)
  client ||= Shipeasy.engine
  if client.nil?
    raise Shipeasy::Error, "Shipeasy::OpenFeature::Provider.new needs " \
      "Shipeasy.configure { |c| c.api_key = … } to have run first " \
      "(or pass an explicit engine)."
  end
  @client = client
  @metadata = OF::ProviderMetadata.new(name: "shipeasy").freeze
end

Instance Attribute Details

#metadataObject (readonly)

Returns the value of attribute metadata.



68
69
70
# File 'lib/shipeasy/sdk/openfeature.rb', line 68

def 
  @metadata
end

Instance Method Details

#fetch_boolean_value(flag_key:, default_value:, evaluation_context: nil) ⇒ Object

— Boolean → gate ——————————————————



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/shipeasy/sdk/openfeature.rb', line 97

def fetch_boolean_value(flag_key:, default_value:, evaluation_context: nil)
  user = to_user(evaluation_context)
  detail = @client.get_flag_detail(flag_key, user)
  of_reason, error_code = REASON_MAP.fetch(detail.reason, [OF::Reason::UNKNOWN, nil])

  if error_code
    OF::ResolutionDetails.new(value: default_value, reason: of_reason, error_code: error_code)
  else
    OF::ResolutionDetails.new(value: detail.value, reason: of_reason)
  end
rescue => e
  OF::ResolutionDetails.new(
    value: default_value, reason: OF::Reason::ERROR,
    error_code: OF::ErrorCode::GENERAL, error_message: e.message,
  )
end

#fetch_float_value(flag_key:, default_value:, evaluation_context: nil) ⇒ Object



128
129
130
# File 'lib/shipeasy/sdk/openfeature.rb', line 128

def fetch_float_value(flag_key:, default_value:, evaluation_context: nil)
  resolve_config(flag_key, default_value) { |v| numeric?(v) }
end

#fetch_integer_value(flag_key:, default_value:, evaluation_context: nil) ⇒ Object



124
125
126
# File 'lib/shipeasy/sdk/openfeature.rb', line 124

def fetch_integer_value(flag_key:, default_value:, evaluation_context: nil)
  resolve_config(flag_key, default_value) { |v| v.is_a?(Integer) }
end

#fetch_number_value(flag_key:, default_value:, evaluation_context: nil) ⇒ Object



120
121
122
# File 'lib/shipeasy/sdk/openfeature.rb', line 120

def fetch_number_value(flag_key:, default_value:, evaluation_context: nil)
  resolve_config(flag_key, default_value) { |v| numeric?(v) }
end

#fetch_object_value(flag_key:, default_value:, evaluation_context: nil) ⇒ Object



132
133
134
# File 'lib/shipeasy/sdk/openfeature.rb', line 132

def fetch_object_value(flag_key:, default_value:, evaluation_context: nil)
  resolve_config(flag_key, default_value) { |v| v.is_a?(Hash) || v.is_a?(Array) }
end

#fetch_string_value(flag_key:, default_value:, evaluation_context: nil) ⇒ Object

— String / number / integer / float / object → dynamic config ——–



116
117
118
# File 'lib/shipeasy/sdk/openfeature.rb', line 116

def fetch_string_value(flag_key:, default_value:, evaluation_context: nil)
  resolve_config(flag_key, default_value) { |v| v.is_a?(String) }
end

#init(_evaluation_context = nil) ⇒ Object

OpenFeature lifecycle (optional but supported): fetch the blob once and tear down the poll thread on shutdown.



87
88
89
# File 'lib/shipeasy/sdk/openfeature.rb', line 87

def init(_evaluation_context = nil)
  @client.init_once
end

#shutdownObject



91
92
93
# File 'lib/shipeasy/sdk/openfeature.rb', line 91

def shutdown
  @client.destroy
end

#track(tracking_event_name, evaluation_context: nil, tracking_event_details: nil) ⇒ Object

OpenFeature ‘track()` → Shipeasy `track()`. No-ops without a targeting key.



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/shipeasy/sdk/openfeature.rb', line 137

def track(tracking_event_name, evaluation_context: nil, tracking_event_details: nil)
  ctx = normalize_context(evaluation_context)
  user_id = ctx["targeting_key"] || ctx["user_id"]
  return if user_id.nil? || user_id.to_s.empty?

  # Base props = the evaluation-context attributes (minus the identity
  # keys), with the tracking-event details merged on top.
  props = ctx.reject { |k, _| k == "targeting_key" || k == "user_id" }

  detail_fields = if tracking_event_details.respond_to?(:fields)
                    tracking_event_details.fields.transform_keys(&:to_s)
                  elsif tracking_event_details.is_a?(Hash)
                    tracking_event_details.transform_keys(&:to_s)
                  else
                    {}
                  end
  props = props.merge(detail_fields)

  if tracking_event_details.respond_to?(:value) && !tracking_event_details.value.nil?
    props["value"] = tracking_event_details.value
  end

  @client.track(user_id, tracking_event_name, props)
end