flipt_client 0.16.1 → 1.0.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: 717a1fe2a20f7f152ff30b3f64215ed35cfa281025513b5bd468f21852ed2a72
4
- data.tar.gz: fba45e5687bb3c2ff658c632a8222afa149e8438a93234cd38098322402aec3f
3
+ metadata.gz: 8cfa993fb8a7521e64c37cef82bf58088f8961d9779e6c5dc9092696a127c6ff
4
+ data.tar.gz: 2cddbf45abafabd21660a60ab64378a2b1c4bfdafee4b931805ca8b2a1c2d002
5
5
  SHA512:
6
- metadata.gz: 70130fe75d70bbc46a2ba89377a3c701c6b399fb0b2644f20c8f7fe9f0841bf012655478eda9aef92bee4eb30b55882365cb5eb2a789f757e49ce8cf3dec96e8
7
- data.tar.gz: c33564298208867ba51cf2b91df5ff806bfc48a1ff6e396625e5ce984d2031e8c0318cc537a00aa9e15263ce8a50943c695823e039334fe833226f251951a438
6
+ metadata.gz: b2cc595e3b40f37c420b920415d65e439a6295ddc1d1b4c22dc0fb2cb98e43e36912842568e1eb47246a088f15eb6bcce530e020daf0c2d34253fec9fa18347d
7
+ data.tar.gz: 26c75eb569c0e0df06cf287ae7f9cb5beb0466be5e1c1cb8ec49252d38277914afa842f56650d7dd857cb3696d73d17a68ea6082ada6101f3bbfa729142c934f
data/README.md CHANGED
@@ -26,12 +26,26 @@ Upon instantiation, the `flipt-client-ruby` library will fetch the flag state fr
26
26
 
27
27
  By default, the SDK will poll the Flipt server for new flag state at a regular interval. This interval can be configured using the `update_interval` option when constructing a client. The default interval is 120 seconds.
28
28
 
29
- ### Streaming (Flipt Cloud Only)
29
+ ### Streaming (Flipt Cloud/Flipt v2)
30
30
 
