openfeature-sdk 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 01c5b0fe23e56edfe9142031b5ad8b25e9f55b42e7eeb4ccb36692ee297b543f
4
- data.tar.gz: 87c4f9c43567db36526725b79c9a7075b3acb47949f07820b444ef31fd59af5f
3
+ metadata.gz: d79e98004b058036248ba05928ee5debe398a607a0d079f3d8d366fcde9bce07
4
+ data.tar.gz: 76c896c762a1e5a7828f7785864c0bbb2034214c5ffcc12988af044ad23d2806
5
5
  SHA512:
6
- metadata.gz: 82b18ea505c33933f3a8798013cfac8d660d2560badcb45f11a9ec1036ab40ba2b61d14f347754f9d679dd07214228ae953a6a5e4e153b1196d53e3163c94cfd
7
- data.tar.gz: 5af77fb1f0668477663dc04c1b7d759fc76ba899c693acf7cb53823133f89834b82ebe2591d8efb31d88638e950b33c53f91aa328c06a8abf98ce42fa795b5ea
6
+ metadata.gz: 62d7a0aa1d5110d346afd5d39317064ef5f5722138bdd90cf31f96aa3587cd3fc1083cfade3b7d8f9b02560075078010e82104fcba9939621e301dfa12e3cf85
7
+ data.tar.gz: 10540bcff19a5d57113d4b298f2f0ed9ddf13f3e1b2bdcff116ef27e6ac55c233a8b2ee4aba3468c40a31c698e9f3cbeb8208c2964f9df67551e632dd0190564
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.2.1"
2
+ ".": "0.3.0"
3
3
  }
data/.standard.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  parallel: true
2
- formatter: progress
2
+ format: progress
3
3
  ruby_version: 3.1
4
4
  plugins:
5
5
  - standard-performance
