oso-oso 0.14.0 → 0.20.0.pre.beta
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Gemfile.lock +24 -2
- data/ext/oso-oso/lib/libpolar.dylib +0 -0
- data/ext/oso-oso/lib/libpolar.so +0 -0
- data/ext/oso-oso/lib/polar.dll +0 -0
- data/lib/oso/polar/data_filtering.rb +155 -0
- data/lib/oso/polar/ffi/error.rb +8 -1
- data/lib/oso/polar/ffi/message.rb +2 -1
- data/lib/oso/polar/ffi/polar.rb +32 -10
- data/lib/oso/polar/ffi/query.rb +19 -7
- data/lib/oso/polar/host.rb +94 -21
- data/lib/oso/polar/polar.rb +64 -9
- data/lib/oso/polar/query.rb +86 -8
- data/lib/oso/polar.rb +1 -0
- data/lib/oso/version.rb +1 -1
- data/oso-oso.gemspec +2 -0
- metadata +33 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aa0564b86f2ceb4b54a62cdbae1cf2a3cc14ee91
|
4
|
+
data.tar.gz: d0cbc579ac5bd7284b559d3ca3d31af9f6837e96
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d2a7f469cff84febf8dd66f737384523fd576d42b1d44e41247bb738a7b26ca230d9e91066be2acf2a6ade07be780aeec5fa529b21c88c7d0bb626ca66ab821a
|
7
|
+
data.tar.gz: 742452956eeb29797b173888a268962bbc6e92e88b7148e7ca3952bb9d9c7687abd55d74c657d170056a61e911ed0183d29fa3ef471aa5c8cd733f47d72d188b
|
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,24 +1,40 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
oso-oso (0.
|
4
|
+
oso-oso (0.20.0.pre.beta)
|
5
5
|
ffi (~> 1.0)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
|
+
activemodel (5.2.6)
|
11
|
+
activesupport (= 5.2.6)
|
12
|
+
activerecord (5.2.6)
|
13
|
+
activemodel (= 5.2.6)
|
14
|
+
activesupport (= 5.2.6)
|
15
|
+
arel (>= 9.0)
|
16
|
+
activesupport (5.2.6)
|
17
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
18
|
+
i18n (>= 0.7, < 2)
|
19
|
+
minitest (~> 5.1)
|
20
|
+
tzinfo (~> 1.1)
|
21
|
+
arel (9.0.0)
|
10
22
|
ast (2.4.1)
|
11
23
|
backport (1.1.2)
|
12
24
|
benchmark (0.1.0)
|
13
25
|
byebug (11.1.3)
|
14
26
|
coderay (1.1.3)
|
27
|
+
concurrent-ruby (1.1.9)
|
15
28
|
diff-lcs (1.4.4)
|
16
29
|
e2mmap (0.1.0)
|
17
|
-
ffi (1.15.
|
30
|
+
ffi (1.15.3)
|
31
|
+
i18n (1.8.10)
|
32
|
+
concurrent-ruby (~> 1.0)
|
18
33
|
jaro_winkler (1.5.4)
|
19
34
|
maruku (0.7.3)
|
20
35
|
method_source (1.0.0)
|
21
36
|
mini_portile2 (2.4.0)
|
37
|
+
minitest (5.14.4)
|
22
38
|
nokogiri (1.10.10)
|
23
39
|
mini_portile2 (~> 2.4.0)
|
24
40
|
parallel (1.19.2)
|
@@ -75,8 +91,12 @@ GEM
|
|
75
91
|
thor (~> 1.0)
|
76
92
|
tilt (~> 2.0)
|
77
93
|
yard (~> 0.9, >= 0.9.24)
|
94
|
+
sqlite3 (1.4.2)
|
78
95
|
thor (1.0.1)
|
96
|
+
thread_safe (0.3.6)
|
79
97
|
tilt (2.0.10)
|
98
|
+
tzinfo (1.2.9)
|
99
|
+
thread_safe (~> 0.1)
|
80
100
|
unicode-display_width (1.7.0)
|
81
101
|
yard (0.9.25)
|
82
102
|
|
@@ -84,12 +104,14 @@ PLATFORMS
|
|
84
104
|
ruby
|
85
105
|
|
86
106
|
DEPENDENCIES
|
107
|
+
activerecord
|
87
108
|
oso-oso!
|
88
109
|
pry-byebug (~> 3.9.0)
|
89
110
|
rake (~> 12.0)
|
90
111
|
rspec (~> 3.0)
|
91
112
|
rubocop (~> 0.89.1)
|
92
113
|
solargraph (~> 0.39.14)
|
114
|
+
sqlite3
|
93
115
|
yard (~> 0.9.25)
|
94
116
|
|
95
117
|
BUNDLED WITH
|
Binary file
|
data/ext/oso-oso/lib/libpolar.so
CHANGED
Binary file
|
data/ext/oso-oso/lib/polar.dll
CHANGED
Binary file
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Oso
|
4
|
+
module Polar
|
5
|
+
# Data filtering interface for Ruby
|
6
|
+
module DataFiltering
|
7
|
+
# Represents a set of filter sequences that should allow the host
|
8
|
+
# to obtain the records satisfying a query.
|
9
|
+
class FilterPlan
|
10
|
+
include Enumerable
|
11
|
+
attr_reader :result_sets
|
12
|
+
|
13
|
+
def initialize(polar, partials, class_name)
|
14
|
+
types = polar.host.serialize_types
|
15
|
+
parsed_json = polar.ffi.build_filter_plan(types, partials, 'resource', class_name)
|
16
|
+
@polar = polar
|
17
|
+
@result_sets = parsed_json['result_sets'].map do |rset|
|
18
|
+
ResultSet.new polar, rset
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def each(&blk)
|
23
|
+
result_sets.each(&blk)
|
24
|
+
end
|
25
|
+
|
26
|
+
def resolve # rubocop:disable Metrics/AbcSize
|
27
|
+
reduce([]) do |acc, rs|
|
28
|
+
requests = rs.requests
|
29
|
+
acc + rs.resolve_order.each_with_object({}) do |i, set_results|
|
30
|
+
req = requests[i]
|
31
|
+
constraints = req.constraints
|
32
|
+
constraints.each { |c| c.ground set_results }
|
33
|
+
set_results[i] = @polar.host.types[req.class_tag].fetcher[constraints]
|
34
|
+
end[rs.result_id]
|
35
|
+
end.uniq
|
36
|
+
end
|
37
|
+
|
38
|
+
# Represents a sequence of filters for one set of results
|
39
|
+
class ResultSet
|
40
|
+
attr_reader :requests, :resolve_order, :result_id
|
41
|
+
|
42
|
+
def initialize(polar, parsed_json)
|
43
|
+
@resolve_order = parsed_json['resolve_order']
|
44
|
+
@result_id = parsed_json['result_id']
|
45
|
+
@requests = parsed_json['requests'].each_with_object({}) do |req, reqs|
|
46
|
+
reqs[req[0].to_i] = Request.new(polar, req[1])
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Represents a filter for a result set
|
51
|
+
class Request
|
52
|
+
attr_reader :constraints, :class_tag
|
53
|
+
|
54
|
+
def initialize(polar, parsed_json)
|
55
|
+
@constraints = parsed_json['constraints'].map do |con|
|
56
|
+
Constraint.parse polar, con
|
57
|
+
end
|
58
|
+
@class_tag = parsed_json['class_tag']
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Represents relationships between resources, eg. parent/child
|
65
|
+
class Relationship
|
66
|
+
attr_reader :kind, :other_type, :my_field, :other_field
|
67
|
+
|
68
|
+
def initialize(kind:, other_type:, my_field:, other_field:)
|
69
|
+
@kind = kind
|
70
|
+
@other_type = other_type
|
71
|
+
@my_field = my_field
|
72
|
+
@other_field = other_field
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Represents field-field relationships on one resource.
|
77
|
+
class Field
|
78
|
+
attr_reader :field
|
79
|
+
|
80
|
+
def initialize(field:)
|
81
|
+
@field = field
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Represents field-field relationships on different resources.
|
86
|
+
class Ref
|
87
|
+
attr_reader :field, :result_id
|
88
|
+
|
89
|
+
def initialize(field:, result_id:)
|
90
|
+
@field = field
|
91
|
+
@result_id = result_id
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Represents a condition that must hold on a resource.
|
96
|
+
class Constraint
|
97
|
+
attr_reader :kind, :field, :value
|
98
|
+
|
99
|
+
CHECKS = {
|
100
|
+
'Eq' => ->(a, b) { a == b },
|
101
|
+
'In' => ->(a, b) { b.include? a },
|
102
|
+
'Contains' => ->(a, b) { a.include? b }
|
103
|
+
}.freeze
|
104
|
+
|
105
|
+
def initialize(kind:, field:, value:)
|
106
|
+
@kind = kind
|
107
|
+
@field = field
|
108
|
+
@value = value
|
109
|
+
@check = CHECKS[kind]
|
110
|
+
raise "Unknown constraint kind `#{kind}`" if @check.nil?
|
111
|
+
end
|
112
|
+
|
113
|
+
def ground(results)
|
114
|
+
return unless value.is_a? Ref
|
115
|
+
|
116
|
+
ref = value
|
117
|
+
@value = results[ref.result_id]
|
118
|
+
@value = value.map { |v| v.send ref.field } unless ref.field.nil?
|
119
|
+
end
|
120
|
+
|
121
|
+
def check(item)
|
122
|
+
val = value.is_a?(Field) ? item.send(value.field) : value
|
123
|
+
item = field.nil? ? item : item.send(field)
|
124
|
+
@check[item, val]
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.parse(polar, constraint) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
128
|
+
kind = constraint['kind']
|
129
|
+
raise unless %w[Eq In Contains].include? kind
|
130
|
+
|
131
|
+
field = constraint['field']
|
132
|
+
value = constraint['value']
|
133
|
+
|
134
|
+
value_kind = value.keys.first
|
135
|
+
value = value[value_kind]
|
136
|
+
|
137
|
+
case value_kind
|
138
|
+
when 'Term'
|
139
|
+
value = polar.host.to_ruby value
|
140
|
+
when 'Ref'
|
141
|
+
child_field = value['field']
|
142
|
+
result_id = value['result_id']
|
143
|
+
value = Ref.new field: child_field, result_id: result_id
|
144
|
+
when 'Field'
|
145
|
+
value = Field.new field: value
|
146
|
+
else
|
147
|
+
raise "Unknown value kind `#{value_kind}`"
|
148
|
+
end
|
149
|
+
|
150
|
+
new kind: kind, field: field, value: value
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
data/lib/oso/polar/ffi/error.rb
CHANGED
@@ -24,7 +24,7 @@ module Oso
|
|
24
24
|
#
|
25
25
|
# @return [::Oso::Polar::Error] if there's an FFI error.
|
26
26
|
# @return [::Oso::Polar::FFIErrorNotFound] if there isn't one.
|
27
|
-
def self.get # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
27
|
+
def self.get(enrich_message) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
28
28
|
error = Rust.get
|
29
29
|
return ::Oso::Polar::FFIErrorNotFound if error.null?
|
30
30
|
|
@@ -40,6 +40,13 @@ module Oso
|
|
40
40
|
subkind, details = nil
|
41
41
|
end
|
42
42
|
|
43
|
+
# Enrich error message and stack trace
|
44
|
+
msg = enrich_message.call(msg) if msg
|
45
|
+
if details
|
46
|
+
details['stack_trace'] = enrich_message.call(details['stack_trace']) if details['stack_trace']
|
47
|
+
details['msg'] = enrich_message.call(details['msg']) if details['msg']
|
48
|
+
end
|
49
|
+
|
43
50
|
case kind
|
44
51
|
when 'Parse'
|
45
52
|
parse_error(subkind, msg: msg, details: details)
|
@@ -19,10 +19,11 @@ module Oso
|
|
19
19
|
attach_function :free, :string_free, [Message], :int32
|
20
20
|
end
|
21
21
|
|
22
|
-
def process
|
22
|
+
def process(enrich_message)
|
23
23
|
message = JSON.parse(to_s)
|
24
24
|
kind = message['kind']
|
25
25
|
msg = message['msg']
|
26
|
+
msg = enrich_message.call(msg)
|
26
27
|
|
27
28
|
case kind
|
28
29
|
when 'Print'
|
data/lib/oso/polar/ffi/polar.rb
CHANGED
@@ -7,6 +7,8 @@ module Oso
|
|
7
7
|
module FFI
|
8
8
|
# Wrapper class for Polar FFI pointer + operations.
|
9
9
|
class Polar < ::FFI::AutoPointer
|
10
|
+
attr_accessor :enrich_message
|
11
|
+
|
10
12
|
Rust = Module.new do
|
11
13
|
extend ::FFI::Library
|
12
14
|
ffi_lib FFI::LIB_PATH
|
@@ -23,6 +25,12 @@ module Oso
|
|
23
25
|
attach_function :register_constant, :polar_register_constant, [FFI::Polar, :string, :string], :int32
|
24
26
|
attach_function :next_message, :polar_next_polar_message, [FFI::Polar], FFI::Message
|
25
27
|
attach_function :free, :polar_free, [FFI::Polar], :int32
|
28
|
+
attach_function(
|
29
|
+
:build_filter_plan,
|
30
|
+
:polar_build_filter_plan,
|
31
|
+
[FFI::Polar, :string, :string, :string, :string],
|
32
|
+
:string
|
33
|
+
)
|
26
34
|
end
|
27
35
|
private_constant :Rust
|
28
36
|
|
@@ -30,7 +38,7 @@ module Oso
|
|
30
38
|
# @raise [FFI::Error] if the FFI call returns an error.
|
31
39
|
def self.create
|
32
40
|
polar = Rust.new
|
33
|
-
|
41
|
+
handle_error if polar.null?
|
34
42
|
|
35
43
|
polar
|
36
44
|
end
|
@@ -39,14 +47,24 @@ module Oso
|
|
39
47
|
def enable_roles
|
40
48
|
result = Rust.enable_roles(self)
|
41
49
|
process_messages
|
42
|
-
|
50
|
+
handle_error if result.zero?
|
43
51
|
end
|
44
52
|
|
45
53
|
# @raise [FFI::Error] if the FFI call returns an error.
|
46
54
|
def validate_roles_config(config)
|
47
55
|
result = Rust.validate_roles_config(self, JSON.dump(config))
|
48
56
|
process_messages
|
49
|
-
|
57
|
+
handle_error if result.zero?
|
58
|
+
end
|
59
|
+
|
60
|
+
def build_filter_plan(types, partials, variable, class_tag)
|
61
|
+
types = JSON.dump(types)
|
62
|
+
partials = JSON.dump(partials)
|
63
|
+
plan = Rust.build_filter_plan(self, types, partials, variable, class_tag)
|
64
|
+
process_messages
|
65
|
+
handle_error if plan.nil?
|
66
|
+
# TODO(gw) more error checking?
|
67
|
+
JSON.parse plan
|
50
68
|
end
|
51
69
|
|
52
70
|
# @param src [String]
|
@@ -55,14 +73,14 @@ module Oso
|
|
55
73
|
def load(src, filename: nil)
|
56
74
|
loaded = Rust.load(self, src, filename)
|
57
75
|
process_messages
|
58
|
-
|
76
|
+
handle_error if loaded.zero?
|
59
77
|
end
|
60
78
|
|
61
79
|
# @raise [FFI::Error] if the FFI call returns an error.
|
62
80
|
def clear_rules
|
63
81
|
cleared = Rust.clear_rules(self)
|
64
82
|
process_messages
|
65
|
-
|
83
|
+
handle_error if cleared.zero?
|
66
84
|
end
|
67
85
|
|
68
86
|
# @return [FFI::Query] if there are remaining inline queries.
|
@@ -80,7 +98,7 @@ module Oso
|
|
80
98
|
id = Rust.new_id(self)
|
81
99
|
# TODO(gj): I don't think this error check is correct. If getting a new ID fails on the
|
82
100
|
# Rust side, it'll probably surface as a panic (e.g., the KB lock is poisoned).
|
83
|
-
|
101
|
+
handle_error if id.zero?
|
84
102
|
|
85
103
|
id
|
86
104
|
end
|
@@ -91,7 +109,7 @@ module Oso
|
|
91
109
|
def new_query_from_str(str)
|
92
110
|
query = Rust.new_query_from_str(self, str, 0)
|
93
111
|
process_messages
|
94
|
-
|
112
|
+
handle_error if query.null?
|
95
113
|
|
96
114
|
query
|
97
115
|
end
|
@@ -102,7 +120,7 @@ module Oso
|
|
102
120
|
def new_query_from_term(term)
|
103
121
|
query = Rust.new_query_from_term(self, JSON.dump(term), 0)
|
104
122
|
process_messages
|
105
|
-
|
123
|
+
handle_error if query.null?
|
106
124
|
|
107
125
|
query
|
108
126
|
end
|
@@ -112,7 +130,7 @@ module Oso
|
|
112
130
|
# @raise [FFI::Error] if the FFI call returns an error.
|
113
131
|
def register_constant(value, name:)
|
114
132
|
registered = Rust.register_constant(self, name, JSON.dump(value))
|
115
|
-
|
133
|
+
handle_error if registered.zero?
|
116
134
|
end
|
117
135
|
|
118
136
|
def next_message
|
@@ -124,9 +142,13 @@ module Oso
|
|
124
142
|
message = next_message
|
125
143
|
break if message.null?
|
126
144
|
|
127
|
-
message.process
|
145
|
+
message.process(enrich_message)
|
128
146
|
end
|
129
147
|
end
|
148
|
+
|
149
|
+
def handle_error
|
150
|
+
raise FFI::Error.get(enrich_message)
|
151
|
+
end
|
130
152
|
end
|
131
153
|
end
|
132
154
|
end
|
data/lib/oso/polar/ffi/query.rb
CHANGED
@@ -7,6 +7,8 @@ module Oso
|
|
7
7
|
module FFI
|
8
8
|
# Wrapper class for Query FFI pointer + operations.
|
9
9
|
class Query < ::FFI::AutoPointer
|
10
|
+
attr_accessor :enrich_message
|
11
|
+
|
10
12
|
Rust = Module.new do
|
11
13
|
extend ::FFI::Library
|
12
14
|
ffi_lib FFI::LIB_PATH
|
@@ -19,6 +21,7 @@ module Oso
|
|
19
21
|
attach_function :next_message, :polar_next_query_message, [FFI::Query], FFI::Message
|
20
22
|
attach_function :source, :polar_query_source_info, [FFI::Query], FFI::Source
|
21
23
|
attach_function :free, :query_free, [FFI::Query], :int32
|
24
|
+
attach_function :bind, :polar_bind, [FFI::Query, :string, :string], :int32
|
22
25
|
end
|
23
26
|
private_constant :Rust
|
24
27
|
|
@@ -27,7 +30,7 @@ module Oso
|
|
27
30
|
def debug_command(cmd)
|
28
31
|
res = Rust.debug_command(self, cmd)
|
29
32
|
process_messages
|
30
|
-
|
33
|
+
handle_error if res.zero?
|
31
34
|
end
|
32
35
|
|
33
36
|
# @param result [String]
|
@@ -35,7 +38,7 @@ module Oso
|
|
35
38
|
# @raise [FFI::Error] if the FFI call returns an error.
|
36
39
|
def call_result(result, call_id:)
|
37
40
|
res = Rust.call_result(self, call_id, result)
|
38
|
-
|
41
|
+
handle_error if res.zero?
|
39
42
|
end
|
40
43
|
|
41
44
|
# @param result [Boolean]
|
@@ -44,7 +47,7 @@ module Oso
|
|
44
47
|
def question_result(result, call_id:)
|
45
48
|
result = result ? 1 : 0
|
46
49
|
res = Rust.question_result(self, call_id, result)
|
47
|
-
|
50
|
+
handle_error if res.zero?
|
48
51
|
end
|
49
52
|
|
50
53
|
# @param result [Boolean]
|
@@ -52,7 +55,7 @@ module Oso
|
|
52
55
|
# @raise [FFI::Error] if the FFI call returns an error.
|
53
56
|
def application_error(message)
|
54
57
|
res = Rust.application_error(self, message)
|
55
|
-
|
58
|
+
handle_error if res.zero?
|
56
59
|
end
|
57
60
|
|
58
61
|
# @return [::Oso::Polar::QueryEvent]
|
@@ -60,11 +63,16 @@ module Oso
|
|
60
63
|
def next_event
|
61
64
|
event = Rust.next_event(self)
|
62
65
|
process_messages
|
63
|
-
|
66
|
+
handle_error if event.null?
|
64
67
|
|
65
68
|
::Oso::Polar::QueryEvent.new(JSON.parse(event.to_s))
|
66
69
|
end
|
67
70
|
|
71
|
+
def bind(name, value)
|
72
|
+
res = Rust.bind(self, name, JSON.dump(value))
|
73
|
+
handle_error if res.zero?
|
74
|
+
end
|
75
|
+
|
68
76
|
def next_message
|
69
77
|
Rust.next_message(self)
|
70
78
|
end
|
@@ -74,7 +82,7 @@ module Oso
|
|
74
82
|
message = next_message
|
75
83
|
break if message.null?
|
76
84
|
|
77
|
-
message.process
|
85
|
+
message.process(enrich_message)
|
78
86
|
end
|
79
87
|
end
|
80
88
|
|
@@ -82,10 +90,14 @@ module Oso
|
|
82
90
|
# @raise [FFI::Error] if the FFI call returns an error.
|
83
91
|
def source
|
84
92
|
res = Rust.source(self)
|
85
|
-
|
93
|
+
handle_error if res.null?
|
86
94
|
|
87
95
|
res.to_s
|
88
96
|
end
|
97
|
+
|
98
|
+
def handle_error
|
99
|
+
raise FFI::Error.get(enrich_message)
|
100
|
+
end
|
89
101
|
end
|
90
102
|
end
|
91
103
|
end
|
data/lib/oso/polar/host.rb
CHANGED
@@ -34,14 +34,29 @@ module Oso
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
+
# For holding type metadata: name, fields, etc.
|
38
|
+
class UserType
|
39
|
+
attr_reader :name, :klass, :id, :fields, :fetcher
|
40
|
+
|
41
|
+
def initialize(name:, klass:, id:, fields:, fetcher:)
|
42
|
+
@name = name
|
43
|
+
@klass = klass
|
44
|
+
@id = id
|
45
|
+
# accept symbol keys
|
46
|
+
@fields = fields.each_with_object({}) { |kv, o| o[kv[0].to_s] = kv[1] }
|
47
|
+
@fetcher = fetcher
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
37
51
|
# Translate between Polar and the host language (Ruby).
|
38
52
|
class Host # rubocop:disable Metrics/ClassLength
|
53
|
+
# @return [Hash<String, Class>]
|
54
|
+
attr_reader :types
|
55
|
+
|
39
56
|
protected
|
40
57
|
|
41
58
|
# @return [FFI::Polar]
|
42
59
|
attr_reader :ffi_polar
|
43
|
-
# @return [Hash<String, Class>]
|
44
|
-
attr_reader :classes
|
45
60
|
# @return [Hash<Integer, Object>]
|
46
61
|
attr_reader :instances
|
47
62
|
# @return [Boolean]
|
@@ -53,39 +68,45 @@ module Oso
|
|
53
68
|
|
54
69
|
def initialize(ffi_polar)
|
55
70
|
@ffi_polar = ffi_polar
|
56
|
-
@
|
71
|
+
@types = {}
|
57
72
|
@instances = {}
|
58
73
|
@accept_expression = false
|
59
74
|
end
|
60
75
|
|
61
76
|
def initialize_copy(other)
|
62
77
|
@ffi_polar = other.ffi_polar
|
63
|
-
@
|
78
|
+
@types = other.types.dup
|
64
79
|
@instances = other.instances.dup
|
65
80
|
end
|
66
81
|
|
67
|
-
# Fetch a Ruby class from the {#
|
82
|
+
# Fetch a Ruby class from the {#types} cache.
|
68
83
|
#
|
69
84
|
# @param name [String]
|
70
85
|
# @return [Class]
|
71
86
|
# @raise [UnregisteredClassError] if the class has not been registered.
|
72
87
|
def get_class(name)
|
73
|
-
raise UnregisteredClassError, name unless
|
88
|
+
raise UnregisteredClassError, name unless types.key? name
|
74
89
|
|
75
|
-
|
90
|
+
types[name].klass.get
|
76
91
|
end
|
77
92
|
|
78
|
-
# Store a Ruby class in the {#
|
93
|
+
# Store a Ruby class in the {#types} cache.
|
79
94
|
#
|
80
95
|
# @param cls [Class] the class to cache.
|
81
96
|
# @param name [String] the name to cache the class as.
|
82
97
|
# @return [String] the name the class is cached as.
|
83
98
|
# @raise [DuplicateClassAliasError] if attempting to register a class
|
84
99
|
# under a previously-registered name.
|
85
|
-
def cache_class(cls, name:)
|
86
|
-
raise DuplicateClassAliasError.new name: name, old: get_class(name), new: cls if
|
100
|
+
def cache_class(cls, name:, fields: {}, fetcher: nil)
|
101
|
+
raise DuplicateClassAliasError.new name: name, old: get_class(name), new: cls if types.key? name
|
87
102
|
|
88
|
-
|
103
|
+
types[name] = types[cls] = UserType.new(
|
104
|
+
name: name,
|
105
|
+
klass: PolarClass.new(cls),
|
106
|
+
id: cache_instance(cls),
|
107
|
+
fields: fields,
|
108
|
+
fetcher: fetcher
|
109
|
+
)
|
89
110
|
name
|
90
111
|
end
|
91
112
|
|
@@ -149,6 +170,33 @@ module Oso
|
|
149
170
|
raise PolarRuntimeError, "Error constructing instance of #{cls_name}: #{e}"
|
150
171
|
end
|
151
172
|
|
173
|
+
OPS = {
|
174
|
+
'Lt' => :<,
|
175
|
+
'Gt' => :>,
|
176
|
+
'Eq' => :==,
|
177
|
+
'Geq' => :>=,
|
178
|
+
'Leq' => :<=,
|
179
|
+
'Neq' => :!=
|
180
|
+
}.freeze
|
181
|
+
|
182
|
+
# Compare two values
|
183
|
+
#
|
184
|
+
# @param op [String] operation to perform.
|
185
|
+
# @param args [Array<Object>] left and right args to operation.
|
186
|
+
# @raise [PolarRuntimeError] if operation fails or is unsupported.
|
187
|
+
# @return [Boolean]
|
188
|
+
def operator(operation, args)
|
189
|
+
left, right = args
|
190
|
+
op = OPS[operation]
|
191
|
+
raise PolarRuntimeError, "Unsupported external operation '#{left.class} #{operation} #{right.class}'" if op.nil?
|
192
|
+
|
193
|
+
begin
|
194
|
+
left.__send__ op, right
|
195
|
+
rescue StandardError
|
196
|
+
raise PolarRuntimeError, "External operation '#{left.class} #{operation} #{right.class}' failed."
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
152
200
|
# Check if the left class is more specific than the right class
|
153
201
|
# with respect to the given instance.
|
154
202
|
#
|
@@ -163,6 +211,10 @@ module Oso
|
|
163
211
|
left_index && right_index && left_index < right_index
|
164
212
|
end
|
165
213
|
|
214
|
+
def subclass?(left_tag:, right_tag:)
|
215
|
+
get_class(left_tag) <= get_class(right_tag)
|
216
|
+
end
|
217
|
+
|
166
218
|
# Check if instance is an instance of class.
|
167
219
|
#
|
168
220
|
# @param instance [Hash<String, Object>]
|
@@ -174,15 +226,30 @@ module Oso
|
|
174
226
|
instance.is_a? cls
|
175
227
|
end
|
176
228
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
229
|
+
def serialize_types # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
230
|
+
polar_types = {}
|
231
|
+
types.values.uniq.each do |typ|
|
232
|
+
tag = typ.name
|
233
|
+
fields = typ.fields
|
234
|
+
field_types = {}
|
235
|
+
fields.each do |k, v|
|
236
|
+
field_types[k] =
|
237
|
+
if v.is_a? ::Oso::Polar::DataFiltering::Relationship
|
238
|
+
{
|
239
|
+
'Relationship' => {
|
240
|
+
'kind' => v.kind,
|
241
|
+
'other_class_tag' => v.other_type,
|
242
|
+
'my_field' => v.my_field,
|
243
|
+
'other_field' => v.other_field
|
244
|
+
}
|
245
|
+
}
|
246
|
+
else
|
247
|
+
{ 'Base' => { 'class_tag' => types[v].name } }
|
248
|
+
end
|
249
|
+
end
|
250
|
+
polar_types[tag] = field_types
|
251
|
+
end
|
252
|
+
polar_types
|
186
253
|
end
|
187
254
|
|
188
255
|
# Turn a Ruby value into a Polar term that's ready to be sent across the
|
@@ -226,7 +293,7 @@ module Oso
|
|
226
293
|
{ 'Pattern' => { 'Instance' => { 'tag' => value.tag, 'fields' => dict['Dictionary'] } } }
|
227
294
|
end
|
228
295
|
else
|
229
|
-
{ 'ExternalInstance' => { 'instance_id' => cache_instance(value), 'repr' =>
|
296
|
+
{ 'ExternalInstance' => { 'instance_id' => cache_instance(value), 'repr' => nil } }
|
230
297
|
end
|
231
298
|
{ 'value' => value }
|
232
299
|
end
|
@@ -292,6 +359,12 @@ module Oso
|
|
292
359
|
raise UnexpectedPolarTypeError, tag
|
293
360
|
end
|
294
361
|
end
|
362
|
+
|
363
|
+
def enrich_message(msg)
|
364
|
+
msg.gsub(/\^\{id: ([0-9]+)\}/) do
|
365
|
+
get_instance(Regexp.last_match[1].to_i).to_s
|
366
|
+
end
|
367
|
+
end
|
295
368
|
end
|
296
369
|
end
|
297
370
|
end
|
data/lib/oso/polar/polar.rb
CHANGED
@@ -34,9 +34,10 @@ module Oso
|
|
34
34
|
# @return [Host]
|
35
35
|
attr_reader :host
|
36
36
|
|
37
|
-
def initialize
|
37
|
+
def initialize # rubocop:disable Metrics/MethodLength
|
38
38
|
@ffi_polar = FFI::Polar.create
|
39
39
|
@host = Host.new(ffi_polar)
|
40
|
+
@ffi_polar.enrich_message = @host.method(:enrich_message)
|
40
41
|
@polar_roles_enabled = false
|
41
42
|
|
42
43
|
# Register global constants.
|
@@ -51,6 +52,10 @@ module Oso
|
|
51
52
|
register_class String
|
52
53
|
end
|
53
54
|
|
55
|
+
def ffi
|
56
|
+
@ffi_polar
|
57
|
+
end
|
58
|
+
|
54
59
|
def enable_roles # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
55
60
|
return if polar_roles_enabled
|
56
61
|
|
@@ -87,6 +92,55 @@ module Oso
|
|
87
92
|
ffi_polar.validate_roles_config(validation_query_results)
|
88
93
|
end
|
89
94
|
|
95
|
+
# get the (maybe user-supplied) name of a class.
|
96
|
+
# kind of a hack because of class autoreloading.
|
97
|
+
def get_class_name(klass) # rubocop:disable Metrics/AbcSize
|
98
|
+
if host.types.key? klass
|
99
|
+
host.types[klass].name
|
100
|
+
elsif host.types.key? klass.name
|
101
|
+
host.types[klass.name].name
|
102
|
+
else
|
103
|
+
rec = host.types.values.find { |v| v.klass.get == klass }
|
104
|
+
raise "Unknown class `#{klass}`" if rec.nil?
|
105
|
+
|
106
|
+
host.types[klass] = rec
|
107
|
+
rec.name
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def get_allowed_resources(actor, action, klass) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
112
|
+
resource = Variable.new 'resource'
|
113
|
+
class_name = get_class_name klass
|
114
|
+
constraint = Expression.new(
|
115
|
+
'And',
|
116
|
+
[Expression.new('Isa', [resource, Pattern.new(class_name, {})])]
|
117
|
+
)
|
118
|
+
|
119
|
+
results = query_rule(
|
120
|
+
'allow',
|
121
|
+
actor,
|
122
|
+
action,
|
123
|
+
resource,
|
124
|
+
bindings: { 'resource' => constraint },
|
125
|
+
accept_expression: true
|
126
|
+
)
|
127
|
+
|
128
|
+
complete = []
|
129
|
+
partial = []
|
130
|
+
|
131
|
+
results.to_a.each do |result|
|
132
|
+
result.to_a.each do |key, val|
|
133
|
+
if val.is_a? Expression
|
134
|
+
partial.push({ 'bindings' => { key => host.to_polar(val) } })
|
135
|
+
else
|
136
|
+
complete.push val
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
filter = ::Oso::Polar::DataFiltering::FilterPlan.new(self, partial, class_name)
|
141
|
+
complete + filter.resolve
|
142
|
+
end
|
143
|
+
|
90
144
|
# Clear all rules and rule sources from the current Polar instance
|
91
145
|
#
|
92
146
|
# @return [self] for chaining.
|
@@ -149,17 +203,16 @@ module Oso
|
|
149
203
|
# @param query [Predicate]
|
150
204
|
# @return [Enumerator] of resulting bindings
|
151
205
|
# @raise [Error] if the FFI call raises one.
|
152
|
-
def query(query)
|
153
|
-
new_host = host.dup
|
206
|
+
def query(query, host: self.host.dup, bindings: {})
|
154
207
|
case query
|
155
208
|
when String
|
156
209
|
ffi_query = ffi_polar.new_query_from_str(query)
|
157
210
|
when Predicate
|
158
|
-
ffi_query = ffi_polar.new_query_from_term(
|
211
|
+
ffi_query = ffi_polar.new_query_from_term(host.to_polar(query))
|
159
212
|
else
|
160
213
|
raise InvalidQueryTypeError
|
161
214
|
end
|
162
|
-
Query.new(ffi_query, host:
|
215
|
+
Query.new(ffi_query, host: host, bindings: bindings)
|
163
216
|
end
|
164
217
|
|
165
218
|
# Query for a rule.
|
@@ -168,8 +221,10 @@ module Oso
|
|
168
221
|
# @param args [Array<Object>]
|
169
222
|
# @return [Enumerator] of resulting bindings
|
170
223
|
# @raise [Error] if the FFI call raises one.
|
171
|
-
def query_rule(name, *args)
|
172
|
-
|
224
|
+
def query_rule(name, *args, accept_expression: false, bindings: {})
|
225
|
+
host = self.host.dup
|
226
|
+
host.accept_expression = accept_expression
|
227
|
+
query(Predicate.new(name, args: args), host: host, bindings: bindings)
|
173
228
|
end
|
174
229
|
|
175
230
|
# Register a Ruby class with Polar.
|
@@ -180,8 +235,8 @@ module Oso
|
|
180
235
|
# under a previously-registered name.
|
181
236
|
# @raise [FFI::Error] if the FFI call returns an error.
|
182
237
|
# @return [self] for chaining.
|
183
|
-
def register_class(cls, name: nil)
|
184
|
-
name = host.cache_class(cls, name: name || cls.name)
|
238
|
+
def register_class(cls, name: nil, fields: {}, fetcher: nil)
|
239
|
+
name = host.cache_class(cls, name: name || cls.name, fields: fields, fetcher: fetcher)
|
185
240
|
register_constant(cls, name: name)
|
186
241
|
end
|
187
242
|
|
data/lib/oso/polar/query.rb
CHANGED
@@ -10,10 +10,12 @@ module Oso
|
|
10
10
|
|
11
11
|
# @param ffi_query [FFI::Query]
|
12
12
|
# @param host [Oso::Polar::Host]
|
13
|
-
def initialize(ffi_query, host:)
|
13
|
+
def initialize(ffi_query, host:, bindings: {})
|
14
14
|
@calls = {}
|
15
15
|
@ffi_query = ffi_query
|
16
|
+
ffi_query.enrich_message = host.method(:enrich_message)
|
16
17
|
@host = host
|
18
|
+
bindings.each { |k, v| ffi_query.bind k, host.to_polar(v) }
|
17
19
|
end
|
18
20
|
|
19
21
|
private
|
@@ -70,6 +72,9 @@ module Oso
|
|
70
72
|
# @raise [Error] if the FFI call raises one.
|
71
73
|
def handle_call(attribute, call_id:, instance:, args:, kwargs:) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
72
74
|
instance = host.to_ruby(instance)
|
75
|
+
rel = get_relationship(instance.class, attribute)
|
76
|
+
return handle_relationship(call_id, instance, rel) unless rel.nil?
|
77
|
+
|
73
78
|
args = args.map { |a| host.to_ruby(a) }
|
74
79
|
kwargs = Hash[kwargs.map { |k, v| [k.to_sym, host.to_ruby(v)] }]
|
75
80
|
# The kwargs.empty? check is for Ruby < 2.7.
|
@@ -85,6 +90,40 @@ module Oso
|
|
85
90
|
call_result(nil, call_id: call_id)
|
86
91
|
end
|
87
92
|
|
93
|
+
# Get the type information for a field on a class.
|
94
|
+
#
|
95
|
+
# @param cls [UserType]
|
96
|
+
# @param tag [String]
|
97
|
+
# @return [UserType]
|
98
|
+
# @raise [Error] if no information is found
|
99
|
+
def get_field(cls, tag) # rubocop:disable Metrics/AbcSize
|
100
|
+
raise unless cls.fields.key? tag
|
101
|
+
|
102
|
+
ref = cls.fields[tag]
|
103
|
+
return host.types[ref] unless ref.is_a? ::Oso::Polar::DataFiltering::Relationship
|
104
|
+
|
105
|
+
case ref.kind
|
106
|
+
when 'parent'
|
107
|
+
host.types[ref.other_type]
|
108
|
+
when 'children'
|
109
|
+
host.types[Array]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Check if a series of dot operations on a base class yield an
|
114
|
+
# instance of another class.
|
115
|
+
def handle_external_isa_with_path(data) # rubocop:disable Metrics/AbcSize
|
116
|
+
sup = host.types[data['class_tag']]
|
117
|
+
bas = host.types[data['base_tag']]
|
118
|
+
path = data['path'].map(&host.method(:to_ruby))
|
119
|
+
sub = path.reduce(bas) { |cls, tag| get_field(cls, tag) }
|
120
|
+
answer = sub.klass.get <= sup.klass.get
|
121
|
+
question_result(answer, call_id: data['call_id'])
|
122
|
+
rescue StandardError => e
|
123
|
+
application_error e.message
|
124
|
+
question_result(nil, call_id: data['call_id'])
|
125
|
+
end
|
126
|
+
|
88
127
|
def handle_next_external(call_id, iterable)
|
89
128
|
unless calls.key? call_id
|
90
129
|
value = host.to_ruby iterable
|
@@ -128,6 +167,8 @@ module Oso
|
|
128
167
|
yield event.data['bindings'].transform_values { |v| host.to_ruby(v) }
|
129
168
|
when 'MakeExternal'
|
130
169
|
handle_make_external(event.data)
|
170
|
+
when 'ExternalIsaWithPath'
|
171
|
+
handle_external_isa_with_path(event.data)
|
131
172
|
when 'ExternalCall'
|
132
173
|
call_id = event.data['call_id']
|
133
174
|
instance = event.data['instance']
|
@@ -141,18 +182,23 @@ module Oso
|
|
141
182
|
right_tag = event.data['right_class_tag']
|
142
183
|
answer = host.subspecializer?(instance_id, left_tag: left_tag, right_tag: right_tag)
|
143
184
|
question_result(answer, call_id: event.data['call_id'])
|
185
|
+
when 'ExternalIsSubclass'
|
186
|
+
call_id = event.data['call_id']
|
187
|
+
left = event.data['left_class_tag']
|
188
|
+
right = event.data['right_class_tag']
|
189
|
+
answer = host.subclass?(left_tag: left, right_tag: right)
|
190
|
+
question_result(answer, call_id: call_id)
|
144
191
|
when 'ExternalIsa'
|
145
192
|
instance = event.data['instance']
|
146
193
|
class_tag = event.data['class_tag']
|
147
194
|
answer = host.isa?(instance, class_tag: class_tag)
|
148
195
|
question_result(answer, call_id: event.data['call_id'])
|
149
|
-
when 'ExternalUnify'
|
150
|
-
left_instance_id = event.data['left_instance_id']
|
151
|
-
right_instance_id = event.data['right_instance_id']
|
152
|
-
answer = host.unify?(left_instance_id, right_instance_id)
|
153
|
-
question_result(answer, call_id: event.data['call_id'])
|
154
196
|
when 'Debug'
|
155
|
-
|
197
|
+
msg = event.data['message']
|
198
|
+
if msg
|
199
|
+
msg = host.enrich_message(msg) if msg
|
200
|
+
puts msg
|
201
|
+
end
|
156
202
|
print 'debug> '
|
157
203
|
begin
|
158
204
|
input = $stdin.readline.chomp.chomp(';')
|
@@ -162,7 +208,10 @@ module Oso
|
|
162
208
|
command = JSON.dump(host.to_polar(input))
|
163
209
|
ffi_query.debug_command(command)
|
164
210
|
when 'ExternalOp'
|
165
|
-
|
211
|
+
op = event.data['operator']
|
212
|
+
args = event.data['args'].map(&host.method(:to_ruby))
|
213
|
+
answer = host.operator(op, args)
|
214
|
+
question_result(answer, call_id: event.data['call_id'])
|
166
215
|
when 'NextExternal'
|
167
216
|
call_id = event.data['call_id']
|
168
217
|
iterable = event.data['iterable']
|
@@ -172,6 +221,35 @@ module Oso
|
|
172
221
|
end
|
173
222
|
end
|
174
223
|
end
|
224
|
+
|
225
|
+
def get_relationship(cls, attr)
|
226
|
+
typ = host.types[cls]
|
227
|
+
return unless typ
|
228
|
+
|
229
|
+
rel = typ.fields[attr]
|
230
|
+
return unless rel.is_a? ::Oso::Polar::DataFiltering::Relationship
|
231
|
+
|
232
|
+
rel
|
233
|
+
end
|
234
|
+
|
235
|
+
def handle_relationship(call_id, instance, rel) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
236
|
+
fetcher = host.types[rel.other_type].fetcher
|
237
|
+
constraint = ::Oso::Polar::DataFiltering::Constraint.new(
|
238
|
+
kind: 'Eq',
|
239
|
+
field: rel.other_field,
|
240
|
+
value: instance.send(rel.my_field)
|
241
|
+
)
|
242
|
+
res = fetcher[[constraint]].uniq
|
243
|
+
|
244
|
+
if rel.kind == 'parent'
|
245
|
+
raise "multiple parents: #{res}" unless res.length == 1
|
246
|
+
|
247
|
+
res = res[0]
|
248
|
+
end
|
249
|
+
|
250
|
+
res = JSON.dump host.to_polar res
|
251
|
+
call_result(res, call_id: call_id)
|
252
|
+
end
|
175
253
|
end
|
176
254
|
end
|
177
255
|
end
|
data/lib/oso/polar.rb
CHANGED
data/lib/oso/version.rb
CHANGED
data/oso-oso.gemspec
CHANGED
@@ -31,10 +31,12 @@ Gem::Specification.new do |spec|
|
|
31
31
|
spec.add_runtime_dependency 'ffi', '~> 1.0'
|
32
32
|
|
33
33
|
# Development dependencies
|
34
|
+
spec.add_development_dependency 'activerecord'
|
34
35
|
spec.add_development_dependency 'pry-byebug', '~> 3.9.0'
|
35
36
|
spec.add_development_dependency 'rake', '~> 12.0'
|
36
37
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
37
38
|
spec.add_development_dependency 'rubocop', '~> 0.89.1'
|
38
39
|
spec.add_development_dependency 'solargraph', '~> 0.39.14'
|
40
|
+
spec.add_development_dependency 'sqlite3'
|
39
41
|
spec.add_development_dependency 'yard', '~> 0.9.25'
|
40
42
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: oso-oso
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.20.0.pre.beta
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Oso Security, Inc.
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-08-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ffi
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activerecord
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: pry-byebug
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -94,6 +108,20 @@ dependencies:
|
|
94
108
|
- - "~>"
|
95
109
|
- !ruby/object:Gem::Version
|
96
110
|
version: 0.39.14
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: sqlite3
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
97
125
|
- !ruby/object:Gem::Dependency
|
98
126
|
name: yard
|
99
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -132,6 +160,7 @@ files:
|
|
132
160
|
- lib/oso.rb
|
133
161
|
- lib/oso/oso.rb
|
134
162
|
- lib/oso/polar.rb
|
163
|
+
- lib/oso/polar/data_filtering.rb
|
135
164
|
- lib/oso/polar/errors.rb
|
136
165
|
- lib/oso/polar/expression.rb
|
137
166
|
- lib/oso/polar/ffi.rb
|
@@ -167,9 +196,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
167
196
|
version: 2.4.0
|
168
197
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
169
198
|
requirements:
|
170
|
-
- - "
|
199
|
+
- - ">"
|
171
200
|
- !ruby/object:Gem::Version
|
172
|
-
version:
|
201
|
+
version: 1.3.1
|
173
202
|
requirements: []
|
174
203
|
rubyforge_project:
|
175
204
|
rubygems_version: 2.6.14.4
|