featurehub-sdk 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,227 @@
1
+ # frozen_string_literal: true
2
+
3
+ # these are bulk copied in from OpenAPI and left in original case
4
+ module FeatureHub
5
+ module Sdk
6
+ # rubocop:disable Naming/ConstantName
7
+ # rubocop:disable Metrics/ModuleLength
8
+ module StrategyAttributeCountryName
9
+ Afghanistan = "afghanistan"
10
+ Albania = "albania"
11
+ Algeria = "algeria"
12
+ Andorra = "andorra"
13
+ Angola = "angola"
14
+ AntiguaAndBarbuda = "antigua_and_barbuda"
15
+ Argentina = "argentina"
16
+ Armenia = "armenia"
17
+ Australia = "australia"
18
+ Austria = "austria"
19
+ Azerbaijan = "azerbaijan"
20
+ TheBahamas = "the_bahamas"
21
+ Bahrain = "bahrain"
22
+ Bangladesh = "bangladesh"
23
+ Barbados = "barbados"
24
+ Belarus = "belarus"
25
+ Belgium = "belgium"
26
+ Belize = "belize"
27
+ Benin = "benin"
28
+ Bhutan = "bhutan"
29
+ Bolivia = "bolivia"
30
+ BosniaAndHerzegovina = "bosnia_and_herzegovina"
31
+ Botswana = "botswana"
32
+ Brazil = "brazil"
33
+ Brunei = "brunei"
34
+ Bulgaria = "bulgaria"
35
+ BurkinaFaso = "burkina_faso"
36
+ Burundi = "burundi"
37
+ CaboVerde = "cabo_verde"
38
+ Cambodia = "cambodia"
39
+ Cameroon = "cameroon"
40
+ Canada = "canada"
41
+ CentralAfricanRepublic = "central_african_republic"
42
+ Chad = "chad"
43
+ Chile = "chile"
44
+ China = "china"
45
+ Colombia = "colombia"
46
+ Comoros = "comoros"
47
+ CongoDemocraticRepublicOfThe = "congo_democratic_republic_of_the"
48
+ CongoRepublicOfThe = "congo_republic_of_the"
49
+ CostaRica = "costa_rica"
50
+ CoteDivoire = "cote_divoire"
51
+ Croatia = "croatia"
52
+ Cuba = "cuba"
53
+ Cyprus = "cyprus"
54
+ CzechRepublic = "czech_republic"
55
+ Denmark = "denmark"
56
+ Djibouti = "djibouti"
57
+ Dominica = "dominica"
58
+ DominicanRepublic = "dominican_republic"
59
+ EastTimor = "east_timor"
60
+ Ecuador = "ecuador"
61
+ Egypt = "egypt"
62
+ ElSalvador = "el_salvador"
63
+ EquatorialGuinea = "equatorial_guinea"
64
+ Eritrea = "eritrea"
65
+ Estonia = "estonia"
66
+ Eswatini = "eswatini"
67
+ Ethiopia = "ethiopia"
68
+ Fiji = "fiji"
69
+ Finland = "finland"
70
+ France = "france"
71
+ Gabon = "gabon"
72
+ TheGambia = "the_gambia"
73
+ Georgia = "georgia"
74
+ Germany = "germany"
75
+ Ghana = "ghana"
76
+ Greece = "greece"
77
+ Grenada = "grenada"
78
+ Guatemala = "guatemala"
79
+ Guinea = "guinea"
80
+ GuineaBissau = "guinea_bissau"
81
+ Guyana = "guyana"
82
+ Haiti = "haiti"
83
+ Honduras = "honduras"
84
+ Hungary = "hungary"
85
+ Iceland = "iceland"
86
+ India = "india"
87
+ Indonesia = "indonesia"
88
+ Iran = "iran"
89
+ Iraq = "iraq"
90
+ Ireland = "ireland"
91
+ Israel = "israel"
92
+ Italy = "italy"
93
+ Jamaica = "jamaica"
94
+ Japan = "japan"
95
+ Jordan = "jordan"
96
+ Kazakhstan = "kazakhstan"
97
+ Kenya = "kenya"
98
+ Kiribati = "kiribati"
99
+ KoreaNorth = "korea_north"
100
+ KoreaSouth = "korea_south"
101
+ Kosovo = "kosovo"
102
+ Kuwait = "kuwait"
103
+ Kyrgyzstan = "kyrgyzstan"
104
+ Laos = "laos"
105
+ Latvia = "latvia"
106
+ Lebanon = "lebanon"
107
+ Lesotho = "lesotho"
108
+ Liberia = "liberia"
109
+ Libya = "libya"
110
+ Liechtenstein = "liechtenstein"
111
+ Lithuania = "lithuania"
112
+ Luxembourg = "luxembourg"
113
+ Madagascar = "madagascar"
114
+ Malawi = "malawi"
115
+ Malaysia = "malaysia"
116
+ Maldives = "maldives"
117
+ Mali = "mali"
118
+ Malta = "malta"
119
+ MarshallIslands = "marshall_islands"
120
+ Mauritania = "mauritania"
121
+ Mauritius = "mauritius"
122
+ Mexico = "mexico"
123
+ MicronesiaFederatedStatesOf = "micronesia_federated_states_of"
124
+ Moldova = "moldova"
125
+ Monaco = "monaco"
126
+ Mongolia = "mongolia"
127
+ Montenegro = "montenegro"
128
+ Morocco = "morocco"
129
+ Mozambique = "mozambique"
130
+ Myanmar = "myanmar"
131
+ Namibia = "namibia"
132
+ Nauru = "nauru"
133
+ Nepal = "nepal"
134
+ Netherlands = "netherlands"
135
+ NewZealand = "new_zealand"
136
+ Nicaragua = "nicaragua"
137
+ Niger = "niger"
138
+ Nigeria = "nigeria"
139
+ NorthMacedonia = "north_macedonia"
140
+ Norway = "norway"
141
+ Oman = "oman"
142
+ Pakistan = "pakistan"
143
+ Palau = "palau"
144
+ Panama = "panama"
145
+ PapuaNewGuinea = "papua_new_guinea"
146
+ Paraguay = "paraguay"
147
+ Peru = "peru"
148
+ Philippines = "philippines"
149
+ Poland = "poland"
150
+ Portugal = "portugal"
151
+ Qatar = "qatar"
152
+ Romania = "romania"
153
+ Russia = "russia"
154
+ Rwanda = "rwanda"
155
+ SaintKittsAndNevis = "saint_kitts_and_nevis"
156
+ SaintLucia = "saint_lucia"
157
+ SaintVincentAndTheGrenadines = "saint_vincent_and_the_grenadines"
158
+ Samoa = "samoa"
159
+ SanMarino = "san_marino"
160
+ SaoTomeAndPrincipe = "sao_tome_and_principe"
161
+ SaudiArabia = "saudi_arabia"
162
+ Senegal = "senegal"
163
+ Serbia = "serbia"
164
+ Seychelles = "seychelles"
165
+ SierraLeone = "sierra_leone"
166
+ Singapore = "singapore"
167
+ Slovakia = "slovakia"
168
+ Slovenia = "slovenia"
169
+ SolomonIslands = "solomon_islands"
170
+ Somalia = "somalia"
171
+ SouthAfrica = "south_africa"
172
+ Spain = "spain"
173
+ SriLanka = "sri_lanka"
174
+ Sudan = "sudan"
175
+ SudanSouth = "sudan_south"
176
+ Suriname = "suriname"
177
+ Sweden = "sweden"
178
+ Switzerland = "switzerland"
179
+ Syria = "syria"
180
+ Taiwan = "taiwan"
181
+ Tajikistan = "tajikistan"
182
+ Tanzania = "tanzania"
183
+ Thailand = "thailand"
184
+ Togo = "togo"
185
+ Tonga = "tonga"
186
+ TrinidadAndTobago = "trinidad_and_tobago"
187
+ Tunisia = "tunisia"
188
+ Turkey = "turkey"
189
+ Turkmenistan = "turkmenistan"
190
+ Tuvalu = "tuvalu"
191
+ Uganda = "uganda"
192
+ Ukraine = "ukraine"
193
+ UnitedArabEmirates = "united_arab_emirates"
194
+ UnitedKingdom = "united_kingdom"
195
+ UnitedStates = "united_states"
196
+ Uruguay = "uruguay"
197
+ Uzbekistan = "uzbekistan"
198
+ Vanuatu = "vanuatu"
199
+ VaticanCity = "vatican_city"
200
+ Venezuela = "venezuela"
201
+ Vietnam = "vietnam"
202
+ Yemen = "yemen"
203
+ Zambia = "zambia"
204
+ Zimbabwe = "zimbabwe"
205
+ end
206
+
207
+ module StrategyAttributeDeviceName
208
+ Browser = "browser"
209
+ Mobile = "mobile"
210
+ Desktop = "desktop"
211
+ Server = "server"
212
+ Watch = "watch"
213
+ Embedded = "embedded"
214
+ end
215
+
216
+ module StrategyAttributePlatformName
217
+ Linux = "linux"
218
+ Windows = "windows"
219
+ Macos = "macos"
220
+ Android = "android"
221
+ Ios = "ios"
222
+ end
223
+
224
+ # rubocop:enable Metrics/ModuleLength
225
+ # rubocop:enable Naming/ConstantName
226
+ end
227
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ld-eventsource"
4
+ require "json"
5
+
6
+ module FeatureHub
7
+ module Sdk
8
+ # provides a streaming service
9
+ class StreamingEdgeService < FeatureHub::Sdk::EdgeService
10
+ attr_reader :repository, :sse_client, :url, :closed
11
+
12
+ def initialize(repository, api_keys, edge_url, logger = nil)
13
+ super(repository, api_keys, edge_url)
14
+
15
+ @url = "#{edge_url}features/#{api_keys[0]}"
16
+ @repository = repository
17
+ @sse_client = nil
18
+ @closed = true
19
+ @logger = logger || FeatureHub::Sdk.default_logger
20
+ end
21
+
22
+ def poll
23
+ start_streaming unless @sse_client
24
+ end
25
+
26
+ def active
27
+ !@closed && !@sse_client.nil?
28
+ end
29
+
30
+ def close
31
+ @closed = true
32
+ return if @sse_client.nil?
33
+
34
+ @sse_client.close
35
+ @sse_client = nil
36
+ end
37
+
38
+ private
39
+
40
+ def start_streaming
41
+ @closed = false
42
+ @logger.info("streaming from #{@url}")
43
+ @sse_client = SSE::Client.new(@url) do |client|
44
+ client.on_event do |event|
45
+ @repository.notify(event.type, JSON.parse(event.data))
46
+ end
47
+ client.on_error do |error|
48
+ if error.is_a?(SSE::Errors::HTTPStatusError) && (error.status == 404)
49
+ @repository.notify("failure", nil)
50
+ close
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FeatureHub
4
+ # already documented elsewhere
5
+ module Sdk
6
+ VERSION = "1.0.0"
7
+
8
+ def default_logger
9
+ log = ::Logger.new($stdout)
10
+ log.level = ::Logger::WARN
11
+ log.progname = "featurehub-sdk"
12
+ log
13
+ end
14
+
15
+ module_function :default_logger
16
+ end
17
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "feature_hub/sdk/impl/rollout_holders"
4
+ require_relative "feature_hub/sdk/impl/apply_features"
5
+ require_relative "feature_hub/sdk/percentage_calc"
6
+ require_relative "feature_hub/sdk/impl/murmur3_percentage"
7
+ require_relative "feature_hub/sdk/version"
8
+ require_relative "feature_hub/sdk/context"
9
+ require_relative "feature_hub/sdk/feature_hub_config"
10
+ require_relative "feature_hub/sdk/internal_feature_repository"
11
+ require_relative "feature_hub/sdk/feature_repository"
12
+ require_relative "feature_hub/sdk/feature_state"
13
+ require_relative "feature_hub/sdk/interceptors"
14
+ require_relative "feature_hub/sdk/strategy_attributes"
15
+ require_relative "feature_hub/sdk/impl/strategy_wrappers"
16
+ require_relative "feature_hub/sdk/poll_edge_service"
17
+ require_relative "feature_hub/sdk/streaming_edge_service"
18
+
19
+ # the FeatureHub SDK namespace
20
+ module FeatureHub
21
+ # the main SDK
22
+
23
+ module Sdk
24
+ # classes inside here are intended to be internal details, do not rely on them
25
+ module Impl
26
+ end
27
+
28
+ class Error < StandardError; end
29
+ end
30
+ end
@@ -0,0 +1,256 @@
1
+ module FeatureHub
2
+ module Sdk
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ #
6
+
7
+ class FeatureState
8
+ attr_reader key: Symbol
9
+
10
+ def initialize: (key: String, repo: InternalFeatureRepository,
11
+ feature_state: Hash[untyped, untyped]?, parent_state: FeatureState?, ctx: ClientContext?) -> void
12
+
13
+ def locked?: -> bool
14
+ def exists?: (top_feature: FeatureState?) -> bool
15
+ def id: -> String?
16
+ def feature_type: -> String?
17
+
18
+ def with_context: (ctx: ClientContext) -> FeatureState
19
+
20
+ def update_feature_state: (feature_state: Hash[untyped, untyped]) -> void
21
+ # this is the feature state of the top level, it always walks up
22
+ def feature_state: () -> Hash[untyped, untyped]
23
+
24
+ # this is strictly the internal feature state, further down from the top level it will be nil
25
+ def internal_feature_state: () -> Hash[untyped, untyped]
26
+
27
+ def value: () -> [bool? | String? | Float?]
28
+
29
+ def version: () -> int
30
+
31
+ def string: () -> String?
32
+
33
+ def number: () -> Float
34
+
35
+ def raw_json: () -> String?
36
+
37
+ def boolean: () -> bool?
38
+
39
+ def flag: () -> bool?
40
+
41
+ def enabled?: () -> bool
42
+
43
+ def set?: () -> bool?
44
+
45
+ private
46
+
47
+ def top_feature_state: -> FeatureState
48
+
49
+ def _feature_state: -> Hash[untyped, untyped]
50
+
51
+ def _set_feature_state: (feature_state: Hash[untyped, untyped]) -> void
52
+
53
+ def get_value: (feature_type: String?) -> [bool? | String? | Float?]
54
+ end
55
+
56
+ class Applied
57
+ attr_reader matched: bool
58
+ attr_reader value: [bool? | String? | Float?]
59
+
60
+ def initialize:(matched: bool, value: [bool? | String? | Float?]) -> void
61
+ end
62
+
63
+ class InterceptorValue
64
+ def initialize: (val: [bool? | String? | Float? ]) -> void
65
+ def cast:(feature_type: String?) -> [bool? | String? | Float? ]
66
+ end
67
+
68
+ class ValueInterceptor
69
+ def intercepted_value: (feature_key: Symbol) -> InterceptorValue?
70
+ end
71
+
72
+ class EnvironmentInterceptor < ValueInterceptor
73
+ end
74
+
75
+ class PercentageCalculator
76
+ def determine_client_percentage: (percentage_text: String, feature_id: String) -> int
77
+ end
78
+
79
+ class ApplyFeatures
80
+ def initialize: (percent_calculator: PercentageCalculator?, matcher_repository: MatcherRepository?) -> void
81
+ def apply: (strategies: Array[RolloutStrategy], key: String, feature_value_id: String, context: ClientContext) -> Applied
82
+ def match_attribute: (context: ClientContext, rs: RolloutStrategyAttribute) -> bool
83
+ def self.determine_percentage_key: (context: ClientContext, rsi: RolloutStrategy) -> String?
84
+ end
85
+
86
+ class InternalFeatureRepository
87
+ def feature: (key: String) -> FeatureState?
88
+ def find_interceptor: (feature_value: String) -> InterceptorValue?
89
+ def ready?: -> bool
90
+ def not_ready!: -> void
91
+ def apply: (strategies: Array[RolloutStrategy], key: String, feature_id: String, context: ClientContext) -> Applied
92
+ def notify: (status: String, data: Hash[untyped, untyped]) -> void
93
+ end
94
+
95
+ class FeatureHubRepository < InternalFeatureRepository
96
+ @features: Hash[String, FeatureState]
97
+ @ready: bool
98
+
99
+ def initialize: (apply_features: nil | ApplyFeatures ) -> void
100
+ def apply:(strategies: Array[RolloutStrategy], key: String, feature_id: String, context: ClientContext) -> Applied
101
+
102
+ def notify: (status: String, data: Hash[untyped, untyped]) -> void
103
+ def feature: (key: String) -> FeatureState
104
+ def register_interceptor: (interceptor: ValueInterceptor) -> void
105
+
106
+ def find_interceptor: (feature_value: String) -> InterceptorValue?
107
+ def ready?: -> bool
108
+ def not_ready!: -> void
109
+
110
+ def extract_feature_state: -> Array[Hash[untyped, untyped]]
111
+ end
112
+
113
+ class ClientContext
114
+ attr_reader repo: InternalFeatureRepository
115
+
116
+ def initialize: (repository: InternalFeatureRepository) -> void
117
+ def user_key: (value: String) -> ClientContext
118
+ def session_key: (value: String) -> ClientContext
119
+ def version: (value: String) -> ClientContext
120
+ def country: (value: Symbol ) -> ClientContext
121
+ def platform: (value: Symbol ) -> ClientContext
122
+ def device: (value: Symbol ) -> ClientContext
123
+ def attribute_value: (key: String, values: Array[String] ) -> ClientContext
124
+ def clear: -> ClientContext
125
+ def get_attr: (key: String, default_val: String?) -> String?
126
+ def default_percentage_key: -> String?
127
+ def enabled: (key: String) -> bool?
128
+
129
+ def feature: (key: String) -> FeatureState
130
+ def set?: (key: String) -> bool?
131
+ def number: (key: String) -> Float?
132
+ def string: (key: String) -> String?
133
+ def json: (key: String) -> Hash[untyped, untyped]?
134
+ def raw_json: (key: String) -> String?
135
+ def flag: (key: String) -> bool?
136
+ def boolean: (key: String) -> bool?
137
+ def exists?: (key: String) -> bool
138
+
139
+ def build: -> ClientContext
140
+ def build_sync: -> ClientContext
141
+ def close: -> ClientContext
142
+ end
143
+
144
+ class EdgeService
145
+ def initialize: (repository: InternalFeatureRepository, api_keys: Array[String], edge_url: String) -> void
146
+ def poll: -> void
147
+ def context_change: (new_header: String?) -> void
148
+ def close: -> void
149
+ end
150
+
151
+ class ServerEvalFeatureContext < ClientContext
152
+ @edge: EdgeService
153
+
154
+ def initialize: (repository: InternalFeatureRepository, edge: EdgeService) -> void
155
+ end
156
+
157
+ class ClientEvalFeatureContext < ClientContext
158
+ @edge: EdgeService
159
+
160
+ def initialize: (repository: InternalFeatureRepository, edge: EdgeService) -> void
161
+ end
162
+
163
+ class FeatureHubConfig
164
+ attr_reader edge_url: String
165
+ attr_reader api_keys: Array[String]
166
+ attr_reader client_evaluated: bool
167
+
168
+ def initialize: (edge_url: String, api_keys: Array[String], repository: InternalFeatureRepository) -> void
169
+
170
+ def repository: (repository: InternalFeatureRepository?) -> InternalFeatureRepository
171
+
172
+ def init: -> void
173
+
174
+ def get_or_create_edge_service: -> EdgeService
175
+
176
+ # can't figure this out
177
+ # def edge_service_provider:
178
+ #
179
+ def use_polling_edge_service: (interval: int?) -> void
180
+
181
+ def new_context: -> ClientContext
182
+
183
+ def close: -> void
184
+ end
185
+
186
+ class PollingEdgeService
187
+
188
+ end
189
+
190
+ class RolloutStrategyCondition
191
+ def equals?: -> bool
192
+ def ends_with?: -> bool
193
+ def starts_with?: -> bool
194
+ def greater?: -> bool
195
+ def greater_equals?: -> bool
196
+ def less?: -> bool
197
+ def less_equals?: -> bool
198
+ def not_equals?: -> bool
199
+ def includes?: -> bool
200
+ def excludes?: -> bool
201
+ def regex?: -> bool
202
+ end
203
+
204
+ class RolloutStrategyAttribute
205
+ attr_reader id: String
206
+ attr_reader conditional: RolloutStrategyCondition
207
+ attr_reader field_name: String
208
+ attr_reader values: Array[[bool? | String? | Float?]]
209
+ attr_reader field_type: String
210
+
211
+ def float_values: -> Array[Float]
212
+ def str_values: -> Array[String]
213
+ end
214
+
215
+ class RolloutStrategy
216
+ attr_reader attributes: Array[RolloutStrategyAttribute]
217
+ attr_reader id: String
218
+ attr_reader name: String?
219
+ attr_reader percentage: int
220
+ attr_reader percentage_attributes: Array[String]
221
+ attr_reader value: [bool? | String? | Float?]
222
+
223
+ def percentage_attributes?: -> bool
224
+ def attributes?: -> bool
225
+ end
226
+
227
+ class StrategyMatcher
228
+ def match: (supplied_value: String, attr: RolloutStrategyAttribute ) -> bool
229
+ end
230
+
231
+ class BooleanMatcher < StrategyMatcher
232
+ def match: (supplied_value: String, attr: RolloutStrategyAttribute ) -> bool
233
+ end
234
+
235
+ class StringMatcher < StrategyMatcher
236
+ end
237
+
238
+ class NumberMatcher < StrategyMatcher
239
+ end
240
+
241
+ class SemanticVersionMatcher < StrategyMatcher
242
+ end
243
+
244
+ class IPNetworkMatcher < StrategyMatcher
245
+ end
246
+
247
+ class MatcherRepository
248
+ def find_matcher: (attr: RolloutStrategyAttribute) -> StrategyMatcher
249
+ end
250
+
251
+ class MatcherRegistry < MatcherRepository
252
+ end
253
+ end
254
+
255
+ end
256
+ end