ff-ruby-server-sdk 0.0.2 → 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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.run/build.sh.run.xml +17 -0
  3. data/.run/install.sh.run.xml +17 -0
  4. data/.run/openapi.sh.run.xml +17 -0
  5. data/.run/publish.sh.run.xml +17 -0
  6. data/.run/sdk_test.rb.run.xml +3 -3
  7. data/.run/unpublish.sh.run.xml +17 -0
  8. data/CHANGELOG.md +1 -1
  9. data/Gemfile +20 -3
  10. data/README.md +155 -7
  11. data/api.yaml +736 -0
  12. data/example/example.rb +99 -3
  13. data/lib/ff/ruby/server/sdk/api/auth_service.rb +91 -0
  14. data/lib/ff/ruby/server/sdk/api/cf_client.rb +93 -0
  15. data/lib/ff/ruby/server/sdk/api/client_callback.rb +45 -0
  16. data/lib/ff/ruby/server/sdk/api/config.rb +140 -0
  17. data/lib/ff/ruby/server/sdk/api/config_builder.rb +116 -0
  18. data/lib/ff/ruby/server/sdk/api/default_cache.rb +112 -0
  19. data/lib/ff/ruby/server/sdk/api/evaluation.rb +29 -0
  20. data/lib/ff/ruby/server/sdk/api/evaluator.rb +526 -0
  21. data/lib/ff/ruby/server/sdk/api/file_map_store.rb +60 -0
  22. data/lib/ff/ruby/server/sdk/api/flag_evaluate_callback.rb +13 -0
  23. data/lib/ff/ruby/server/sdk/api/inner_client.rb +311 -0
  24. data/lib/ff/ruby/server/sdk/api/inner_client_flag_evaluate_callback.rb +30 -0
  25. data/lib/ff/ruby/server/sdk/api/inner_client_metrics_callback.rb +33 -0
  26. data/lib/ff/ruby/server/sdk/api/inner_client_repository_callback.rb +44 -0
  27. data/lib/ff/ruby/server/sdk/api/inner_client_updater.rb +63 -0
  28. data/lib/ff/ruby/server/sdk/api/metrics_callback.rb +19 -0
  29. data/lib/ff/ruby/server/sdk/api/metrics_event.rb +16 -0
  30. data/lib/ff/ruby/server/sdk/api/metrics_processor.rb +297 -0
  31. data/lib/ff/ruby/server/sdk/api/operators.rb +20 -0
  32. data/lib/ff/ruby/server/sdk/api/polling_processor.rb +164 -0
  33. data/lib/ff/ruby/server/sdk/api/repository_callback.rb +28 -0
  34. data/lib/ff/ruby/server/sdk/api/storage_repository.rb +263 -0
  35. data/lib/ff/ruby/server/sdk/api/summary_metrics.rb +16 -0
  36. data/lib/ff/ruby/server/sdk/api/update_processor.rb +149 -0
  37. data/lib/ff/ruby/server/sdk/common/cache.rb +27 -0
  38. data/lib/ff/ruby/server/sdk/common/closeable.rb +7 -0
  39. data/lib/ff/ruby/server/sdk/common/destroyable.rb +12 -0
  40. data/lib/ff/ruby/server/sdk/common/repository.rb +45 -0
  41. data/lib/ff/ruby/server/sdk/common/storage.rb +29 -0
  42. data/lib/ff/ruby/server/sdk/connector/connector.rb +44 -0
  43. data/lib/ff/ruby/server/sdk/connector/events.rb +118 -0
  44. data/lib/ff/ruby/server/sdk/connector/harness_connector.rb +236 -0
  45. data/lib/ff/ruby/server/sdk/connector/service.rb +19 -0
  46. data/lib/ff/ruby/server/sdk/connector/updater.rb +32 -0
  47. data/lib/ff/ruby/server/sdk/dto/message.rb +13 -0
  48. data/lib/ff/ruby/server/sdk/dto/target.rb +24 -0
  49. data/lib/ff/ruby/server/sdk/version.rb +2 -1
  50. data/lib/ff/ruby/server/sdk.rb +39 -3
  51. data/openapitools.json +7 -0
  52. data/scripts/openapi.sh +35 -0
  53. data/scripts/sdk_specs.sh +1 -1
  54. metadata +46 -3
  55. data/lib/ff/ruby/server/sdk/cf_client.rb +0 -6