data/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.0](https://github.com/open-feature/ruby-sdk/compare/v0.2.1...v0.3.0) (2024-04-05)
4
+
5
+
6
+ ### ⚠ BREAKING CHANGES
7
+
8
+ * Add `EvaluationContext` helpers and context merging to flag evaluation ([#119](https://github.com/open-feature/ruby-sdk/issues/119))
9
+ * Separate `Client` and `Provider` metadata, add client creation tests ([#116](https://github.com/open-feature/ruby-sdk/issues/116))
10
+
11
+ ### Features
12
+
13
+ * Add `EvaluationContext` helpers and context merging to flag evaluation ([#119](https://github.com/open-feature/ruby-sdk/issues/119)) ([34e4795](https://github.com/open-feature/ruby-sdk/commit/34e47956d66e0c6763f58c818461aa52f628bd21))
14
+ * Add evaluation context based on requirement 3.1 ([#114](https://github.com/open-feature/ruby-sdk/issues/114)) ([f8e016f](https://github.com/open-feature/ruby-sdk/commit/f8e016f1cf7bf1ca7fddce7a41efdeb4d3d522c1))
15
+ * Flag Evaluation Requirement 1.1.4 and 1.1.5 and Provider Requirement 2.1.1 ([#112](https://github.com/open-feature/ruby-sdk/issues/112)) ([aac74b1](https://github.com/open-feature/ruby-sdk/commit/aac74b1e80a4b3e69983e55cf5c75b9cee37b71b))
16
+
17
+
18
+ ### Code Refactoring
19
+
20
+ * Separate `Client` and `Provider` metadata, add client creation tests ([#116](https://github.com/open-feature/ruby-sdk/issues/116)) ([f028c39](https://github.com/open-feature/ruby-sdk/commit/f028c398db3e2317847fe7e7bcbe6bbe96bb0b1c))
21
+
3
22
  ## [0.2.1](https://github.com/open-feature/ruby-sdk/compare/v0.2.0...v0.2.1) (2024-03-29)
4
23
 
5
24
 
data/Gemfile CHANGED
@@ -4,5 +4,3 @@ source "https://rubygems.org"
4
4
 
5
5
  # Specify your gem's dependencies in openfeature-sdk.gemspec
6
6
  gemspec
7
-
8
- gem "concurrent-ruby", require: "concurrent"
data/Gemfile.lock CHANGED
@@ -1,72 +1,69 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- openfeature-sdk (0.2.1)
4
+ openfeature-sdk (0.3.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
9
  ast (2.4.2)
10
- base64 (0.1.1)
11
- concurrent-ruby (1.2.3)
12
- debug (1.9.1)
10
+ debug (1.9.2)
13
11
  irb (~> 1.10)
14
12
  reline (>= 0.3.8)
15
- diff-lcs (1.5.0)
13
+ diff-lcs (1.5.1)
16
14
  docile (1.4.0)
17
15
  io-console (0.7.2)
18
- irb (1.11.2)
16
+ irb (1.12.0)
19
17
  rdoc
20
18
  reline (>= 0.4.2)
21
- json (2.6.3)
19
+ json (2.7.1)
22
20
  language_server-protocol (3.17.0.3)
23
21
  lint_roller (1.1.0)
24
22
  markly (0.10.0)
25
- parallel (1.23.0)
26
- parser (3.2.2.3)
23
+ parallel (1.24.0)
24
+ parser (3.3.0.5)
27
25
  ast (~> 2.4.1)
28
26
  racc
29
27
  psych (5.1.2)
30
28
  stringio
31
- racc (1.7.1)
29
+ racc (1.7.3)
32
30
  rainbow (3.1.1)
33
- rake (13.0.6)
34
- rdoc (6.6.2)
31
+ rake (13.1.0)
32
+ rdoc (6.6.3.1)
35
33
  psych (>= 4.0.0)
36
- regexp_parser (2.8.1)
37
- reline (0.4.3)
34
+ regexp_parser (2.9.0)
35
+ reline (0.5.0)
38
36
  io-console (~> 0.5)
39
37
  rexml (3.2.6)
40
38
  rspec (3.12.0)
41
39
  rspec-core (~> 3.12.0)
42
40
  rspec-expectations (~> 3.12.0)
43
41
  rspec-mocks (~> 3.12.0)
44
- rspec-core (3.12.2)
42
+ rspec-core (3.12.3)
45
43
  rspec-support (~> 3.12.0)
46
- rspec-expectations (3.12.3)
44
+ rspec-expectations (3.12.4)
47
45
  diff-lcs (>= 1.2.0, < 2.0)
48
46
  rspec-support (~> 3.12.0)
49
- rspec-mocks (3.12.6)
47
+ rspec-mocks (3.12.7)
50
48
  diff-lcs (>= 1.2.0, < 2.0)
51
49
  rspec-support (~> 3.12.0)
52
- rspec-support (3.12.1)
53
- rubocop (1.56.3)
54
- base64 (~> 0.1.1)
50
+ rspec-support (3.12.2)
51
+ rubocop (1.62.1)
55
52
  json (~> 2.3)
56
53
  language_server-protocol (>= 3.17.0)
57
54
  parallel (~> 1.10)
58
- parser (>= 3.2.2.3)
55
+ parser (>= 3.3.0.2)
59
56
  rainbow (>= 2.2.2, < 4.0)
60
57
  regexp_parser (>= 1.8, < 3.0)
61
58
  rexml (>= 3.2.5, < 4.0)
62
- rubocop-ast (>= 1.28.1, < 2.0)
59
+ rubocop-ast (>= 1.31.1, < 2.0)
63
60
  ruby-progressbar (~> 1.7)
64
61
  unicode-display_width (>= 2.4.0, < 3.0)
65
- rubocop-ast (1.29.0)
66
- parser (>= 3.2.1.0)
67
- rubocop-performance (1.19.0)
68
- rubocop (>= 1.7.0, < 2.0)
69
- rubocop-ast (>= 0.4.0)
62
+ rubocop-ast (1.31.2)
63
+ parser (>= 3.3.0.4)
64
+ rubocop-performance (1.20.2)
65
+ rubocop (>= 1.48.1, < 2.0)
66
+ rubocop-ast (>= 1.30.0, < 2.0)
70
67
  ruby-progressbar (1.13.0)
71
68
  simplecov (0.22.0)
72
69
  docile (~> 1.1)
@@ -77,20 +74,20 @@ GEM
77
74
  simplecov (~> 0.19)
78
75
  simplecov-html (0.12.3)
79
76
  simplecov_json_formatter (0.1.4)
80
- standard (1.31.1)
77
+ standard (1.35.1)
81
78
  language_server-protocol (~> 3.17.0.2)
82
79
  lint_roller (~> 1.0)
83
- rubocop (~> 1.56.2)
80
+ rubocop (~> 1.62.0)
84
81
  standard-custom (~> 1.0.0)
85
- standard-performance (~> 1.2)
82
+ standard-performance (~> 1.3)
86
83
  standard-custom (1.0.2)
87
84
  lint_roller (~> 1.0)
88
85
  rubocop (~> 1.50)
89
- standard-performance (1.2.0)
86
+ standard-performance (1.3.1)
90
87
  lint_roller (~> 1.1)
91
- rubocop-performance (~> 1.19.0)
88
+ rubocop-performance (~> 1.20.2)
92
89
  stringio (3.1.0)
93
- unicode-display_width (2.4.2)
90
+ unicode-display_width (2.5.0)
94
91
 
95
92
  PLATFORMS
96
93
  arm64-darwin-21
@@ -104,7 +101,6 @@ PLATFORMS
104
101
  x86_64-linux
105
102
 
106
103
  DEPENDENCIES
107
- concurrent-ruby
108
104
  debug
109
105
  markly
110
106
  openfeature-sdk!
data/README.md CHANGED
@@ -50,12 +50,18 @@ OpenFeature::SDK.configure do |config|
50
50
  ))
51
51
  # alternatively, you can bind multiple providers to different domains
52
52
  config.set_provider(OpenFeature::SDK::Provider::NoOpProvider.new, domain: "legacy_flags")
53
+ # you can set a global evaluation context here
54
+ config.evaluation_context = OpenFeature::SDK::EvaluationContext.new("host" => "myhost.com")
53
55
  end
54
56
 
55
57
  # Create a client
56
58
  client = OpenFeature::SDK.build_client
57
59
  # Create a client for a different domain, this will use the provider assigned to that domain
58
60
  legacy_flag_client = OpenFeature::SDK.build_client(domain: "legacy_flags")
61
+ # Evaluation context can be set on a client as well
62
+ client_with_context = OpenFeature::SDK.build_client(
63
+ evaluation_context: OpenFeature::SDK::EvaluationContext.new("controller_name" => "admin")
64
+ )
59
65
 
60
66
  # fetching boolean value feature flag
61
67
  bool_value = client.fetch_boolean_value(flag_key: 'boolean_flag', default_value: false)
@@ -69,6 +75,15 @@ integer_value = client.fetch_number_value(flag_key: 'number_value', default_valu
69
75
 
70
76
  # get an object value
71
77
  object = client.fetch_object_value(flag_key: 'object_value', default_value: JSON.dump({ name: 'object'}))
78
+
79
+ # Invocation evaluation context can also be passed in during flag evaluation.
80
+ # During flag evaluation, invocation context takes precedence over client context
81
+ # which takes precedence over API (aka global) context.
82
+ bool_value = client.fetch_boolean_value(
83
+ flag_key: 'boolean_flag',
84
+ default_value: false,
85
+ evaluation_context: OpenFeature::SDK::EvaluationContext.new("is_friday" => true)
86
+ )
72
87
  ```
73
88
 
74
89
  For complete documentation, visit: https://openfeature.dev/docs/category/concepts
@@ -4,9 +4,11 @@ require "forwardable"
4
4
  require "singleton"
5
5
 
6
6
  require_relative "configuration"
7
+ require_relative "evaluation_context"
8
+ require_relative "evaluation_context_builder"
7
9
  require_relative "evaluation_details"
10
+ require_relative "client_metadata"
8
11
  require_relative "client"
9
- require_relative "metadata"
10
12
  require_relative "provider"
11
13
 
12
14
  module OpenFeature
@@ -30,7 +32,7 @@ module OpenFeature
30
32
  include Singleton # Satisfies Flag Evaluation API Requirement 1.1.1
31
33
  extend Forwardable
32
34
 
33
- def_delegators :configuration, :provider, :set_provider, :hooks, :context
35
+ def_delegators :configuration, :provider, :set_provider, :hooks, :evaluation_context
34
36
 
35
37
  def configuration
36
38
  @configuration ||= Configuration.new
@@ -42,12 +44,12 @@ module OpenFeature
42
44
  block.call(configuration)
43
45
  end
44
46
 
45
- def build_client(name: nil, version: nil, domain: nil)
46
- client_options = Metadata.new(name: name, version: version, domain: domain).freeze
47
-
47
+ def build_client(domain: nil, evaluation_context: nil)
48
48
  active_provider = provider(domain:).nil? ? Provider::NoOpProvider.new : provider(domain:)
49
49
 
50
- Client.new(provider: active_provider, client_options:, context:)
50
+ Client.new(provider: active_provider, domain:, evaluation_context:)
51
+ rescue
52
+ Client.new(provider: Provider::NoOpProvider.new, evaluation_context:)
51
53
  end
52
54
  end
53
55
  end
@@ -8,14 +8,14 @@ module OpenFeature
8
8
  RESULT_TYPE = %i[boolean string number object].freeze
9
9
  SUFFIXES = %i[value details].freeze
10
10
 
11
- attr_reader :metadata
11
+ attr_reader :metadata, :evaluation_context
12
12
 
13
13
  attr_accessor :hooks
14
14
 
15
- def initialize(provider:, client_options: nil, context: nil)
15
+ def initialize(provider:, domain: nil, evaluation_context: nil)
16
16
  @provider = provider
17
- @metadata = client_options
18
- @context = context
17
+ @metadata = ClientMetadata.new(domain:)
18
+ @evaluation_context = evaluation_context
19
19
  @hooks = []
20
20
  end
21
21
 
@@ -26,7 +26,8 @@ module OpenFeature
26
26
  # result = @provider.fetch_boolean_value(flag_key: flag_key, default_value: default_value, evaluation_context: evaluation_context)
27
27
  # end
28
28
  def fetch_#{result_type}_#{suffix}(flag_key:, default_value:, evaluation_context: nil)
29
- resolution_details = @provider.fetch_#{result_type}_value(flag_key:, default_value:, evaluation_context:)
29
+ built_context = EvaluationContextBuilder.new.call(api_context: OpenFeature::SDK.evaluation_context, client_context: self.evaluation_context, invocation_context: evaluation_context)
30
+ resolution_details = @provider.fetch_#{result_type}_value(flag_key:, default_value:, evaluation_context: built_context)
30
31
  evaluation_details = EvaluationDetails.new(flag_key:, resolution_details:)
31
32
  #{"evaluation_details.value" if suffix == :value}
32
33
  end
@@ -0,0 +1,5 @@
1
+ module OpenFeature
2
+ module SDK
3
+ ClientMetadata = Struct.new(:domain, keyword_init: true)
4
+ end
5
+ end
@@ -1,21 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "concurrent"
4
-
5
3
  require_relative "api"
6
4
 
7
5
  module OpenFeature
8
6
  module SDK
9
7
  # Represents the configuration object for the global API where <tt>Provider</tt>, <tt>Hook</tt>,
10
- # and <tt>Context</tt> are configured.
8
+ # and <tt>EvaluationContext</tt> are configured.
11
9
  # This class is not meant to be interacted with directly but instead through the <tt>OpenFeature::SDK.configure</tt>
12
10
  # method
13
11
  class Configuration
14
12
  extend Forwardable
15
13
 
16
- attr_accessor :context, :hooks
17
-
18
- def_delegator :provider, :metadata
14
+ attr_accessor :evaluation_context, :hooks
19
15
 
20
16
  def initialize
21
17
  @hooks = []
@@ -23,7 +19,7 @@ module OpenFeature
23
19
  end
24
20
 
25
21
  def provider(domain: nil)
26
- @providers[domain]
22
+ @providers[domain] || @providers[nil]
27
23
  end
28
24
 
29
25
  # When switching providers, there are a few lifecycle methods that need to be taken care of.
@@ -0,0 +1,32 @@
1
+ module OpenFeature
2
+ module SDK
3
+ class EvaluationContext
4
+ TARGETING_KEY = "targeting_key"
5
+
6
+ attr_reader :fields
7
+
8
+ def initialize(**fields)
9
+ @fields = fields.transform_keys(&:to_s)
10
+ end
11
+
12
+ def targeting_key
13
+ fields[TARGETING_KEY]
14
+ end
15
+
16
+ def field(key)
17
+ fields[key]
18
+ end
19
+
20
+ def merge(overriding_context)
21
+ EvaluationContext.new(
22
+ targeting_key: overriding_context.targeting_key || targeting_key,
23
+ **fields.merge(overriding_context.fields)
24
+ )
25
+ end
26
+
27
+ def ==(other)
28
+ fields == other.fields
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,16 @@
1
+ module OpenFeature
2
+ module SDK
3
+ # Used to combine evaluation contexts from different sources
4
+ class EvaluationContextBuilder
5
+ def call(api_context:, client_context:, invocation_context:)
6
+ available_contexts = [api_context, client_context, invocation_context].compact
7
+
8
+ return nil if available_contexts.empty?
9
+
10
+ available_contexts.reduce(EvaluationContext.new) do |built_context, context|
11
+ built_context.merge(context)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -5,8 +5,10 @@ module OpenFeature
5
5
  class InMemoryProvider
6
6
  NAME = "In-memory Provider"
7
7
 
8
+ attr_reader :metadata
9
+
8
10
  def initialize(flags = {})
9
- @metadata = Metadata.new(name: NAME).freeze
11
+ @metadata = ProviderMetadata.new(name: NAME).freeze
10
12
  @flags = flags
11
13
  end
12
14
 
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../metadata"
4
-
5
3
  # rubocop:disable Lint/UnusedMethodArgument
6
4
  module OpenFeature
7
5
  module SDK
@@ -31,7 +29,7 @@ module OpenFeature
31
29
  attr_reader :metadata
32
30
 
33
31
  def initialize
34
- @metadata = Metadata.new(name: NAME).freeze
32
+ @metadata = ProviderMetadata.new(name: NAME).freeze
35
33
  end
36
34
 
37
35
  def fetch_boolean_value(flag_key:, default_value:, evaluation_context: nil)
@@ -0,0 +1,7 @@
1
+ module OpenFeature
2
+ module SDK
3
+ module Provider
4
+ ProviderMetadata = Struct.new(:name, keyword_init: true)
5
+ end
6
+ end
7
+ end
@@ -1,6 +1,7 @@
1
1
  require_relative "provider/error_code"
2
2
  require_relative "provider/reason"
3
3
  require_relative "provider/resolution_details"
4
+ require_relative "provider/provider_metadata"
4
5
  require_relative "provider/no_op_provider"
5
6
  require_relative "provider/in_memory_provider"
6
7
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module OpenFeature
4
4
  module SDK
5
- VERSION = "0.2.1"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openfeature-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OpenFeature Authors
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-03-29 00:00:00.000000000 Z
11
+ date: 2024-04-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: debug
@@ -147,13 +147,16 @@ files:
147
147
  - lib/open_feature/sdk.rb
148
148
  - lib/open_feature/sdk/api.rb
149
149
  - lib/open_feature/sdk/client.rb
150
+ - lib/open_feature/sdk/client_metadata.rb
150
151
  - lib/open_feature/sdk/configuration.rb
152
+ - lib/open_feature/sdk/evaluation_context.rb
153
+ - lib/open_feature/sdk/evaluation_context_builder.rb
151
154
  - lib/open_feature/sdk/evaluation_details.rb
152
- - lib/open_feature/sdk/metadata.rb
153
155
  - lib/open_feature/sdk/provider.rb
154
156
  - lib/open_feature/sdk/provider/error_code.rb
155
157
  - lib/open_feature/sdk/provider/in_memory_provider.rb
156
158
  - lib/open_feature/sdk/provider/no_op_provider.rb
159
+ - lib/open_feature/sdk/provider/provider_metadata.rb
157
160
  - lib/open_feature/sdk/provider/reason.rb
158
161
  - lib/open_feature/sdk/provider/resolution_details.rb
159
162
  - lib/open_feature/sdk/version.rb
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OpenFeature
4
- module SDK
5
- # Metadata structure that defines general metadata relating to a <tt>Provider</tt> or <tt>Client</tt>
6
- #
7
- # Within the Metadata structure, the following attribute readers are available:
8
- #
9
- # * <tt>name</tt> - Defines the name of the structure
10
- #
11
- # * <tt>version</tt> - Allows you to specify version of the Metadata structure
12
- #
13
- # * <tt>domain</tt> - Allows you to specify the domain of the Metadata structure
14
- #
15
- # Usage:
16
- #
17
- # metadata = Metadata.new(name: 'name-for-metadata', version: 'v1.1.3', domain: 'test')
18
- # metadata.name # 'name-for-metadata'
19
- # metadata.version # version
20
- # metadata_two = Metadata.new(name: 'name-for-metadata')
21
- # metadata_two == metadata # true - equality based on values
22
- class Metadata
23
- attr_reader :name, :version, :domain
24
-
25
- def initialize(name:, version: nil, domain: nil)
26
- @name = name
27
- @version = version
28
- @domain = domain
29
- end
30
-
31
- def ==(other)
32
- raise ArgumentError("Expected comparison to be between Metadata object") unless other.is_a?(Metadata)
33
-
34
- @name == other.name && @version == other.version
35
- end
36
- end
37
- end
38
- end