yggdrasil-engine 0.0.2 → 0.0.3.beta.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +36 -36
- data/lib/custom_strategy.rb +60 -60
- data/lib/x86_64-pc-windows-gnu/yggdrasilffi.dll +0 -0
- data/lib/yggdrasil_engine.rb +114 -114
- data/spec/custom_strategy_spec.rb +136 -136
- data/spec/yggdrasil_engine_spec.rb +139 -139
- metadata +7 -21
- data/lib/libyggdrasilffi.so +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 374876a9c18a7d7bec48b8910ed683aa6985a29d177259387c49b91784312e1a
|
4
|
+
data.tar.gz: 55b60e48ea625e9a4bfe34ecd405cea25bbd6c3b9487b8f80ba7e219dcb9637f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dcac8c9b7d0d4db83844f6f9f32b832d1c9cee4e8176e84d8c100960d6cefc6dd51bb1dc0f5b83d4d14d808c3ce9881fceffcbcde6bd44aef93bc6665bac0540
|
7
|
+
data.tar.gz: 4881b0ae99329cb46ceb1f2dd041e08074b349b54201201f014dedf4843b9e689ab15924f634971aa19aae2297a64f6a6e5e9adb4880e607c3dcf6e5e4cb5cdd
|
data/README.md
CHANGED
@@ -1,36 +1,36 @@
|
|
1
|
-
# Ruby Bindings to Yggdrasil
|
2
|
-
|
3
|
-
## Running the tests
|
4
|
-
|
5
|
-
First make sure that you have built the native FFI code and it's located in the right place. This can be done with the build script in `build.sh`:
|
6
|
-
|
7
|
-
|
8
|
-
```bash
|
9
|
-
./build.sh
|
10
|
-
```
|
11
|
-
|
12
|
-
Then you can run the tests with:
|
13
|
-
|
14
|
-
```bash
|
15
|
-
rspec
|
16
|
-
```
|
17
|
-
|
18
|
-
There's also a `mem_check.rb` in the scripts folder. This is not a bullet proof test, but it can be helpful for detecting large leaks. This requires human interaction - you need to read the output and understand what it's telling you, so it's not run as part of the test suite.
|
19
|
-
|
20
|
-
```bash
|
21
|
-
ruby scripts/mem_check.rb
|
22
|
-
```
|
23
|
-
|
24
|
-
## Build
|
25
|
-
|
26
|
-
You can build the gem with:
|
27
|
-
|
28
|
-
```bash
|
29
|
-
gem build yggdrasil-engine.gemspec
|
30
|
-
```
|
31
|
-
|
32
|
-
Then you can install the gem for local development with:
|
33
|
-
|
34
|
-
```
|
35
|
-
gem install yggdrasil-engine-0.0.1.gem
|
36
|
-
```
|
1
|
+
# Ruby Bindings to Yggdrasil
|
2
|
+
|
3
|
+
## Running the tests
|
4
|
+
|
5
|
+
First make sure that you have built the native FFI code and it's located in the right place. This can be done with the build script in `build.sh`:
|
6
|
+
|
7
|
+
|
8
|
+
```bash
|
9
|
+
./build.sh
|
10
|
+
```
|
11
|
+
|
12
|
+
Then you can run the tests with:
|
13
|
+
|
14
|
+
```bash
|
15
|
+
rspec
|
16
|
+
```
|
17
|
+
|
18
|
+
There's also a `mem_check.rb` in the scripts folder. This is not a bullet proof test, but it can be helpful for detecting large leaks. This requires human interaction - you need to read the output and understand what it's telling you, so it's not run as part of the test suite.
|
19
|
+
|
20
|
+
```bash
|
21
|
+
ruby scripts/mem_check.rb
|
22
|
+
```
|
23
|
+
|
24
|
+
## Build
|
25
|
+
|
26
|
+
You can build the gem with:
|
27
|
+
|
28
|
+
```bash
|
29
|
+
gem build yggdrasil-engine.gemspec
|
30
|
+
```
|
31
|
+
|
32
|
+
Then you can install the gem for local development with:
|
33
|
+
|
34
|
+
```
|
35
|
+
gem install yggdrasil-engine-0.0.1.gem
|
36
|
+
```
|
data/lib/custom_strategy.rb
CHANGED
@@ -1,60 +1,60 @@
|
|
1
|
-
STANDARD_STRATEGIES = [
|
2
|
-
"default",
|
3
|
-
"userWithId",
|
4
|
-
"gradualRolloutUserId",
|
5
|
-
"gradualRolloutSessionId",
|
6
|
-
"gradualRolloutRandom",
|
7
|
-
"flexibleRollout",
|
8
|
-
"remoteAddress",
|
9
|
-
].freeze
|
10
|
-
|
11
|
-
class CustomStrategyHandler
|
12
|
-
def initialize
|
13
|
-
@custom_strategies_definitions = {}
|
14
|
-
@custom_strategy_implementations = {}
|
15
|
-
end
|
16
|
-
|
17
|
-
def update_strategies(json_str)
|
18
|
-
custom_strategies = {}
|
19
|
-
parsed_json = JSON.parse(json_str)
|
20
|
-
|
21
|
-
parsed_json["features"].each do |feature|
|
22
|
-
toggle_name = feature["name"]
|
23
|
-
strategies = feature["strategies"]
|
24
|
-
|
25
|
-
custom_strategies_for_toggle = strategies.select do |strategy|
|
26
|
-
!STANDARD_STRATEGIES.include?(strategy["name"])
|
27
|
-
end
|
28
|
-
|
29
|
-
unless custom_strategies_for_toggle.empty?
|
30
|
-
custom_strategies[toggle_name] = custom_strategies_for_toggle
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
@custom_strategies_definitions = custom_strategies
|
35
|
-
end
|
36
|
-
|
37
|
-
def register_custom_strategies(strategies)
|
38
|
-
strategies.each do |strategy|
|
39
|
-
if strategy.respond_to?(:name) && strategy.name.is_a?(String) &&
|
40
|
-
strategy.respond_to?(:enabled?)
|
41
|
-
@custom_strategy_implementations[strategy.name] = strategy
|
42
|
-
else
|
43
|
-
raise "Invalid strategy object. Must have a name method that returns a String and an enabled? method."
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def evaluate_custom_strategies(toggle_name, context)
|
49
|
-
results = {}
|
50
|
-
|
51
|
-
@custom_strategies_definitions[toggle_name]&.each_with_index do |strategy, index|
|
52
|
-
key = "customStrategy#{index + 1}"
|
53
|
-
strategy_impl = @custom_strategy_implementations[strategy["name"]]
|
54
|
-
result = strategy_impl&.enabled?(strategy["parameters"], context) || false
|
55
|
-
results[key] = result
|
56
|
-
end
|
57
|
-
|
58
|
-
results
|
59
|
-
end
|
60
|
-
end
|
1
|
+
STANDARD_STRATEGIES = [
|
2
|
+
"default",
|
3
|
+
"userWithId",
|
4
|
+
"gradualRolloutUserId",
|
5
|
+
"gradualRolloutSessionId",
|
6
|
+
"gradualRolloutRandom",
|
7
|
+
"flexibleRollout",
|
8
|
+
"remoteAddress",
|
9
|
+
].freeze
|
10
|
+
|
11
|
+
class CustomStrategyHandler
|
12
|
+
def initialize
|
13
|
+
@custom_strategies_definitions = {}
|
14
|
+
@custom_strategy_implementations = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def update_strategies(json_str)
|
18
|
+
custom_strategies = {}
|
19
|
+
parsed_json = JSON.parse(json_str)
|
20
|
+
|
21
|
+
parsed_json["features"].each do |feature|
|
22
|
+
toggle_name = feature["name"]
|
23
|
+
strategies = feature["strategies"]
|
24
|
+
|
25
|
+
custom_strategies_for_toggle = strategies.select do |strategy|
|
26
|
+
!STANDARD_STRATEGIES.include?(strategy["name"])
|
27
|
+
end
|
28
|
+
|
29
|
+
unless custom_strategies_for_toggle.empty?
|
30
|
+
custom_strategies[toggle_name] = custom_strategies_for_toggle
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
@custom_strategies_definitions = custom_strategies
|
35
|
+
end
|
36
|
+
|
37
|
+
def register_custom_strategies(strategies)
|
38
|
+
strategies.each do |strategy|
|
39
|
+
if strategy.respond_to?(:name) && strategy.name.is_a?(String) &&
|
40
|
+
strategy.respond_to?(:enabled?)
|
41
|
+
@custom_strategy_implementations[strategy.name] = strategy
|
42
|
+
else
|
43
|
+
raise "Invalid strategy object. Must have a name method that returns a String and an enabled? method."
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def evaluate_custom_strategies(toggle_name, context)
|
49
|
+
results = {}
|
50
|
+
|
51
|
+
@custom_strategies_definitions[toggle_name]&.each_with_index do |strategy, index|
|
52
|
+
key = "customStrategy#{index + 1}"
|
53
|
+
strategy_impl = @custom_strategy_implementations[strategy["name"]]
|
54
|
+
result = strategy_impl&.enabled?(strategy["parameters"], context) || false
|
55
|
+
results[key] = result
|
56
|
+
end
|
57
|
+
|
58
|
+
results
|
59
|
+
end
|
60
|
+
end
|
Binary file
|
data/lib/yggdrasil_engine.rb
CHANGED
@@ -1,114 +1,114 @@
|
|
1
|
-
require 'ffi'
|
2
|
-
require 'json'
|
3
|
-
require 'custom_strategy'
|
4
|
-
|
5
|
-
TOGGLE_MISSING_RESPONSE = 'NotFound'.freeze
|
6
|
-
ERROR_RESPONSE = 'Error'.freeze
|
7
|
-
OK_RESPONSE = 'Ok'.freeze
|
8
|
-
|
9
|
-
def platform_specific_lib
|
10
|
-
case RbConfig::CONFIG['host_os']
|
11
|
-
when /darwin|mac os/
|
12
|
-
'libyggdrasilffi.dylib'
|
13
|
-
when /linux/
|
14
|
-
'libyggdrasilffi.so'
|
15
|
-
when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
|
16
|
-
'libyggdrasilffi.dll'
|
17
|
-
else
|
18
|
-
raise "unsupported platform #{RbConfig::CONFIG['host_os']}"
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def to_variant(raw_variant)
|
23
|
-
payload = raw_variant[:payload] && raw_variant[:payload].transform_keys(&:to_s)
|
24
|
-
{
|
25
|
-
name: raw_variant[:name],
|
26
|
-
enabled: raw_variant[:enabled],
|
27
|
-
payload: payload,
|
28
|
-
}
|
29
|
-
end
|
30
|
-
|
31
|
-
class YggdrasilEngine
|
32
|
-
extend FFI::Library
|
33
|
-
ffi_lib File.expand_path(platform_specific_lib, __dir__)
|
34
|
-
|
35
|
-
attach_function :new_engine, [], :pointer
|
36
|
-
attach_function :free_engine, [:pointer], :void
|
37
|
-
|
38
|
-
attach_function :take_state, %i[pointer string], :pointer
|
39
|
-
attach_function :check_enabled, %i[pointer string string string], :pointer
|
40
|
-
attach_function :check_variant, %i[pointer string string string], :pointer
|
41
|
-
attach_function :get_metrics, [:pointer], :pointer
|
42
|
-
attach_function :free_response, [:pointer], :void
|
43
|
-
|
44
|
-
attach_function :count_toggle, %i[pointer string bool], :void
|
45
|
-
attach_function :count_variant, %i[pointer string string], :void
|
46
|
-
|
47
|
-
def initialize
|
48
|
-
@engine = YggdrasilEngine.new_engine
|
49
|
-
@custom_strategy_handler = CustomStrategyHandler.new
|
50
|
-
ObjectSpace.define_finalizer(self, self.class.finalize(@engine))
|
51
|
-
end
|
52
|
-
|
53
|
-
def self.finalize(engine)
|
54
|
-
proc { YggdrasilEngine.free_engine(engine) }
|
55
|
-
end
|
56
|
-
|
57
|
-
def take_state(toggles)
|
58
|
-
@custom_strategy_handler.update_strategies(toggles)
|
59
|
-
response_ptr = YggdrasilEngine.take_state(@engine, toggles)
|
60
|
-
take_toggles_response = JSON.parse(response_ptr.read_string, symbolize_names: true)
|
61
|
-
YggdrasilEngine.free_response(response_ptr)
|
62
|
-
end
|
63
|
-
|
64
|
-
def get_variant(name, context)
|
65
|
-
context_json = (context || {}).to_json
|
66
|
-
custom_strategy_results = @custom_strategy_handler.evaluate_custom_strategies(name, context).to_json
|
67
|
-
|
68
|
-
variant_def_json_ptr = YggdrasilEngine.check_variant(@engine, name, context_json, custom_strategy_results)
|
69
|
-
variant_def_json = variant_def_json_ptr.read_string
|
70
|
-
YggdrasilEngine.free_response(variant_def_json_ptr)
|
71
|
-
variant_response = JSON.parse(variant_def_json, symbolize_names: true)
|
72
|
-
|
73
|
-
return nil if variant_response[:status_code] == TOGGLE_MISSING_RESPONSE
|
74
|
-
variant = variant_response[:value]
|
75
|
-
|
76
|
-
return to_variant(variant) if variant_response[:status_code] == OK_RESPONSE
|
77
|
-
end
|
78
|
-
|
79
|
-
def enabled?(toggle_name, context)
|
80
|
-
context_json = (context || {}).to_json
|
81
|
-
custom_strategy_results = @custom_strategy_handler.evaluate_custom_strategies(toggle_name, context).to_json
|
82
|
-
|
83
|
-
response_ptr = YggdrasilEngine.check_enabled(@engine, toggle_name, context_json, custom_strategy_results)
|
84
|
-
response_json = response_ptr.read_string
|
85
|
-
YggdrasilEngine.free_response(response_ptr)
|
86
|
-
response = JSON.parse(response_json, symbolize_names: true)
|
87
|
-
|
88
|
-
raise "Error: #{response[:error_message]}" if response[:status_code] == ERROR_RESPONSE
|
89
|
-
return nil if response[:status_code] == TOGGLE_MISSING_RESPONSE
|
90
|
-
|
91
|
-
return response[:value] == true
|
92
|
-
end
|
93
|
-
|
94
|
-
def count_toggle(toggle_name, enabled)
|
95
|
-
response_ptr = YggdrasilEngine.count_toggle(@engine, toggle_name, enabled)
|
96
|
-
YggdrasilEngine.free_response(response_ptr)
|
97
|
-
end
|
98
|
-
|
99
|
-
def count_variant(toggle_name, variant_name)
|
100
|
-
response_ptr = YggdrasilEngine.count_variant(@engine, toggle_name, variant_name)
|
101
|
-
YggdrasilEngine.free_response(response_ptr)
|
102
|
-
end
|
103
|
-
|
104
|
-
def get_metrics
|
105
|
-
metrics_ptr = YggdrasilEngine.get_metrics(@engine)
|
106
|
-
metrics = JSON.parse(metrics_ptr.read_string, symbolize_names: true)
|
107
|
-
YggdrasilEngine.free_response(metrics_ptr)
|
108
|
-
metrics[:value]
|
109
|
-
end
|
110
|
-
|
111
|
-
def register_custom_strategies(strategies)
|
112
|
-
@custom_strategy_handler.register_custom_strategies(strategies)
|
113
|
-
end
|
114
|
-
end
|
1
|
+
require 'ffi'
|
2
|
+
require 'json'
|
3
|
+
require 'custom_strategy'
|
4
|
+
|
5
|
+
TOGGLE_MISSING_RESPONSE = 'NotFound'.freeze
|
6
|
+
ERROR_RESPONSE = 'Error'.freeze
|
7
|
+
OK_RESPONSE = 'Ok'.freeze
|
8
|
+
|
9
|
+
def platform_specific_lib
|
10
|
+
case RbConfig::CONFIG['host_os']
|
11
|
+
when /darwin|mac os/
|
12
|
+
'libyggdrasilffi.dylib'
|
13
|
+
when /linux/
|
14
|
+
'libyggdrasilffi.so'
|
15
|
+
when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
|
16
|
+
'libyggdrasilffi.dll'
|
17
|
+
else
|
18
|
+
raise "unsupported platform #{RbConfig::CONFIG['host_os']}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_variant(raw_variant)
|
23
|
+
payload = raw_variant[:payload] && raw_variant[:payload].transform_keys(&:to_s)
|
24
|
+
{
|
25
|
+
name: raw_variant[:name],
|
26
|
+
enabled: raw_variant[:enabled],
|
27
|
+
payload: payload,
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
class YggdrasilEngine
|
32
|
+
extend FFI::Library
|
33
|
+
ffi_lib File.expand_path(platform_specific_lib, __dir__)
|
34
|
+
|
35
|
+
attach_function :new_engine, [], :pointer
|
36
|
+
attach_function :free_engine, [:pointer], :void
|
37
|
+
|
38
|
+
attach_function :take_state, %i[pointer string], :pointer
|
39
|
+
attach_function :check_enabled, %i[pointer string string string], :pointer
|
40
|
+
attach_function :check_variant, %i[pointer string string string], :pointer
|
41
|
+
attach_function :get_metrics, [:pointer], :pointer
|
42
|
+
attach_function :free_response, [:pointer], :void
|
43
|
+
|
44
|
+
attach_function :count_toggle, %i[pointer string bool], :void
|
45
|
+
attach_function :count_variant, %i[pointer string string], :void
|
46
|
+
|
47
|
+
def initialize
|
48
|
+
@engine = YggdrasilEngine.new_engine
|
49
|
+
@custom_strategy_handler = CustomStrategyHandler.new
|
50
|
+
ObjectSpace.define_finalizer(self, self.class.finalize(@engine))
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.finalize(engine)
|
54
|
+
proc { YggdrasilEngine.free_engine(engine) }
|
55
|
+
end
|
56
|
+
|
57
|
+
def take_state(toggles)
|
58
|
+
@custom_strategy_handler.update_strategies(toggles)
|
59
|
+
response_ptr = YggdrasilEngine.take_state(@engine, toggles)
|
60
|
+
take_toggles_response = JSON.parse(response_ptr.read_string, symbolize_names: true)
|
61
|
+
YggdrasilEngine.free_response(response_ptr)
|
62
|
+
end
|
63
|
+
|
64
|
+
def get_variant(name, context)
|
65
|
+
context_json = (context || {}).to_json
|
66
|
+
custom_strategy_results = @custom_strategy_handler.evaluate_custom_strategies(name, context).to_json
|
67
|
+
|
68
|
+
variant_def_json_ptr = YggdrasilEngine.check_variant(@engine, name, context_json, custom_strategy_results)
|
69
|
+
variant_def_json = variant_def_json_ptr.read_string
|
70
|
+
YggdrasilEngine.free_response(variant_def_json_ptr)
|
71
|
+
variant_response = JSON.parse(variant_def_json, symbolize_names: true)
|
72
|
+
|
73
|
+
return nil if variant_response[:status_code] == TOGGLE_MISSING_RESPONSE
|
74
|
+
variant = variant_response[:value]
|
75
|
+
|
76
|
+
return to_variant(variant) if variant_response[:status_code] == OK_RESPONSE
|
77
|
+
end
|
78
|
+
|
79
|
+
def enabled?(toggle_name, context)
|
80
|
+
context_json = (context || {}).to_json
|
81
|
+
custom_strategy_results = @custom_strategy_handler.evaluate_custom_strategies(toggle_name, context).to_json
|
82
|
+
|
83
|
+
response_ptr = YggdrasilEngine.check_enabled(@engine, toggle_name, context_json, custom_strategy_results)
|
84
|
+
response_json = response_ptr.read_string
|
85
|
+
YggdrasilEngine.free_response(response_ptr)
|
86
|
+
response = JSON.parse(response_json, symbolize_names: true)
|
87
|
+
|
88
|
+
raise "Error: #{response[:error_message]}" if response[:status_code] == ERROR_RESPONSE
|
89
|
+
return nil if response[:status_code] == TOGGLE_MISSING_RESPONSE
|
90
|
+
|
91
|
+
return response[:value] == true
|
92
|
+
end
|
93
|
+
|
94
|
+
def count_toggle(toggle_name, enabled)
|
95
|
+
response_ptr = YggdrasilEngine.count_toggle(@engine, toggle_name, enabled)
|
96
|
+
YggdrasilEngine.free_response(response_ptr)
|
97
|
+
end
|
98
|
+
|
99
|
+
def count_variant(toggle_name, variant_name)
|
100
|
+
response_ptr = YggdrasilEngine.count_variant(@engine, toggle_name, variant_name)
|
101
|
+
YggdrasilEngine.free_response(response_ptr)
|
102
|
+
end
|
103
|
+
|
104
|
+
def get_metrics
|
105
|
+
metrics_ptr = YggdrasilEngine.get_metrics(@engine)
|
106
|
+
metrics = JSON.parse(metrics_ptr.read_string, symbolize_names: true)
|
107
|
+
YggdrasilEngine.free_response(metrics_ptr)
|
108
|
+
metrics[:value]
|
109
|
+
end
|
110
|
+
|
111
|
+
def register_custom_strategies(strategies)
|
112
|
+
@custom_strategy_handler.register_custom_strategies(strategies)
|
113
|
+
end
|
114
|
+
end
|
@@ -1,136 +1,136 @@
|
|
1
|
-
require_relative '../lib/custom_strategy'
|
2
|
-
|
3
|
-
RSpec.describe "custom strategies" do
|
4
|
-
let(:raw_state) do
|
5
|
-
{
|
6
|
-
"version": 1,
|
7
|
-
"features": [
|
8
|
-
{
|
9
|
-
"name": "Feature.A",
|
10
|
-
"enabled": true,
|
11
|
-
"strategies": [
|
12
|
-
{
|
13
|
-
"name": "default",
|
14
|
-
"parameters": {}
|
15
|
-
},
|
16
|
-
{
|
17
|
-
"name": "custom",
|
18
|
-
"parameters": {
|
19
|
-
"gerkhins": "yes"
|
20
|
-
}
|
21
|
-
},
|
22
|
-
{
|
23
|
-
"name": "some-other-custom",
|
24
|
-
"parameters": {
|
25
|
-
"gerkhins": "yes"
|
26
|
-
}
|
27
|
-
},
|
28
|
-
]
|
29
|
-
}
|
30
|
-
]
|
31
|
-
}
|
32
|
-
end
|
33
|
-
|
34
|
-
let(:handler) { CustomStrategyHandler.new }
|
35
|
-
|
36
|
-
before do
|
37
|
-
handler.update_strategies(raw_state.to_json)
|
38
|
-
end
|
39
|
-
|
40
|
-
describe 'computing custom strategies' do
|
41
|
-
it 'respects the logic contained in the enabled function' do
|
42
|
-
class TestStrategy
|
43
|
-
attr_reader :name
|
44
|
-
|
45
|
-
def initialize(name)
|
46
|
-
@name = name
|
47
|
-
end
|
48
|
-
|
49
|
-
def enabled?(params, context)
|
50
|
-
params["gerkhins"] == "yes"
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
handler.register_custom_strategies([TestStrategy.new("custom")])
|
55
|
-
strategy_results = handler.evaluate_custom_strategies("Feature.A", {})
|
56
|
-
expect(strategy_results.length).to eq(2)
|
57
|
-
expect(strategy_results["customStrategy1"]).to eq(true)
|
58
|
-
expect(strategy_results["customStrategy2"]).to eq(false)
|
59
|
-
end
|
60
|
-
|
61
|
-
it 'creates a strategy result for every custom strategy thats implemented and defined' do
|
62
|
-
class TestStrategy
|
63
|
-
attr_reader :name
|
64
|
-
|
65
|
-
def initialize(name)
|
66
|
-
@name = name
|
67
|
-
end
|
68
|
-
|
69
|
-
def enabled?(params, context)
|
70
|
-
params["gerkhins"] == "yes"
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
handler.register_custom_strategies([TestStrategy.new("custom"), TestStrategy.new("some-other-custom")])
|
75
|
-
strategy_results = handler.evaluate_custom_strategies("Feature.A", {})
|
76
|
-
expect(strategy_results.length).to eq(2)
|
77
|
-
expect(strategy_results["customStrategy1"]).to eq(true)
|
78
|
-
expect(strategy_results["customStrategy2"]).to eq(true)
|
79
|
-
end
|
80
|
-
|
81
|
-
it 'returns false for missing implementations' do
|
82
|
-
handler.register_custom_strategies([])
|
83
|
-
strategy_results = handler.evaluate_custom_strategies("Feature.A", {})
|
84
|
-
expect(strategy_results.length).to eq(2)
|
85
|
-
expect(strategy_results["customStrategy1"]).to eq(false)
|
86
|
-
end
|
87
|
-
|
88
|
-
it "should calculate custom strategies e2e" do
|
89
|
-
class TestStrategy
|
90
|
-
attr_reader :name
|
91
|
-
|
92
|
-
def initialize(name)
|
93
|
-
@name = name
|
94
|
-
end
|
95
|
-
|
96
|
-
def enabled?(params, context)
|
97
|
-
context[:userId] == "123"
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
state = {
|
102
|
-
"version": 1,
|
103
|
-
"features": [
|
104
|
-
{
|
105
|
-
"name": "Feature.A",
|
106
|
-
"enabled": true,
|
107
|
-
"strategies": [
|
108
|
-
{
|
109
|
-
"name": "custom",
|
110
|
-
"parameters": {
|
111
|
-
"gerkhins": "yes"
|
112
|
-
}
|
113
|
-
}
|
114
|
-
]
|
115
|
-
}
|
116
|
-
]
|
117
|
-
}
|
118
|
-
|
119
|
-
engine = YggdrasilEngine.new
|
120
|
-
engine.register_custom_strategies([TestStrategy.new("custom")])
|
121
|
-
|
122
|
-
engine.take_state(state.to_json)
|
123
|
-
|
124
|
-
should_be_enabled = engine.enabled?("Feature.A", {
|
125
|
-
userId: "123"
|
126
|
-
})
|
127
|
-
|
128
|
-
should_not_be_enabled = engine.enabled?("Feature.A", {
|
129
|
-
userId: "456"
|
130
|
-
})
|
131
|
-
|
132
|
-
expect(should_be_enabled).to eq(true)
|
133
|
-
expect(should_not_be_enabled).to eq(false)
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|
1
|
+
require_relative '../lib/custom_strategy'
|
2
|
+
|
3
|
+
RSpec.describe "custom strategies" do
|
4
|
+
let(:raw_state) do
|
5
|
+
{
|
6
|
+
"version": 1,
|
7
|
+
"features": [
|
8
|
+
{
|
9
|
+
"name": "Feature.A",
|
10
|
+
"enabled": true,
|
11
|
+
"strategies": [
|
12
|
+
{
|
13
|
+
"name": "default",
|
14
|
+
"parameters": {}
|
15
|
+
},
|
16
|
+
{
|
17
|
+
"name": "custom",
|
18
|
+
"parameters": {
|
19
|
+
"gerkhins": "yes"
|
20
|
+
}
|
21
|
+
},
|
22
|
+
{
|
23
|
+
"name": "some-other-custom",
|
24
|
+
"parameters": {
|
25
|
+
"gerkhins": "yes"
|
26
|
+
}
|
27
|
+
},
|
28
|
+
]
|
29
|
+
}
|
30
|
+
]
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
let(:handler) { CustomStrategyHandler.new }
|
35
|
+
|
36
|
+
before do
|
37
|
+
handler.update_strategies(raw_state.to_json)
|
38
|
+
end
|
39
|
+
|
40
|
+
describe 'computing custom strategies' do
|
41
|
+
it 'respects the logic contained in the enabled function' do
|
42
|
+
class TestStrategy
|
43
|
+
attr_reader :name
|
44
|
+
|
45
|
+
def initialize(name)
|
46
|
+
@name = name
|
47
|
+
end
|
48
|
+
|
49
|
+
def enabled?(params, context)
|
50
|
+
params["gerkhins"] == "yes"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
handler.register_custom_strategies([TestStrategy.new("custom")])
|
55
|
+
strategy_results = handler.evaluate_custom_strategies("Feature.A", {})
|
56
|
+
expect(strategy_results.length).to eq(2)
|
57
|
+
expect(strategy_results["customStrategy1"]).to eq(true)
|
58
|
+
expect(strategy_results["customStrategy2"]).to eq(false)
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'creates a strategy result for every custom strategy thats implemented and defined' do
|
62
|
+
class TestStrategy
|
63
|
+
attr_reader :name
|
64
|
+
|
65
|
+
def initialize(name)
|
66
|
+
@name = name
|
67
|
+
end
|
68
|
+
|
69
|
+
def enabled?(params, context)
|
70
|
+
params["gerkhins"] == "yes"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
handler.register_custom_strategies([TestStrategy.new("custom"), TestStrategy.new("some-other-custom")])
|
75
|
+
strategy_results = handler.evaluate_custom_strategies("Feature.A", {})
|
76
|
+
expect(strategy_results.length).to eq(2)
|
77
|
+
expect(strategy_results["customStrategy1"]).to eq(true)
|
78
|
+
expect(strategy_results["customStrategy2"]).to eq(true)
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'returns false for missing implementations' do
|
82
|
+
handler.register_custom_strategies([])
|
83
|
+
strategy_results = handler.evaluate_custom_strategies("Feature.A", {})
|
84
|
+
expect(strategy_results.length).to eq(2)
|
85
|
+
expect(strategy_results["customStrategy1"]).to eq(false)
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should calculate custom strategies e2e" do
|
89
|
+
class TestStrategy
|
90
|
+
attr_reader :name
|
91
|
+
|
92
|
+
def initialize(name)
|
93
|
+
@name = name
|
94
|
+
end
|
95
|
+
|
96
|
+
def enabled?(params, context)
|
97
|
+
context[:userId] == "123"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
state = {
|
102
|
+
"version": 1,
|
103
|
+
"features": [
|
104
|
+
{
|
105
|
+
"name": "Feature.A",
|
106
|
+
"enabled": true,
|
107
|
+
"strategies": [
|
108
|
+
{
|
109
|
+
"name": "custom",
|
110
|
+
"parameters": {
|
111
|
+
"gerkhins": "yes"
|
112
|
+
}
|
113
|
+
}
|
114
|
+
]
|
115
|
+
}
|
116
|
+
]
|
117
|
+
}
|
118
|
+
|
119
|
+
engine = YggdrasilEngine.new
|
120
|
+
engine.register_custom_strategies([TestStrategy.new("custom")])
|
121
|
+
|
122
|
+
engine.take_state(state.to_json)
|
123
|
+
|
124
|
+
should_be_enabled = engine.enabled?("Feature.A", {
|
125
|
+
userId: "123"
|
126
|
+
})
|
127
|
+
|
128
|
+
should_not_be_enabled = engine.enabled?("Feature.A", {
|
129
|
+
userId: "456"
|
130
|
+
})
|
131
|
+
|
132
|
+
expect(should_be_enabled).to eq(true)
|
133
|
+
expect(should_not_be_enabled).to eq(false)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -1,139 +1,139 @@
|
|
1
|
-
require 'rspec'
|
2
|
-
require 'json'
|
3
|
-
require_relative '../lib/yggdrasil_engine'
|
4
|
-
|
5
|
-
index_file_path = '../client-specification/specifications/index.json'
|
6
|
-
test_suites = JSON.parse(File.read(index_file_path))
|
7
|
-
|
8
|
-
RSpec.describe YggdrasilEngine do
|
9
|
-
let(:yggdrasil_engine) { YggdrasilEngine.new }
|
10
|
-
|
11
|
-
describe '#checking a toggle' do
|
12
|
-
it 'that does not exist should yield a not found' do
|
13
|
-
is_enabled = yggdrasil_engine.enabled?('missing-toggle', {})
|
14
|
-
expect(is_enabled).to be_nil
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
describe '#metrics' do
|
19
|
-
it 'should clear metrics when get_metrics is called' do
|
20
|
-
feature_name = 'Feature.A'
|
21
|
-
|
22
|
-
suite_path = File.join('../client-specification/specifications', '01-simple-examples.json')
|
23
|
-
suite_data = JSON.parse(File.read(suite_path))
|
24
|
-
|
25
|
-
yggdrasil_engine.take_state(suite_data['state'].to_json)
|
26
|
-
|
27
|
-
yggdrasil_engine.count_toggle(feature_name, true)
|
28
|
-
yggdrasil_engine.count_toggle(feature_name, false)
|
29
|
-
|
30
|
-
metrics = yggdrasil_engine.get_metrics() # This should clear the metrics buffer
|
31
|
-
|
32
|
-
metric = metrics[:toggles][feature_name.to_sym]
|
33
|
-
expect(metric[:yes]).to eq(1)
|
34
|
-
expect(metric[:no]).to eq(1)
|
35
|
-
|
36
|
-
metrics = yggdrasil_engine.get_metrics()
|
37
|
-
expect(metrics).to be_nil
|
38
|
-
end
|
39
|
-
|
40
|
-
it 'should increment toggle count when it exists' do
|
41
|
-
toggle_name = 'Feature.A'
|
42
|
-
|
43
|
-
suite_path = File.join('../client-specification/specifications', '01-simple-examples.json')
|
44
|
-
suite_data = JSON.parse(File.read(suite_path))
|
45
|
-
|
46
|
-
yggdrasil_engine.take_state(suite_data['state'].to_json)
|
47
|
-
|
48
|
-
yggdrasil_engine.count_toggle(toggle_name, true)
|
49
|
-
yggdrasil_engine.count_toggle(toggle_name, false)
|
50
|
-
|
51
|
-
metrics = yggdrasil_engine.get_metrics()
|
52
|
-
metric = metrics[:toggles][toggle_name.to_sym]
|
53
|
-
|
54
|
-
expect(metric[:yes]).to eq(1)
|
55
|
-
expect(metric[:no]).to eq(1)
|
56
|
-
end
|
57
|
-
|
58
|
-
it 'should increment toggle count when the toggle does not exist' do
|
59
|
-
toggle_name = 'Feature.X'
|
60
|
-
|
61
|
-
yggdrasil_engine.count_toggle(toggle_name, true)
|
62
|
-
yggdrasil_engine.count_toggle(toggle_name, false)
|
63
|
-
|
64
|
-
metrics = yggdrasil_engine.get_metrics()
|
65
|
-
metric = metrics[:toggles][toggle_name.to_sym]
|
66
|
-
|
67
|
-
expect(metric[:yes]).to eq(1)
|
68
|
-
expect(metric[:no]).to eq(1)
|
69
|
-
end
|
70
|
-
|
71
|
-
it 'should increment variant' do
|
72
|
-
toggle_name = 'Feature.Q'
|
73
|
-
|
74
|
-
suite_path = File.join('../client-specification/specifications', '01-simple-examples.json')
|
75
|
-
suite_data = JSON.parse(File.read(suite_path))
|
76
|
-
|
77
|
-
yggdrasil_engine.take_state(suite_data['state'].to_json)
|
78
|
-
|
79
|
-
yggdrasil_engine.count_variant(toggle_name, 'disabled')
|
80
|
-
|
81
|
-
metrics = yggdrasil_engine.get_metrics()
|
82
|
-
metric = metrics[:toggles][toggle_name.to_sym]
|
83
|
-
|
84
|
-
expect(metric[:variants][:disabled]).to eq(1)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
RSpec.describe 'Client Specification' do
|
90
|
-
let(:yggdrasil_engine) { YggdrasilEngine.new }
|
91
|
-
|
92
|
-
test_suites.each do |suite|
|
93
|
-
suite_path = File.join('../client-specification/specifications', suite)
|
94
|
-
suite_data = JSON.parse(File.read(suite_path), symbolize_names: true)
|
95
|
-
|
96
|
-
describe "Suite '#{suite}'" do
|
97
|
-
before(:each) do
|
98
|
-
yggdrasil_engine.take_state(suite_data[:state].to_json)
|
99
|
-
end
|
100
|
-
|
101
|
-
suite_data.fetch(:tests, []).each do |test|
|
102
|
-
describe "Test '#{test[:description]}'" do
|
103
|
-
let(:context) { test[:context] }
|
104
|
-
let(:toggle_name) { test[:toggleName] }
|
105
|
-
let(:expected_result) { test[:expectedResult] }
|
106
|
-
|
107
|
-
it 'returns correct result for `is_enabled?` method' do
|
108
|
-
result = yggdrasil_engine.enabled?(toggle_name, context) || false
|
109
|
-
|
110
|
-
expect(result).to eq(expected_result),
|
111
|
-
"Failed test '#{test['description']}': expected #{expected_result}, got #{result}"
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
suite_data.fetch(:variantTests, []).each do |test|
|
117
|
-
next unless test[:expectedResult]
|
118
|
-
|
119
|
-
describe "Variant Test '#{test[:description]}'" do
|
120
|
-
let(:context) { test[:context] }
|
121
|
-
let(:toggle_name) { test[:toggleName] }
|
122
|
-
let(:expected_result) { to_variant(test[:expectedResult]) }
|
123
|
-
|
124
|
-
it 'returns correct result for `get_variant` method' do
|
125
|
-
result = yggdrasil_engine.get_variant(toggle_name, context) || {
|
126
|
-
:name => 'disabled',
|
127
|
-
:payload => nil,
|
128
|
-
:enabled => false
|
129
|
-
}
|
130
|
-
|
131
|
-
expect(result[:name]).to eq(expected_result[:name])
|
132
|
-
expect(result[:payload]).to eq(expected_result[:payload])
|
133
|
-
expect(result[:enabled]).to eq(expected_result[:enabled])
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|
137
|
-
end
|
138
|
-
end
|
139
|
-
end
|
1
|
+
require 'rspec'
|
2
|
+
require 'json'
|
3
|
+
require_relative '../lib/yggdrasil_engine'
|
4
|
+
|
5
|
+
index_file_path = '../client-specification/specifications/index.json'
|
6
|
+
test_suites = JSON.parse(File.read(index_file_path))
|
7
|
+
|
8
|
+
RSpec.describe YggdrasilEngine do
|
9
|
+
let(:yggdrasil_engine) { YggdrasilEngine.new }
|
10
|
+
|
11
|
+
describe '#checking a toggle' do
|
12
|
+
it 'that does not exist should yield a not found' do
|
13
|
+
is_enabled = yggdrasil_engine.enabled?('missing-toggle', {})
|
14
|
+
expect(is_enabled).to be_nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#metrics' do
|
19
|
+
it 'should clear metrics when get_metrics is called' do
|
20
|
+
feature_name = 'Feature.A'
|
21
|
+
|
22
|
+
suite_path = File.join('../client-specification/specifications', '01-simple-examples.json')
|
23
|
+
suite_data = JSON.parse(File.read(suite_path))
|
24
|
+
|
25
|
+
yggdrasil_engine.take_state(suite_data['state'].to_json)
|
26
|
+
|
27
|
+
yggdrasil_engine.count_toggle(feature_name, true)
|
28
|
+
yggdrasil_engine.count_toggle(feature_name, false)
|
29
|
+
|
30
|
+
metrics = yggdrasil_engine.get_metrics() # This should clear the metrics buffer
|
31
|
+
|
32
|
+
metric = metrics[:toggles][feature_name.to_sym]
|
33
|
+
expect(metric[:yes]).to eq(1)
|
34
|
+
expect(metric[:no]).to eq(1)
|
35
|
+
|
36
|
+
metrics = yggdrasil_engine.get_metrics()
|
37
|
+
expect(metrics).to be_nil
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should increment toggle count when it exists' do
|
41
|
+
toggle_name = 'Feature.A'
|
42
|
+
|
43
|
+
suite_path = File.join('../client-specification/specifications', '01-simple-examples.json')
|
44
|
+
suite_data = JSON.parse(File.read(suite_path))
|
45
|
+
|
46
|
+
yggdrasil_engine.take_state(suite_data['state'].to_json)
|
47
|
+
|
48
|
+
yggdrasil_engine.count_toggle(toggle_name, true)
|
49
|
+
yggdrasil_engine.count_toggle(toggle_name, false)
|
50
|
+
|
51
|
+
metrics = yggdrasil_engine.get_metrics()
|
52
|
+
metric = metrics[:toggles][toggle_name.to_sym]
|
53
|
+
|
54
|
+
expect(metric[:yes]).to eq(1)
|
55
|
+
expect(metric[:no]).to eq(1)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should increment toggle count when the toggle does not exist' do
|
59
|
+
toggle_name = 'Feature.X'
|
60
|
+
|
61
|
+
yggdrasil_engine.count_toggle(toggle_name, true)
|
62
|
+
yggdrasil_engine.count_toggle(toggle_name, false)
|
63
|
+
|
64
|
+
metrics = yggdrasil_engine.get_metrics()
|
65
|
+
metric = metrics[:toggles][toggle_name.to_sym]
|
66
|
+
|
67
|
+
expect(metric[:yes]).to eq(1)
|
68
|
+
expect(metric[:no]).to eq(1)
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should increment variant' do
|
72
|
+
toggle_name = 'Feature.Q'
|
73
|
+
|
74
|
+
suite_path = File.join('../client-specification/specifications', '01-simple-examples.json')
|
75
|
+
suite_data = JSON.parse(File.read(suite_path))
|
76
|
+
|
77
|
+
yggdrasil_engine.take_state(suite_data['state'].to_json)
|
78
|
+
|
79
|
+
yggdrasil_engine.count_variant(toggle_name, 'disabled')
|
80
|
+
|
81
|
+
metrics = yggdrasil_engine.get_metrics()
|
82
|
+
metric = metrics[:toggles][toggle_name.to_sym]
|
83
|
+
|
84
|
+
expect(metric[:variants][:disabled]).to eq(1)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
RSpec.describe 'Client Specification' do
|
90
|
+
let(:yggdrasil_engine) { YggdrasilEngine.new }
|
91
|
+
|
92
|
+
test_suites.each do |suite|
|
93
|
+
suite_path = File.join('../client-specification/specifications', suite)
|
94
|
+
suite_data = JSON.parse(File.read(suite_path), symbolize_names: true)
|
95
|
+
|
96
|
+
describe "Suite '#{suite}'" do
|
97
|
+
before(:each) do
|
98
|
+
yggdrasil_engine.take_state(suite_data[:state].to_json)
|
99
|
+
end
|
100
|
+
|
101
|
+
suite_data.fetch(:tests, []).each do |test|
|
102
|
+
describe "Test '#{test[:description]}'" do
|
103
|
+
let(:context) { test[:context] }
|
104
|
+
let(:toggle_name) { test[:toggleName] }
|
105
|
+
let(:expected_result) { test[:expectedResult] }
|
106
|
+
|
107
|
+
it 'returns correct result for `is_enabled?` method' do
|
108
|
+
result = yggdrasil_engine.enabled?(toggle_name, context) || false
|
109
|
+
|
110
|
+
expect(result).to eq(expected_result),
|
111
|
+
"Failed test '#{test['description']}': expected #{expected_result}, got #{result}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
suite_data.fetch(:variantTests, []).each do |test|
|
117
|
+
next unless test[:expectedResult]
|
118
|
+
|
119
|
+
describe "Variant Test '#{test[:description]}'" do
|
120
|
+
let(:context) { test[:context] }
|
121
|
+
let(:toggle_name) { test[:toggleName] }
|
122
|
+
let(:expected_result) { to_variant(test[:expectedResult]) }
|
123
|
+
|
124
|
+
it 'returns correct result for `get_variant` method' do
|
125
|
+
result = yggdrasil_engine.get_variant(toggle_name, context) || {
|
126
|
+
:name => 'disabled',
|
127
|
+
:payload => nil,
|
128
|
+
:enabled => false
|
129
|
+
}
|
130
|
+
|
131
|
+
expect(result[:name]).to eq(expected_result[:name])
|
132
|
+
expect(result[:payload]).to eq(expected_result[:payload])
|
133
|
+
expect(result[:enabled]).to eq(expected_result[:enabled])
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
metadata
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yggdrasil-engine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3.beta.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Unleash
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
date: 2023-06-28 00:00:00.000000000 Z
|
@@ -24,20 +24,6 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 1.15.5
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: fiddle
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: 1.0.6
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: 1.0.6
|
41
27
|
description: "..."
|
42
28
|
email: liquidwicked64@gmail.com
|
43
29
|
executables: []
|
@@ -46,7 +32,7 @@ extra_rdoc_files: []
|
|
46
32
|
files:
|
47
33
|
- README.md
|
48
34
|
- lib/custom_strategy.rb
|
49
|
-
- lib/
|
35
|
+
- lib/x86_64-pc-windows-gnu/yggdrasilffi.dll
|
50
36
|
- lib/yggdrasil_engine.rb
|
51
37
|
- spec/custom_strategy_spec.rb
|
52
38
|
- spec/yggdrasil_engine_spec.rb
|
@@ -54,7 +40,7 @@ homepage: http://github.com/username/my_gem
|
|
54
40
|
licenses:
|
55
41
|
- MIT
|
56
42
|
metadata: {}
|
57
|
-
post_install_message:
|
43
|
+
post_install_message:
|
58
44
|
rdoc_options: []
|
59
45
|
require_paths:
|
60
46
|
- lib
|
@@ -65,12 +51,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
65
51
|
version: '0'
|
66
52
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
53
|
requirements:
|
68
|
-
- - "
|
54
|
+
- - ">"
|
69
55
|
- !ruby/object:Gem::Version
|
70
|
-
version:
|
56
|
+
version: 1.3.1
|
71
57
|
requirements: []
|
72
58
|
rubygems_version: 3.2.33
|
73
|
-
signing_key:
|
59
|
+
signing_key:
|
74
60
|
specification_version: 4
|
75
61
|
summary: Unleash engine for evaluating feature toggles
|
76
62
|
test_files: []
|
data/lib/libyggdrasilffi.so
DELETED
Binary file
|