31
- [Flipt Cloud](https://flipt.io/cloud) users can use the `streaming` fetch method to stream flag state changes from the Flipt server to the SDK.
31
+ [Flipt Cloud](https://flipt.io/cloud) and [Flipt v2](https://docs.flipt.io/v2) users can use the `streaming` fetch method to stream flag state changes from the Flipt server to the SDK.
32
32
 
33
33
  When in streaming mode, the SDK will connect to the Flipt server and open a persistent connection that will remain open until the client is closed. The SDK will then receive flag state changes in real-time.
34
34
 
35
+ ### Retries
36
+
37
+ The SDK will automatically retry fetching (or initiating streaming) flag state if the client is unable to reach the Flipt server temporarily.
38
+
39
+ The SDK will retry up to 3 times with an exponential backoff interval between retries. The base delay is 1 second and the maximum delay is 30 seconds.
40
+
41
+ Retriable errors include:
42
+
43
+ - `429 Too Many Requests`
44
+ - `502 Bad Gateway`
45
+ - `503 Service Unavailable`
46
+ - `504 Gateway Timeout`
47
+ - Other potential transient network or DNS errors
48
+
35
49
  ## Supported Architectures
36
50
 
37
51
  This SDK currently supports the following OSes/architectures:
@@ -53,6 +67,21 @@ gem install ffi -- --enable-system-libffi # to install the gem manually
53
67
  bundle config build.ffi --enable-system-libffi # for bundle install
54
68
  ```
55
69
 
70
+ ## Migration Notes
71
+
72
+ ### Pre-1.0.0 -> 1.0.0
73
+
74
+ This section is for users who are migrating from a previous (pre-1.0.0) version of the SDK.
75
+
76
+ - `Flipt::EvaluationClient` has been renamed to `Flipt::Client`. Update all usages and imports accordingly. A deprecation warning is emitted if you use the old class.
77
+ - All evaluation methods now use **keyword arguments** (e.g., `flag_key:`, `entity_id:`, `context:`) instead of a single hash argument. Update your method calls to use keyword arguments.
78
+ - All evaluation methods now return **response model objects** (`VariantEvaluationResponse`, `BooleanEvaluationResponse`, `BatchEvaluationResponse`, `ErrorEvaluationResponse`) instead of raw hashes. Update your code to use attribute readers (e.g., `resp.flag_key`, `resp.enabled`).
79
+ - Batch evaluation responses now contain an array of model objects, not hashes.
80
+ - Error handling is now standardized and idiomatic. All errors inherit from `Flipt::Error` (with subclasses like `ValidationError`, `EvaluationError`). Update your rescue blocks accordingly.
81
+ - The minimum supported Ruby version is now 2.7.0.
82
+ - The client constructor now accepts keyword arguments for configuration (e.g., `url:`, `namespace:`, `authentication:`). The `environment` option is now supported.
83
+ - The API and documentation are more idiomatic and Ruby-like throughout.
84
+
56
85
  ## Usage
57
86
 
58
87
  In your Ruby code you can import this client and use it as so:
@@ -60,40 +89,35 @@ In your Ruby code you can import this client and use it as so:
60
89
  ```ruby
61
90
  require 'flipt_client'
62
91
 
63
- # namespace is the first positional argument and is optional here and will have a value of "default" if not specified.
64
- # opts is the second positional argument and is also optional, the structure is:
65
- # {
66
- # "url": "http://localhost:8080",
67
- # "update_interval": 120,
68
- # "authentication": {
69
- # "client_token": "secret"
70
- # }
71
- # }
72
- #
73
- # You can replace the url with where your upstream Flipt instance points to, the update interval for how long you are willing
74
- # to wait for updated flag state, and the auth token if your Flipt instance requires it.
75
- client = Flipt::EvaluationClient.new()
76
- resp = client.evaluate_variant({ flag_key: 'buzz', entity_id: 'someentity', context: { fizz: 'buzz' } })
77
-
78
- puts resp
92
+ client = Flipt::Client.new(url: 'http://localhost:8080', authentication: Flipt::ClientTokenAuthentication.new('secret'))
93
+
94
+ resp = client.evaluate_variant(flag_key: 'buzz', entity_id: 'someentity', context: { fizz: 'buzz' })
95
+ puts resp.flag_key # => 'buzz'
96
+ puts resp.match # => true
97
+ puts resp.reason # => 'MATCH_EVALUATION_REASON'
98
+
99
+ resp = client.evaluate_boolean(flag_key: 'my-feature', entity_id: 'someentity')
100
+ puts resp.enabled # => true
79
101
  ```
80
102
 
81
103
  ### Constructor Arguments
82
104
 
83
- The `Flipt::EvaluationClient` constructor accepts two optional arguments:
105
+ The `Flipt::Client` constructor accepts the following keyword arguments:
84
106
 
107
+ - `environment`: The environment (Flipt v2) to fetch flag state from. If not provided, the client will default to the `default` environment.
85
108
  - `namespace`: The namespace to fetch flag state from. If not provided, the client will default to the `default` namespace.
86
- - `opts`: A hash that supports several options for the client. The structure is:
87
- - `url`: The URL of the upstream Flipt instance. If not provided, the client will default to `http://localhost:8080`.
88
- - `update_interval`: The interval (in seconds) in which to fetch new flag state. If not provided, the client will default to 120 seconds.
89
- - `authentication`: The authentication strategy to use when communicating with the upstream Flipt instance. If not provided, the client will default to no authentication. See the [Authentication](#authentication) section for more information.
90
- - `reference`: The [reference](https://docs.flipt.io/guides/user/using-references) to use when fetching flag state. If not provided, reference will not be used.
91
- - `fetch_mode`: The fetch mode to use when fetching flag state. If not provided, the client will default to polling.
92
- - `error_strategy`: The error strategy to use when fetching flag state. If not provide, the client will be default to fail. See the [Error Strategies](#error-strategies) section for more information.
109
+ - `url`: The URL of the upstream Flipt instance. Defaults to `http://localhost:8080`.
110
+ - `request_timeout`: Timeout (in seconds) for requests. Defaults to no timeout.
111
+ - `update_interval`: Interval (in seconds) to fetch new flag state. Defaults to 120 seconds.
112
+ - `authentication`: The authentication strategy to use. Defaults to no authentication. See [Authentication](#authentication).
113
+ - `reference`: The [reference](https://docs.flipt.io/guides/user/using-references) to use when fetching flag state.
114
+ - `fetch_mode`: The fetch mode to use. Defaults to polling.
115
+ - `error_strategy`: The error strategy to use. Defaults to fail. See [Error Strategies](#error-strategies).
116
+ - `snapshot`: The snapshot to use when initializing the client. Defaults to no snapshot. See [Snapshotting](#snapshotting).
93
117
 
94
118
  ### Authentication
95
119
 
96
- The `FliptEvaluationClient` supports the following authentication strategies:
120
+ The `Flipt::Client` supports the following authentication strategies:
97
121
 
98
122
  - No Authentication (default)
99
123
  - [Client Token Authentication](https://docs.flipt.io/authentication/using-tokens)
@@ -106,6 +130,46 @@ The client supports the following error strategies:
106
130
  - `fail`: The client will throw an error if the flag state cannot be fetched. This is the default behavior.
107
131
  - `fallback`: The client will maintain the last known good state and use that state for evaluation in case of an error.
108
132
 
133
+ ### Response Models
134
+
135
+ All evaluation methods return response model objects:
136
+
137
+ - `evaluate_variant` returns a `Flipt::VariantEvaluationResponse`
138
+ - `evaluate_boolean` returns a `Flipt::BooleanEvaluationResponse`
139
+ - `evaluate_batch` returns a `Flipt::BatchEvaluationResponse`
140
+
141
+ ### Error Handling
142
+
143
+ All errors inherit from `Flipt::Error`. Common subclasses include:
144
+
145
+ - `Flipt::ValidationError`
146
+ - `Flipt::EvaluationError`
147
+
148
+ You can rescue these errors as needed:
149
+
150
+ ```ruby
151
+ begin
152
+ client.evaluate_variant(flag_key: 'missing', entity_id: 'user')
153
+ rescue Flipt::EvaluationError => e
154
+ puts "Evaluation failed: #{e.message}"
155
+ end
156
+ ```
157
+
158
+ ### Snapshotting
159
+
160
+ The client supports snapshotting of flag state as well as seeding the client with a snapshot for evaluation. This is helpful if you want to use the client in an environment where the Flipt server is not guaranteed to be available or reachable on startup.
161
+
162
+ To get the snapshot for the client, you can use the `snapshot` method. This returns a base64 encoded JSON string that represents the flag state for the client.
163
+
164
+ You can set the snapshot for the client using the `snapshot` option when constructing a client.
165
+
166
+ **Note:** You most likely will want to also set the `error_strategy` to `fallback` when using snapshots. This will ensure that you wont get an error if the Flipt server is not available or reachable even on the initial fetch.
167
+
168
+ You also may want to store the snapshot in a local file so that you can use it to seed the client on startup.
169
+
170
+ > [!IMPORTANT]
171
+ > If the Flipt server becomes reachable after the setting the snapshot, the client will replace the snapshot with the new flag state from the Flipt server.
172
+
109
173
  ## Load Test
110
174
 
111
175
  1. To run the load test, you'll need to have Flipt running locally. You can do this by running the following command from the root of the repository:
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
11
11
  spec.description = 'Flipt Client Evaluation SDK'
12
12
  spec.homepage = 'https://www.flipt.io'
13
13
  spec.license = 'MIT'
14
- spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
14
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.7.0')
15
15
 
16
16
  # spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
17
17
 
@@ -8,7 +8,35 @@
8
8
  *
9
9
  * This function will initialize an Engine and return a pointer back to the caller.
10
10
  */
11
- void *initialize_engine(const char *namespace_, const char *opts);
11
+ void *initialize_engine_ffi(const char *opts);
12
+
13
+ /**
14
+ * # Safety
15
+ *
16
+ * This function will initialize an Engine and return a pointer back to the caller.
17
+ */
18
+ void *initialize_engine(const char *opts);
19
+
20
+ /**
21
+ * # Safety
22
+ *
23
+ * This function will take in a pointer to the engine and return the current snapshot as a JSON string.
24
+ */
25
+ const char *get_snapshot_ffi(void *engine_ptr);
26
+
27
+ /**
28
+ * # Safety
29
+ *
30
+ * This function will take in a pointer to the engine and return the current snapshot as a JSON string.
31
+ */
32
+ const char *get_snapshot(void *engine_ptr);
33
+
34
+ /**
35
+ * # Safety
36
+ *
37
+ * This function will take in a pointer to the engine and return a variant evaluation response.
38
+ */
39
+ const char *evaluate_variant_ffi(void *engine_ptr, const char *evaluation_request);
12
40
 
13
41
  /**
14
42
  * # Safety
@@ -17,6 +45,13 @@ void *initialize_engine(const char *namespace_, const char *opts);
17
45
  */
18
46
  const char *evaluate_variant(void *engine_ptr, const char *evaluation_request);
19
47
 
48
+ /**
49
+ * # Safety
50
+ *
51
+ * This function will take in a pointer to the engine and return a boolean evaluation response.
52
+ */
53
+ const char *evaluate_boolean_ffi(void *engine_ptr, const char *evaluation_request);
54
+
20
55
  /**
21
56
  * # Safety
22
57
  *
@@ -24,6 +59,13 @@ const char *evaluate_variant(void *engine_ptr, const char *evaluation_request);
24
59
  */
25
60
  const char *evaluate_boolean(void *engine_ptr, const char *evaluation_request);
26
61
 
62
+ /**
63
+ * # Safety
64
+ *
65
+ * This function will take in a pointer to the engine and return a batch evaluation response.
66
+ */
67
+ const char *evaluate_batch_ffi(void *engine_ptr, const char *batch_evaluation_request);
68
+
27
69
  /**
28
70
  * # Safety
29
71
  *
@@ -34,30 +76,41 @@ const char *evaluate_batch(void *engine_ptr, const char *batch_evaluation_reques
34
76
  /**
35
77
  * # Safety
36
78
  *
37
- * This function will take in a pointer to the engine and return a list of flags for the given namespace.
79
+ * This function will take in a pointer to the engine and return a list of flags.
80
+ */
81
+ const char *list_flags_ffi(void *engine_ptr);
82
+
83
+ /**
84
+ * # Safety
85
+ *
86
+ * This function will take in a pointer to the engine and return a list of flags.
38
87
  */
39
88
  const char *list_flags(void *engine_ptr);
40
89
 
41
90
  /**
42
91
  * # Safety
43
92
  *
44
- * This function will free the memory occupied by the engine.
93
+ * This function will take in a pointer to the engine and destroy it.
94
+ */
95
+ void destroy_engine_ffi(void *engine_ptr);
96
+
97
+ /**
98
+ * # Safety
99
+ *
100
+ * This function will take in a pointer to the engine and destroy it.
45
101
  */
46
102
  void destroy_engine(void *engine_ptr);
47
103
 
48
104
  /**
49
105
  * # Safety
50
106
  *
51
- * This function will take in a pointer to the string and free the memory.
52
- * See Rust the safety section in CString::from_raw.
107
+ * This function will take in a pointer to a string and destroy it.
53
108
  */
54
- void destroy_string(char *ptr);
109
+ void destroy_string_ffi(char *ptr);
55
110
 
56
- // Add missing external declarations for Rust functions
57
- extern void* initialize_engine_ffi(const char* namespace, const char* options);
58
- extern const char* evaluate_boolean_ffi(void* engine, const char* request);
59
- extern const char* evaluate_variant_ffi(void* engine, const char* request);
60
- extern const char* evaluate_batch_ffi(void* engine, const char* request);
61
- extern const char* list_flags_ffi(void* engine);
62
- extern void destroy_engine_ffi(void* engine);
63
- extern void destroy_string_ffi(char* str);
111
+ /**
112
+ * # Safety
113
+ *
114
+ * This function will take in a pointer to a string and destroy it.
115
+ */
116
+ void destroy_string(char *ptr);
Binary file
Binary file
@@ -40,4 +40,66 @@ module Flipt
40
40
  }
41
41
  end
42
42
  end
43
+
44
+ # VariantEvaluationResponse
45
+ # @attr_reader [String] flag_key
46
+ # @attr_reader [Boolean] match
47
+ # @attr_reader [String] reason
48
+ # @attr_reader [String] variant_key
49
+ # @attr_reader [String, nil] variant_attachment
50
+ # @attr_reader [Array<String>] segment_keys
51
+ class VariantEvaluationResponse
52
+ attr_reader :flag_key, :match, :reason, :variant_key, :variant_attachment, :segment_keys
53
+
54
+ def initialize(flag_key:, match:, reason:, variant_key:, variant_attachment: nil, segment_keys: [])
55
+ @flag_key = flag_key
56
+ @match = match
57
+ @reason = reason
58
+ @variant_key = variant_key
59
+ @variant_attachment = variant_attachment
60
+ @segment_keys = segment_keys
61
+ end
62
+ end
63
+
64
+ # BooleanEvaluationResponse
65
+ # @attr_reader [String] flag_key
66
+ # @attr_reader [Boolean] enabled
67
+ # @attr_reader [String] reason
68
+ # @attr_reader [Array<String>] segment_keys
69
+ class BooleanEvaluationResponse
70
+ attr_reader :flag_key, :enabled, :reason, :segment_keys
71
+
72
+ def initialize(flag_key:, enabled:, reason:, segment_keys: [])
73
+ @flag_key = flag_key
74
+ @enabled = enabled
75
+ @reason = reason
76
+ @segment_keys = segment_keys
77
+ end
78
+ end
79
+
80
+ # ErrorEvaluationResponse
81
+ # @attr_reader [String] flag_key
82
+ # @attr_reader [String] namespace_key
83
+ # @attr_reader [String] reason
84
+ # @attr_reader [String] error_message
85
+ class ErrorEvaluationResponse
86
+ attr_reader :flag_key, :namespace_key, :reason, :error_message
87
+
88
+ def initialize(flag_key:, namespace_key:, reason:, error_message:)
89
+ @flag_key = flag_key
90
+ @namespace_key = namespace_key
91
+ @reason = reason
92
+ @error_message = error_message
93
+ end
94
+ end
95
+
96
+ # BatchEvaluationResponse
97
+ # @attr_reader [Array] responses
98
+ class BatchEvaluationResponse
99
+ attr_reader :responses
100
+
101
+ def initialize(responses: [])
102
+ @responses = responses
103
+ end
104
+ end
43
105
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Flipt
4
- VERSION = '0.16.1'
4
+ VERSION = '1.0.0'
5
5
  end
data/lib/flipt_client.rb CHANGED
@@ -7,9 +7,11 @@ require 'json'
7
7
 
8
8
  module Flipt
9
9
  class Error < StandardError; end
10
+ class ValidationError < Error; end
11
+ class EvaluationError < Error; end
10
12
 
11
- # EvaluationClient is a Ruby Client Side Evaluation Library for Flipt
12
- class EvaluationClient
13
+ # Client is a Ruby Client Side Evaluation Library for Flipt
14
+ class Client
13
15
  extend FFI::Library
14
16
 
15
17
  FLIPTENGINE = 'fliptengine'
@@ -32,8 +34,8 @@ module Flipt
32
34
 
33
35
  ffi_lib File.expand_path(libfile, __dir__)
34
36
 
35
- # void *initialize_engine(const char *namespace, const char *opts);
36
- attach_function :initialize_engine, %i[string string], :pointer
37
+ # void *initialize_engine(const char *opts);
38
+ attach_function :initialize_engine, [:string], :pointer
37
39
  # void destroy_engine(void *engine_ptr);
38
40
  attach_function :destroy_engine, [:pointer], :void
39
41
  # const char *evaluate_variant(void *engine_ptr, const char *evaluation_request);
@@ -46,26 +48,31 @@ module Flipt
46
48
  attach_function :list_flags, [:pointer], :strptr
47
49
  # void destroy_string(const char *ptr);
48
50
  attach_function :destroy_string, [:pointer], :void
51
+ # const char *get_snapshot(void *engine_ptr);
52
+ attach_function :get_snapshot, [:pointer], :strptr
49
53
 
50
54
  # Create a new Flipt client
51
55
  #
52
- # @param namespace [String] namespace
53
56
  # @param opts [Hash] options
57
+ # @option opts [String] :environment Flipt environment (default: 'default')
58
+ # @option opts [String] :namespace Flipt namespace (default: 'default')
54
59
  # @option opts [String] :url Flipt server url
55
60
  # @option opts [AuthenticationStrategy] :authentication strategy to authenticate with Flipt
61
+ # @option opts [Integer] :request_timeout timeout in seconds for the request
56
62
  # @option opts [Integer] :update_interval interval in seconds to update the cache
57
63
  # @option opts [String] :reference reference to use for namespace data
58
64
  # @option opts [Symbol] :fetch_mode fetch mode to use for the client (:polling or :streaming).
59
- # Note: Streaming is currently only supported when using the SDK with Flipt Cloud (https://flipt.io/cloud).
65
+ # Note: Streaming is currently only supported when using the SDK with Flipt Cloud or Flipt v2.
60
66
  # @option opts [Symbol] :error_strategy error strategy to use for the client (:fail or :fallback).
61
- def initialize(namespace = 'default', opts = {})
62
- @namespace = namespace
67
+ # @option opts [String] :snapshot snapshot to use when initializing the client
68
+ def initialize(**opts)
69
+ @namespace = opts.fetch(:namespace, 'default')
63
70
 
64
71
  opts[:authentication] = validate_authentication(opts.fetch(:authentication, NoAuthentication.new))
65
72
  opts[:fetch_mode] = validate_fetch_mode(opts.fetch(:fetch_mode, :polling))
66
73
  opts[:error_strategy] = validate_error_strategy(opts.fetch(:error_strategy, :fail))
67
74
 
68
- @engine = self.class.initialize_engine(namespace, opts.to_json)
75
+ @engine = self.class.initialize_engine(opts.to_json)
69
76
  ObjectSpace.define_finalizer(self, self.class.finalize(@engine))
70
77
  end
71
78
 
@@ -75,88 +82,154 @@ module Flipt
75
82
 
76
83
  # Evaluate a variant flag for a given request
77
84
  #
78
- # @param evaluation_request [Hash] evaluation request
79
- # @option evaluation_request [String] :entity_id entity id
80
- # @option evaluation_request [String] :flag_key flag key
81
- def evaluate_variant(evaluation_request = {})
82
- validate_evaluation_request(evaluation_request)
83
- resp, ptr = self.class.evaluate_variant(@engine, evaluation_request.to_json)
84
- ptr = FFI::AutoPointer.new(ptr, EvaluationClient.method(:destroy_string))
85
+ # @param flag_key [String]
86
+ # @param entity_id [String]
87
+ # @param context [Hash]
88
+ def evaluate_variant(flag_key:, entity_id:, context: {})
89
+ validate_evaluation_request(flag_key, entity_id, context)
90
+ req = { flag_key: flag_key, entity_id: entity_id, context: context }
91
+ resp, ptr = self.class.evaluate_variant(@engine, req.to_json)
92
+ ptr = FFI::AutoPointer.new(ptr, Client.method(:destroy_string))
85
93
  data = JSON.parse(resp)
86
- raise Error, data['error_message'] if data['status'] != 'success'
87
-
88
- data['result']
94
+ raise EvaluationError, data['error_message'] if data['status'] != 'success'
95
+
96
+ r = data['result']
97
+ VariantEvaluationResponse.new(
98
+ flag_key: r['flag_key'],
99
+ match: r['match'],
100
+ reason: r['reason'],
101
+ variant_key: r['variant_key'],
102
+ variant_attachment: r['variant_attachment'],
103
+ segment_keys: r['segment_keys'] || []
104
+ )
89
105
  end
90
106
 
91
107
  # Evaluate a boolean flag for a given request
92
108
  #
93
- # @param evaluation_request [Hash] evaluation request
94
- # @option evaluation_request [String] :entity_id entity id
95
- # @option evaluation_request [String] :flag_key flag key
96
- def evaluate_boolean(evaluation_request = {})
97
- validate_evaluation_request(evaluation_request)
98
- resp, ptr = self.class.evaluate_boolean(@engine, evaluation_request.to_json)
99
- ptr = FFI::AutoPointer.new(ptr, EvaluationClient.method(:destroy_string))
109
+ # @param flag_key [String]
110
+ # @param entity_id [String]
111
+ # @param context [Hash]
112
+ def evaluate_boolean(flag_key:, entity_id:, context: {})
113
+ validate_evaluation_request(flag_key, entity_id, context)
114
+ req = { flag_key: flag_key, entity_id: entity_id, context: context }
115
+ resp, ptr = self.class.evaluate_boolean(@engine, req.to_json)
116
+ ptr = FFI::AutoPointer.new(ptr, Client.method(:destroy_string))
100
117
  data = JSON.parse(resp)
101
- raise Error, data['error_message'] if data['status'] != 'success'
102
-
103
- data['result']
118
+ raise EvaluationError, data['error_message'] if data['status'] != 'success'
119
+
120
+ r = data['result']
121
+ BooleanEvaluationResponse.new(
122
+ flag_key: r['flag_key'],
123
+ enabled: r['enabled'],
124
+ reason: r['reason'],
125
+ segment_keys: r['segment_keys'] || []
126
+ )
104
127
  end
105
128
 
106
129
  # Evaluate a batch of flags for a given request
107
130
  #
108
- # @param batch_evaluation_request [Array<Hash>] batch evaluation request
131
+ # @param requests [Array<Hash>] batch evaluation request
109
132
  # - :entity_id [String] entity id
110
133
  # - :flag_key [String] flag key
111
- def evaluate_batch(batch_evaluation_request = [])
112
- batch_evaluation_request.each do |request|
113
- validate_evaluation_request(request)
134
+ def evaluate_batch(requests:)
135
+ unless requests.is_a?(Array)
136
+ raise ValidationError, 'requests must be an array of evaluation requests'
114
137
  end
115
138
 
116
- resp, ptr = self.class.evaluate_batch(@engine, batch_evaluation_request.to_json)
117
- ptr = FFI::AutoPointer.new(ptr, EvaluationClient.method(:destroy_string))
139
+ requests.each do |request|
140
+ validate_evaluation_request(request[:flag_key], request[:entity_id], request[:context] || {})
141
+ end
142
+ resp, ptr = self.class.evaluate_batch(@engine, requests.to_json)
143
+ ptr = FFI::AutoPointer.new(ptr, Client.method(:destroy_string))
118
144
  data = JSON.parse(resp)
119
- raise Error, data['error_message'] if data['status'] != 'success'
120
-
121
- data['result']
145
+ raise EvaluationError, data['error_message'] if data['status'] != 'success'
146
+
147
+ responses = (data['result']['responses'] || []).map do |r|
148
+ case r['type']
149
+ when 'VARIANT_EVALUATION_RESPONSE_TYPE'
150
+ v = r['variant_evaluation_response']
151
+ VariantEvaluationResponse.new(
152
+ flag_key: v['flag_key'],
153
+ match: v['match'],
154
+ reason: v['reason'],
155
+ variant_key: v['variant_key'],
156
+ variant_attachment: v['variant_attachment'],
157
+ segment_keys: v['segment_keys'] || []
158
+ )
159
+ when 'BOOLEAN_EVALUATION_RESPONSE_TYPE'
160
+ b = r['boolean_evaluation_response']
161
+ BooleanEvaluationResponse.new(
162
+ flag_key: b['flag_key'],
163
+ enabled: b['enabled'],
164
+ reason: b['reason'],
165
+ segment_keys: b['segment_keys'] || []
166
+ )
167
+ when 'ERROR_EVALUATION_RESPONSE_TYPE'
168
+ e = r['error_evaluation_response']
169
+ ErrorEvaluationResponse.new(
170
+ flag_key: e['flag_key'],
171
+ namespace_key: e['namespace_key'],
172
+ reason: e['reason'],
173
+ error_message: e['error_message']
174
+ )
175
+ else
176
+ raise EvaluationError, "Unknown response type encountered: #{r['type']}"
177
+ end
178
+ end
179
+ BatchEvaluationResponse.new(responses: responses)
122
180
  end
123
181
 
124
182
  # List all flags in the namespace
125
183
  def list_flags
126
184
  resp, ptr = self.class.list_flags(@engine)
127
- ptr = FFI::AutoPointer.new(ptr, EvaluationClient.method(:destroy_string))
185
+ ptr = FFI::AutoPointer.new(ptr, Client.method(:destroy_string))
128
186
  data = JSON.parse(resp)
129
187
  raise Error, data['error_message'] if data['status'] != 'success'
130
188
 
131
189
  data['result']
132
190
  end
133
191
 
192
+ # Get the snapshot of the current flag state
193
+ def snapshot
194
+ resp, ptr = self.class.get_snapshot(@engine)
195
+ ptr = FFI::AutoPointer.new(ptr, Client.method(:destroy_string))
196
+ resp
197
+ end
198
+
134
199
  private
135
200
 
136
- def validate_evaluation_request(evaluation_request)
137
- if evaluation_request[:entity_id].nil? || evaluation_request[:entity_id].empty?
138
- raise ArgumentError, 'entity_id is required'
139
- elsif evaluation_request[:flag_key].nil? || evaluation_request[:flag_key].empty?
140
- raise ArgumentError, 'flag_key is required'
141
- end
201
+ def validate_evaluation_request(flag_key, entity_id, context)
202
+ raise ValidationError, 'flag_key is required' if flag_key.nil? || flag_key.empty?
203
+ raise ValidationError, 'entity_id is required' if entity_id.nil? || entity_id.empty?
204
+ return if context.is_a?(Hash)
205
+
206
+ raise ValidationError, 'context must be a Hash<String, String>'
142
207
  end
143
208
 
144
209
  def validate_authentication(authentication)
145
210
  return authentication.strategy if authentication.is_a?(AuthenticationStrategy)
146
211
 
147
- raise ArgumentError, 'invalid authentication strategy'
212
+ raise ValidationError, 'invalid authentication strategy'
148
213
  end
149
214
 
150
215
  def validate_fetch_mode(fetch_mode)
151
216
  return fetch_mode if %i[polling streaming].include?(fetch_mode)
152
217
 
153
- raise ArgumentError, 'invalid fetch mode'
218
+ raise ValidationError, 'invalid fetch mode'
154
219
  end
155
220
 
156
221
  def validate_error_strategy(error_strategy)
157
222
  return error_strategy if %i[fail fallback].include?(error_strategy)
158
223
 
159
- raise ArgumentError, 'invalid error strategy'
224
+ raise ValidationError, 'invalid error strategy'
225
+ end
226
+ end
227
+
228
+ # Deprecation shim for EvaluationClient
229
+ class EvaluationClient < Client
230
+ def initialize(*args, **kwargs)
231
+ warn '[DEPRECATION] `EvaluationClient` is deprecated. Please use `Client` instead.'
232
+ super
160
233
  end
161
234
  end
162
235
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flipt_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Flipt Devs
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-03-07 00:00:00.000000000 Z
11
+ date: 2025-06-09 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Flipt Client Evaluation SDK
14
14
  email:
@@ -42,7 +42,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
42
42
  requirements:
43
43
  - - ">="
44
44
  - !ruby/object:Gem::Version
45
- version: 2.3.0
45
+ version: 2.7.0
46
46
  required_rubygems_version: !ruby/object:Gem::Requirement
47
47
  requirements:
48
48
  - - ">="