openfeature-sdk 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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