@@ -0,0 +1,263 @@
1
+ require_relative "operators"
2
+ require_relative "repository_callback"
3
+ require_relative "../common/repository"
4
+
5
+ class StorageRepository < Repository
6
+
7
+ def initialize(cache, callback = nil, store = nil, logger = nil)
8
+
9
+ @cache = cache
10
+ @store = store
11
+ @callback = callback
12
+
13
+ if logger != nil
14
+
15
+ @logger = logger
16
+ else
17
+
18
+ @logger = Logger.new(STDOUT)
19
+ end
20
+ end
21
+
22
+ def get_flag(identifier, cacheable = true)
23
+
24
+ flag_key = format_flag_key(identifier)
25
+ flag = @cache.get(flag_key)
26
+
27
+ if flag != nil
28
+
29
+ return flag
30
+ end
31
+
32
+ if @store != nil
33
+
34
+ flag = @store.get(flag_key)
35
+ if flag != nil && cacheable
36
+
37
+ @cache.set(flag_key, flag)
38
+ end
39
+
40
+ return flag
41
+ end
42
+
43
+ nil
44
+ end
45
+
46
+ def get_segment(identifier, cacheable = true)
47
+
48
+ segment_key = format_segment_key(identifier)
49
+ segment = @cache.get(segment_key)
50
+
51
+ if segment != nil
52
+
53
+ return segment
54
+ end
55
+
56
+ if @store != nil
57
+
58
+ segment = @store.get(segment_key)
59
+ if segment != nil && cacheable
60
+
61
+ @cache.set(segment_key, segment)
62
+ end
63
+
64
+ return segment
65
+ end
66
+
67
+ nil
68
+ end
69
+
70
+ def find_flags_by_segment(identifier)
71
+
72
+ result = []
73
+ keys = @cache.keys
74
+
75
+ if @store != nil
76
+
77
+ keys = @store.keys
78
+ end
79
+
80
+ keys.each do |key|
81
+
82
+ flag = get_flag(key)
83
+
84
+ if flag != nil && !flag.rules.length > 0
85
+
86
+ flag.rules.each do |rule|
87
+
88
+ rule.clauses.each do |clause|
89
+
90
+ if clause.op == Operators.SEGMENT_MATCH && clause.values.include(identifier)
91
+
92
+ result.push(flag.feature)
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ result
100
+ end
101
+
102
+ def set_flag(identifier, feature_config)
103
+
104
+ if is_flag_outdated(identifier, feature_config)
105
+
106
+ @logger.debug "Flag " + identifier + " already exists"
107
+ return
108
+ end
109
+
110
+ flag_key = format_flag_key(identifier)
111
+
112
+ if @store != nil
113
+
114
+ @store.set(flag_key, feature_config)
115
+ @cache.delete(flag_key)
116
+
117
+ @logger.debug "Flag " + identifier + " successfully stored and cache invalidated"
118
+ else
119
+
120
+ @cache.set(flag_key, feature_config)
121
+
122
+ @logger.debug "Flag " + identifier + " successfully cached"
123
+ end
124
+
125
+ if @callback != nil
126
+
127
+ unless @callback.kind_of?(RepositoryCallback)
128
+
129
+ raise "The 'callback' parameter must be of '" + RepositoryCallback.to_s + "' data type"
130
+ end
131
+
132
+ @callback.on_flag_stored(identifier)
133
+ end
134
+ end
135
+
136
+ def set_segment(identifier, segment)
137
+
138
+ if is_segment_outdated(identifier, segment)
139
+
140
+ @logger.debug "Segment " + identifier + " already exists"
141
+ return
142
+ end
143
+
144
+ segment_key = format_segment_key(identifier)
145
+
146
+ if @store != nil
147
+
148
+ @store.set(segment_key, segment)
149
+ @cache.delete(segment_key)
150
+
151
+ @logger.debug "Segment " + identifier + " successfully stored and cache invalidated"
152
+ else
153
+
154
+ @cache.set(segment_key, segment)
155
+
156
+ @logger.debug "Segment " + identifier + " successfully cached"
157
+ end
158
+
159
+ if @callback != nil
160
+
161
+ unless @callback.kind_of?(RepositoryCallback)
162
+
163
+ raise "The 'callback' parameter must be of '" + RepositoryCallback.to_s + "' data type"
164
+ end
165
+
166
+ @callback.on_segment_stored(identifier)
167
+ end
168
+ end
169
+
170
+ def delete_flag(identifier)
171
+
172
+ flag_key = format_flag_key(identifier)
173
+
174
+ if @store != nil
175
+
176
+ @store.delete(flag_key)
177
+
178
+ @logger.debug "Flag " + identifier + " successfully deleted from store"
179
+ end
180
+
181
+ @cache.delete(flag_key)
182
+
183
+ @logger.debug "Flag " + identifier + " successfully deleted from cache"
184
+
185
+ if @callback != nil
186
+
187
+ unless @callback.kind_of?(RepositoryCallback)
188
+
189
+ raise "The 'callback' parameter must be of '" + RepositoryCallback.to_s + "' data type"
190
+ end
191
+
192
+ @callback.on_flag_deleted(identifier)
193
+ end
194
+ end
195
+
196
+ def delete_segment(identifier)
197
+
198
+ segment_key = format_segment_key(identifier)
199
+
200
+ if @store != nil
201
+
202
+ @store.delete(segment_key)
203
+
204
+ @logger.debug "Segment " + identifier + " successfully deleted from store"
205
+ end
206
+
207
+ @cache.delete(segment_key)
208
+
209
+ @logger.debug "Segment " + identifier + " successfully deleted from cache"
210
+
211
+ if @callback != nil
212
+
213
+ unless @callback.kind_of?(RepositoryCallback)
214
+
215
+ raise "The 'callback' parameter must be of '" + RepositoryCallback.to_s + "' data type"
216
+ end
217
+
218
+ @callback.on_segment_deleted(identifier)
219
+ end
220
+ end
221
+
222
+ def close
223
+
224
+ if @store != nil
225
+
226
+ @store.close
227
+ end
228
+ end
229
+
230
+ protected
231
+
232
+ def is_flag_outdated(identifier, new_feature_config)
233
+
234
+ flag = get_flag(identifier, false)
235
+ if flag != nil && flag.version != new_feature_config.version
236
+
237
+ return flag.version >= new_feature_config.version
238
+ end
239
+
240
+ false
241
+ end
242
+
243
+ def is_segment_outdated(identifier, new_segment)
244
+
245
+ segment = get_segment(identifier, false)
246
+ if segment != nil && segment.version != new_segment.version
247
+
248
+ return segment.version >= new_segment.version
249
+ end
250
+
251
+ false
252
+ end
253
+
254
+ def format_flag_key(identifier)
255
+
256
+ "flags_" + identifier
257
+ end
258
+
259
+ def format_segment_key(identifier)
260
+
261
+ "segments_" + identifier
262
+ end
263
+ end
@@ -0,0 +1,16 @@
1
+ class SummaryMetrics
2
+
3
+ attr_accessor :feature_name, :variation_identifier, :variation_value
4
+
5
+ def initialize(
6
+
7
+ feature_name,
8
+ variation_identifier,
9
+ variation_value
10
+ )
11
+
12
+ @feature_name = feature_name
13
+ @variation_value = variation_value
14
+ @variation_identifier = variation_identifier
15
+ end
16
+ end
@@ -0,0 +1,149 @@
1
+ require "concurrent-ruby"
2
+
3
+ require_relative "../common/closeable"
4
+
5
+ class UpdateProcessor < Closeable
6
+
7
+ def initialize(
8
+
9
+ connector,
10
+ repository,
11
+ callback,
12
+ logger
13
+ )
14
+
15
+ @connector = connector
16
+ @repository = repository
17
+ @updater = callback
18
+ @executor = Concurrent::FixedThreadPool.new(100)
19
+
20
+ if logger != nil
21
+
22
+ @logger = logger
23
+ else
24
+
25
+ @logger = Logger.new(STDOUT)
26
+ end
27
+ end
28
+
29
+ def start
30
+
31
+ @logger.info "Starting updater (EventSource)"
32
+
33
+ if @updater != nil
34
+
35
+ unless @updater.kind_of?(Updater)
36
+
37
+ raise "The 'callback' parameter must be of '" + Updater.to_s + "' data type"
38
+ end
39
+ end
40
+
41
+ if @connector != nil
42
+
43
+ unless @connector.kind_of?(Connector)
44
+
45
+ raise "The 'connector' must be of '" + Connector.to_s + "' data type"
46
+ end
47
+
48
+ @executor.post do
49
+
50
+ @stream = @connector.stream(@updater)
51
+ @stream.start
52
+ end
53
+ end
54
+ end
55
+
56
+ def stop
57
+
58
+ @logger.info "Stopping updater (EventSource)"
59
+
60
+ if @stream != nil
61
+
62
+ @stream.stop
63
+ end
64
+
65
+ @executor.shutdown
66
+ @executor.wait_for_termination(3)
67
+
68
+ if @executor.shuttingdown?
69
+
70
+ @executor.kill
71
+ end
72
+
73
+ @logger.info "Updater stopped (EventSource)"
74
+ end
75
+
76
+ def close
77
+
78
+ @logger.info "Closing UpdateProcessor"
79
+
80
+ stop
81
+ end
82
+
83
+ def update(message)
84
+
85
+ if message["domain"] == "flag"
86
+
87
+ @executor.post do
88
+
89
+ process_flag(message)
90
+ end
91
+
92
+ return
93
+ end
94
+
95
+ if message["domain"] == "target-segment"
96
+
97
+ @executor.post do
98
+
99
+ process_segment(message)
100
+ end
101
+ end
102
+ end
103
+
104
+ protected
105
+
106
+ def process_flag(message)
107
+
108
+ @logger.info "Processing flag message: " + message.to_s
109
+
110
+ config = @connector.get_flag(message["identifier"])
111
+
112
+ if config != nil
113
+
114
+ if message["event"] == "create" || message["event"] == "patch"
115
+
116
+ @repository.set_flag(message["identifier"], config)
117
+
118
+ else
119
+
120
+ if message["event"] == "delete"
121
+
122
+ @repository.delete_flag(message["identifier"])
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+ def process_segment(message)
129
+
130
+ @logger.info "Processing segment message: " + message.to_s
131
+
132
+ segment = @connector.get_segment(message["identifier"])
133
+
134
+ if segment != nil
135
+
136
+ if message["event"] == "create" || message["event"] == "patch"
137
+
138
+ @repository.set_segment(message.identifier, segment)
139
+
140
+ else
141
+
142
+ if message["event"] == "delete"
143
+
144
+ @repository.delete_segment(message["identifier"])
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,27 @@
1
+ class Cache
2
+
3
+ def initialize
4
+
5
+ @tbi = "To be implemented"
6
+ end
7
+
8
+ def set(key, value)
9
+
10
+ raise @tbi
11
+ end
12
+
13
+ def get(key)
14
+
15
+ raise @tbi
16
+ end
17
+
18
+ def delete(key)
19
+
20
+ raise @tbi
21
+ end
22
+
23
+ def keys
24
+
25
+ raise @tbi
26
+ end
27
+ end
@@ -0,0 +1,7 @@
1
+ class Closeable
2
+
3
+ def close
4
+
5
+ raise "Not implemented"
6
+ end
7
+ end
@@ -0,0 +1,12 @@
1
+ class Destroyable
2
+
3
+ def initialize
4
+
5
+ @tbi = "to be implemented"
6
+ end
7
+
8
+ def destroy
9
+
10
+ raise @tbi
11
+ end
12
+ end
@@ -0,0 +1,45 @@
1
+ require_relative "closeable"
2
+
3
+ class Repository < Closeable
4
+
5
+ def initialize
6
+ super
7
+
8
+ @tbi = "To be implemented"
9
+ end
10
+
11
+ def get_flag(identifier, cacheable = true)
12
+
13
+ raise @tbi
14
+ end
15
+
16
+ def get_segment(identifier, cacheable = true)
17
+
18
+ raise @tbi
19
+ end
20
+
21
+ def find_flags_by_segment(identifier)
22
+
23
+ raise @tbi
24
+ end
25
+
26
+ def set_flag(identifier, feature_config)
27
+
28
+ raise @tbi
29
+ end
30
+
31
+ def set_segment(identifier, segment)
32
+
33
+ raise @tbi
34
+ end
35
+
36
+ def delete_flag(identifier)
37
+
38
+ raise @tbi
39
+ end
40
+
41
+ def delete_segment(identifier)
42
+
43
+ raise @tbi
44
+ end
45
+ end
@@ -0,0 +1,29 @@
1
+ require_relative "closeable"
2
+
3
+ class Storage < Closeable
4
+
5
+ def initialize
6
+
7
+ @tbi = "To be implemented"
8
+ end
9
+
10
+ def set(key, value)
11
+
12
+ raise @tbi
13
+ end
14
+
15
+ def get(key)
16
+
17
+ raise @tbi
18
+ end
19
+
20
+ def delete(key)
21
+
22
+ raise @tbi
23
+ end
24
+
25
+ def keys
26
+
27
+ raise @tbi
28
+ end
29
+ end
@@ -0,0 +1,44 @@
1
+ require_relative '../common/closeable'
2
+
3
+ class Connector < Closeable
4
+
5
+ def initialize
6
+
7
+ @tbi = "To be implemented"
8
+ end
9
+
10
+ def authenticate
11
+
12
+ raise @tbi
13
+ end
14
+
15
+ def get_flags
16
+
17
+ raise @tbi
18
+ end
19
+
20
+ def get_flag(identifier)
21
+
22
+ raise @tbi
23
+ end
24
+
25
+ def get_segments
26
+
27
+ raise @tbi
28
+ end
29
+
30
+ def get_segment(identifier)
31
+
32
+ raise @tbi
33
+ end
34
+
35
+ def post_metrics(metrics)
36
+
37
+ raise @tbi
38
+ end
39
+
40
+ def stream(updater)
41
+
42
+ raise @tbi
43
+ end
44
+ end
@@ -0,0 +1,118 @@
1
+ require "json"
2
+ require "sse-client"
3
+
4
+ require_relative './service'
5
+
6
+ class Events < Service
7
+
8
+ def initialize(
9
+
10
+ url,
11
+ headers,
12
+ updater,
13
+ logger = nil
14
+ )
15
+
16
+ if @updater != nil
17
+
18
+ unless @updater.kind_of?(Updater)
19
+
20
+ raise "The 'callback' parameter must be of '" + Updater.to_s + "' data type"
21
+ end
22
+ end
23
+
24
+ @updater = updater
25
+
26
+ if logger != nil
27
+
28
+ @logger = logger
29
+ else
30
+
31
+ @logger = Logger.new(STDOUT)
32
+ end
33
+
34
+ @sse = SSE::EventSource.new(
35
+
36
+ url = url,
37
+ query = {},
38
+ headers = headers
39
+ )
40
+
41
+ @sse.open do
42
+
43
+ on_open
44
+ end
45
+
46
+ @sse.error do |error|
47
+
48
+ if error != nil
49
+
50
+ @logger.error "SSE ERROR: " + error.body
51
+ end
52
+
53
+ on_error
54
+ end
55
+
56
+ @sse.message do |message|
57
+
58
+ on_message(message)
59
+ end
60
+
61
+ @sse.on("*") do |message|
62
+
63
+ on_message(message)
64
+ end
65
+
66
+ @updater.on_ready
67
+ end
68
+
69
+ def start
70
+
71
+ @logger.info "Starting EventSource service"
72
+
73
+ @sse.start
74
+ end
75
+
76
+ def stop
77
+
78
+ @logger.info "Stopping EventSource service"
79
+
80
+ on_closed
81
+ end
82
+
83
+ def close
84
+
85
+ stop
86
+ end
87
+
88
+ def on_open
89
+
90
+ @logger.info "EventSource connected"
91
+
92
+ @updater.on_connected
93
+ end
94
+
95
+ def on_error
96
+
97
+ @logger.error "EventSource error"
98
+
99
+ @updater.on_error
100
+
101
+ stop
102
+ end
103
+
104
+ def on_closed
105
+
106
+ @logger.info "EventSource disconnected"
107
+
108
+ @updater.on_disconnected
109
+ end
110
+
111
+ def on_message(message)
112
+
113
+ @logger.debug "EventSource message received " + message.to_s
114
+
115
+ msg = JSON.parse(message)
116
+ @updater.update(msg)
117
+ end
118
+ end