oso-oso 0.5.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/Makefile +5 -5
- 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/errors.rb +13 -19
- data/lib/oso/polar/ffi.rb +8 -0
- data/lib/oso/polar/ffi/error.rb +4 -0
- data/lib/oso/polar/ffi/message.rb +3 -1
- data/lib/oso/polar/ffi/polar.rb +14 -4
- data/lib/oso/polar/ffi/query.rb +12 -0
- data/lib/oso/polar/ffi/source.rb +24 -0
- data/lib/oso/polar/host.rb +49 -68
- data/lib/oso/polar/polar.rb +33 -44
- data/lib/oso/polar/query.rb +36 -29
- data/lib/oso/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2dbfedef3bc14832b47137d1c17b247af0cf8bf3
|
4
|
+
data.tar.gz: 3bad36d04f6744069299b72855be913bc4bd9562
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 13d18f6a934a266e82ec7e981187f7f8523c998b55158044bc09271c255fa56e40159246a55096447296971d88e9c5c68355684d37db99cea59b911810949cd9
|
7
|
+
data.tar.gz: 5cdae20c3b4c08752db940bcce466d65a787e9728ec935b837eb20eb98fffda4e88f812f63c1d75822f2bd25e6e741c1ae6df8620f2b587ca4b5db0f4901029b
|
data/Gemfile.lock
CHANGED
data/Makefile
CHANGED
@@ -3,17 +3,17 @@
|
|
3
3
|
rust:
|
4
4
|
$(MAKE) -C ../.. rust-build
|
5
5
|
|
6
|
-
install:
|
6
|
+
install:
|
7
7
|
bundle install
|
8
8
|
|
9
|
-
test: install
|
9
|
+
test: install rust
|
10
10
|
bundle exec rake spec
|
11
11
|
|
12
|
-
lint:
|
12
|
+
lint: install
|
13
13
|
bundle exec rubocop
|
14
14
|
|
15
|
-
typecheck:
|
15
|
+
typecheck: install
|
16
16
|
bundle exec solargraph typecheck
|
17
17
|
|
18
|
-
repl: install
|
18
|
+
repl: install rust
|
19
19
|
bundle exec oso
|
Binary file
|
data/ext/oso-oso/lib/libpolar.so
CHANGED
Binary file
|
data/ext/oso-oso/lib/polar.dll
CHANGED
Binary file
|
data/lib/oso/polar/errors.rb
CHANGED
@@ -27,36 +27,24 @@ module Oso
|
|
27
27
|
class UnsupportedError < PolarRuntimeError; end
|
28
28
|
class PolarTypeError < PolarRuntimeError; end
|
29
29
|
class StackOverflowError < PolarRuntimeError; end
|
30
|
+
class FileLoadingError < PolarRuntimeError; end
|
30
31
|
|
31
32
|
# Errors originating from this side of the FFI boundary.
|
32
33
|
|
33
34
|
class UnregisteredClassError < PolarRuntimeError; end
|
34
|
-
class MissingConstructorError < PolarRuntimeError; end
|
35
35
|
class UnregisteredInstanceError < PolarRuntimeError; end
|
36
36
|
class DuplicateInstanceRegistrationError < PolarRuntimeError; end
|
37
|
+
|
38
|
+
# TODO: I think this should probably have some arguments to say what the call is
|
37
39
|
class InvalidCallError < PolarRuntimeError; end
|
38
40
|
class InvalidConstructorError < PolarRuntimeError; end
|
39
41
|
class InvalidQueryTypeError < PolarRuntimeError; end
|
40
|
-
class InlineQueryFailedError < PolarRuntimeError; end
|
41
42
|
class NullByteInPolarFileError < PolarRuntimeError; end
|
42
43
|
class UnexpectedPolarTypeError < PolarRuntimeError; end
|
43
|
-
class
|
44
|
-
# @param
|
45
|
-
def initialize(
|
46
|
-
super("
|
47
|
-
end
|
48
|
-
end
|
49
|
-
class PolarFileContentsChangedError < PolarRuntimeError # rubocop:disable Style/Documentation
|
50
|
-
# @param file [String]
|
51
|
-
def initialize(file)
|
52
|
-
super("A file with the name #{file}, but different contents, has already been loaded.")
|
53
|
-
end
|
54
|
-
end
|
55
|
-
class PolarFileNameChangedError < PolarRuntimeError # rubocop:disable Style/Documentation
|
56
|
-
# @param file [String]
|
57
|
-
# @param existing [String]
|
58
|
-
def initialize(file, existing)
|
59
|
-
super("A file with the same contents as #{file} named #{existing} has already been loaded.")
|
44
|
+
class InlineQueryFailedError < PolarRuntimeError; # rubocop:disable Style/Documentation
|
45
|
+
# @param source [String]
|
46
|
+
def initialize(source)
|
47
|
+
super("Inline query failed: #{source}")
|
60
48
|
end
|
61
49
|
end
|
62
50
|
class PolarFileExtensionError < PolarRuntimeError # rubocop:disable Style/Documentation
|
@@ -79,6 +67,12 @@ module Oso
|
|
79
67
|
end
|
80
68
|
end
|
81
69
|
|
70
|
+
class UnimplementedOperationError < PolarRuntimeError # rubocop:disable Style/Documentation
|
71
|
+
def initialize(operation)
|
72
|
+
super("#{operation} are unimplemented in the oso Ruby library")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
82
76
|
# Generic operational exception.
|
83
77
|
class OperationalError < Error; end
|
84
78
|
class UnknownError < OperationalError; end
|
data/lib/oso/polar/ffi.rb
CHANGED
@@ -46,6 +46,13 @@ module Oso
|
|
46
46
|
Rust.free(ptr) unless ptr.null?
|
47
47
|
end
|
48
48
|
end
|
49
|
+
|
50
|
+
# Wrapper class for Source FFI pointer.
|
51
|
+
class Source < ::FFI::AutoPointer
|
52
|
+
def self.release(ptr)
|
53
|
+
Rust.free(ptr) unless ptr.null?
|
54
|
+
end
|
55
|
+
end
|
49
56
|
end
|
50
57
|
private_constant :FFI
|
51
58
|
end
|
@@ -56,3 +63,4 @@ require 'oso/polar/ffi/query'
|
|
56
63
|
require 'oso/polar/ffi/query_event'
|
57
64
|
require 'oso/polar/ffi/error'
|
58
65
|
require 'oso/polar/ffi/message'
|
66
|
+
require 'oso/polar/ffi/source'
|
data/lib/oso/polar/ffi/error.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'json'
|
4
|
+
|
3
5
|
module Oso
|
4
6
|
module Polar
|
5
7
|
module FFI
|
@@ -83,6 +85,8 @@ module Oso
|
|
83
85
|
::Oso::Polar::PolarTypeError.new(msg, details: details)
|
84
86
|
when 'StackOverflow'
|
85
87
|
::Oso::Polar::StackOverflowError.new(msg, details: details)
|
88
|
+
when 'FileLoading'
|
89
|
+
::Oso::Polar::FileLoadingError.new(msg, details: details)
|
86
90
|
else
|
87
91
|
::Oso::Polar::PolarRuntimeError.new(msg, details: details)
|
88
92
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'json'
|
4
|
+
|
3
5
|
module Oso
|
4
6
|
module Polar
|
5
7
|
module FFI
|
@@ -26,7 +28,7 @@ module Oso
|
|
26
28
|
when 'Print'
|
27
29
|
puts(msg)
|
28
30
|
when 'Warning'
|
29
|
-
warn('[warning] %<msg>s')
|
31
|
+
warn(format('[warning] %<msg>s', msg: msg))
|
30
32
|
end
|
31
33
|
end
|
32
34
|
|
data/lib/oso/polar/ffi/polar.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'json'
|
4
|
+
|
3
5
|
module Oso
|
4
6
|
module Polar
|
5
7
|
module FFI
|
@@ -10,7 +12,8 @@ module Oso
|
|
10
12
|
ffi_lib FFI::LIB_PATH
|
11
13
|
|
12
14
|
attach_function :new, :polar_new, [], FFI::Polar
|
13
|
-
attach_function :
|
15
|
+
attach_function :load, :polar_load, [FFI::Polar, :string, :string], :int32
|
16
|
+
attach_function :clear_rules, :polar_clear_rules, [FFI::Polar], :int32
|
14
17
|
attach_function :next_inline_query, :polar_next_inline_query, [FFI::Polar, :uint32], FFI::Query
|
15
18
|
attach_function :new_id, :polar_get_external_id, [FFI::Polar], :uint64
|
16
19
|
attach_function :new_query_from_str, :polar_new_query, [FFI::Polar, :string, :uint32], FFI::Query
|
@@ -33,12 +36,19 @@ module Oso
|
|
33
36
|
# @param src [String]
|
34
37
|
# @param filename [String]
|
35
38
|
# @raise [FFI::Error] if the FFI call returns an error.
|
36
|
-
def
|
37
|
-
loaded = Rust.
|
39
|
+
def load(src, filename: nil)
|
40
|
+
loaded = Rust.load(self, src, filename)
|
38
41
|
process_messages
|
39
42
|
raise FFI::Error.get if loaded.zero?
|
40
43
|
end
|
41
44
|
|
45
|
+
# @raise [FFI::Error] if the FFI call returns an error.
|
46
|
+
def clear_rules
|
47
|
+
cleared = Rust.clear_rules(self)
|
48
|
+
process_messages
|
49
|
+
raise FFI::Error.get if cleared.zero?
|
50
|
+
end
|
51
|
+
|
42
52
|
# @return [FFI::Query] if there are remaining inline queries.
|
43
53
|
# @return [nil] if there are no remaining inline queries.
|
44
54
|
# @raise [FFI::Error] if the FFI call returns an error.
|
@@ -84,7 +94,7 @@ module Oso
|
|
84
94
|
# @param name [String]
|
85
95
|
# @param value [Hash<String, Object>]
|
86
96
|
# @raise [FFI::Error] if the FFI call returns an error.
|
87
|
-
def register_constant(
|
97
|
+
def register_constant(value, name:)
|
88
98
|
registered = Rust.register_constant(self, name, JSON.dump(value))
|
89
99
|
raise FFI::Error.get if registered.zero?
|
90
100
|
end
|
data/lib/oso/polar/ffi/query.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'json'
|
4
|
+
|
3
5
|
module Oso
|
4
6
|
module Polar
|
5
7
|
module FFI
|
@@ -15,6 +17,7 @@ module Oso
|
|
15
17
|
attach_function :application_error, :polar_application_error, [FFI::Query, :string], :int32
|
16
18
|
attach_function :next_event, :polar_next_query_event, [FFI::Query], FFI::QueryEvent
|
17
19
|
attach_function :next_message, :polar_next_query_message, [FFI::Query], FFI::Message
|
20
|
+
attach_function :source, :polar_query_source_info, [FFI::Query], FFI::Source
|
18
21
|
attach_function :free, :query_free, [FFI::Query], :int32
|
19
22
|
end
|
20
23
|
private_constant :Rust
|
@@ -74,6 +77,15 @@ module Oso
|
|
74
77
|
message.process
|
75
78
|
end
|
76
79
|
end
|
80
|
+
|
81
|
+
# @return [String]
|
82
|
+
# @raise [FFI::Error] if the FFI call returns an error.
|
83
|
+
def source
|
84
|
+
res = Rust.source(self)
|
85
|
+
raise FFI::Error.get if res.null?
|
86
|
+
|
87
|
+
res.to_s
|
88
|
+
end
|
77
89
|
end
|
78
90
|
end
|
79
91
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Oso
|
4
|
+
module Polar
|
5
|
+
module FFI
|
6
|
+
# Wrapper class for Source FFI pointer.
|
7
|
+
class Source < ::FFI::AutoPointer
|
8
|
+
# @return [String]
|
9
|
+
def to_s
|
10
|
+
@to_s ||= read_string.force_encoding('UTF-8')
|
11
|
+
end
|
12
|
+
|
13
|
+
Rust = Module.new do
|
14
|
+
extend ::FFI::Library
|
15
|
+
ffi_lib FFI::LIB_PATH
|
16
|
+
|
17
|
+
attach_function :free, :string_free, [Message], :int32
|
18
|
+
end
|
19
|
+
|
20
|
+
private_constant :Rust
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/oso/polar/host.rb
CHANGED
@@ -10,8 +10,6 @@ module Oso
|
|
10
10
|
attr_reader :ffi_polar
|
11
11
|
# @return [Hash<String, Class>]
|
12
12
|
attr_reader :classes
|
13
|
-
# @return [Hash<String, Object>]
|
14
|
-
attr_reader :constructors
|
15
13
|
# @return [Hash<Integer, Object>]
|
16
14
|
attr_reader :instances
|
17
15
|
|
@@ -20,14 +18,12 @@ module Oso
|
|
20
18
|
def initialize(ffi_polar)
|
21
19
|
@ffi_polar = ffi_polar
|
22
20
|
@classes = {}
|
23
|
-
@constructors = {}
|
24
21
|
@instances = {}
|
25
22
|
end
|
26
23
|
|
27
24
|
def initialize_copy(other)
|
28
25
|
@ffi_polar = other.ffi_polar
|
29
26
|
@classes = other.classes.dup
|
30
|
-
@constructors = other.constructors.dup
|
31
27
|
@instances = other.instances.dup
|
32
28
|
end
|
33
29
|
|
@@ -44,38 +40,18 @@ module Oso
|
|
44
40
|
|
45
41
|
# Store a Ruby class in the {#classes} cache.
|
46
42
|
#
|
47
|
-
# @param cls [Class] the class to cache
|
48
|
-
# @param name [String] the name to cache the class as.
|
49
|
-
# @param constructor [Proc] optional custom constructor function. Defaults to the :new method.
|
43
|
+
# @param cls [Class] the class to cache.
|
44
|
+
# @param name [String] the name to cache the class as.
|
50
45
|
# @return [String] the name the class is cached as.
|
51
|
-
# @raise [
|
52
|
-
|
53
|
-
|
46
|
+
# @raise [DuplicateClassAliasError] if attempting to register a class
|
47
|
+
# under a previously-registered name.
|
48
|
+
def cache_class(cls, name:)
|
54
49
|
raise DuplicateClassAliasError, name: name, old: get_class(name), new: cls if classes.key? name
|
55
50
|
|
56
51
|
classes[name] = cls
|
57
|
-
if constructor.nil?
|
58
|
-
constructors[name] = :new
|
59
|
-
elsif constructor.respond_to? :call
|
60
|
-
constructors[name] = constructor
|
61
|
-
else
|
62
|
-
raise InvalidConstructorError
|
63
|
-
end
|
64
52
|
name
|
65
53
|
end
|
66
54
|
|
67
|
-
# Fetch a constructor from the {#constructors} cache.
|
68
|
-
#
|
69
|
-
# @param name [String]
|
70
|
-
# @return [Symbol] if constructor is the default of `:new`.
|
71
|
-
# @return [Proc] if a custom constructor was registered.
|
72
|
-
# @raise [UnregisteredConstructorError] if the constructor has not been registered.
|
73
|
-
def get_constructor(name)
|
74
|
-
raise MissingConstructorError, name unless constructors.key? name
|
75
|
-
|
76
|
-
constructors[name]
|
77
|
-
end
|
78
|
-
|
79
55
|
# Check if an instance exists in the {#instances} cache.
|
80
56
|
#
|
81
57
|
# @param id [Integer]
|
@@ -100,12 +76,12 @@ module Oso
|
|
100
76
|
instances[id]
|
101
77
|
end
|
102
78
|
|
103
|
-
# Cache a Ruby instance in the {#instances} cache, fetching a
|
104
|
-
#
|
79
|
+
# Cache a Ruby instance in the {#instances} cache, fetching a new id if
|
80
|
+
# one isn't provided.
|
105
81
|
#
|
106
82
|
# @param instance [Object]
|
107
|
-
# @param id [Integer]
|
108
|
-
# @return [Integer]
|
83
|
+
# @param id [Integer] the instance ID. Generated via FFI if not provided.
|
84
|
+
# @return [Integer] the instance ID.
|
109
85
|
def cache_instance(instance, id: nil)
|
110
86
|
id = ffi_polar.new_id if id.nil?
|
111
87
|
instances[id] = instance
|
@@ -114,30 +90,17 @@ module Oso
|
|
114
90
|
|
115
91
|
# Construct and cache a Ruby instance.
|
116
92
|
#
|
117
|
-
# @param cls_name [String]
|
118
|
-
# @param
|
119
|
-
# @param
|
93
|
+
# @param cls_name [String] name of the instance's class.
|
94
|
+
# @param args [Array<Object>] positional args to the constructor.
|
95
|
+
# @param kwargs [Hash<String, Object>] keyword args to the constructor.
|
96
|
+
# @param id [Integer] the instance ID.
|
120
97
|
# @raise [PolarRuntimeError] if instance construction fails.
|
121
|
-
|
122
|
-
|
123
|
-
instance = if
|
124
|
-
|
125
|
-
get_class(cls_name).__send__(:new)
|
126
|
-
elsif initargs.is_a? Array
|
127
|
-
get_class(cls_name).__send__(:new, *initargs)
|
128
|
-
elsif initargs.is_a? Hash
|
129
|
-
get_class(cls_name).__send__(:new, **initargs)
|
130
|
-
else
|
131
|
-
raise PolarRuntimeError, "Bad initargs: #{initargs}"
|
132
|
-
end
|
133
|
-
elsif initargs.empty?
|
134
|
-
constructor.call
|
135
|
-
elsif initargs.is_a? Array
|
136
|
-
constructor.call(*initargs)
|
137
|
-
elsif initargs.is_a? Hash
|
138
|
-
constructor.call(**initargs)
|
98
|
+
# @return [Integer] the instance ID.
|
99
|
+
def make_instance(cls_name, args:, kwargs:, id:)
|
100
|
+
instance = if kwargs.empty? # This check is for Ruby < 2.7.
|
101
|
+
get_class(cls_name).__send__(:new, *args)
|
139
102
|
else
|
140
|
-
|
103
|
+
get_class(cls_name).__send__(:new, *args, **kwargs)
|
141
104
|
end
|
142
105
|
cache_instance(instance, id: id)
|
143
106
|
rescue StandardError => e
|
@@ -153,9 +116,9 @@ module Oso
|
|
153
116
|
# @return [Boolean]
|
154
117
|
def subspecializer?(instance_id, left_tag:, right_tag:)
|
155
118
|
mro = get_instance(instance_id).class.ancestors
|
156
|
-
|
157
|
-
|
158
|
-
|
119
|
+
left_index = mro.index(get_class(left_tag))
|
120
|
+
right_index = mro.index(get_class(right_tag))
|
121
|
+
left_index && right_index && left_index < right_index
|
159
122
|
end
|
160
123
|
|
161
124
|
# Check if instance is an instance of class.
|
@@ -167,8 +130,6 @@ module Oso
|
|
167
130
|
instance = to_ruby(instance)
|
168
131
|
cls = get_class(class_tag)
|
169
132
|
instance.is_a? cls
|
170
|
-
rescue PolarRuntimeError
|
171
|
-
false
|
172
133
|
end
|
173
134
|
|
174
135
|
# Check if two instances unify
|
@@ -180,8 +141,6 @@ module Oso
|
|
180
141
|
left_instance = get_instance(left_instance_id)
|
181
142
|
right_instance = get_instance(right_instance_id)
|
182
143
|
left_instance == right_instance
|
183
|
-
rescue PolarRuntimeError
|
184
|
-
false
|
185
144
|
end
|
186
145
|
|
187
146
|
# Turn a Ruby value into a Polar term that's ready to be sent across the
|
@@ -189,22 +148,29 @@ module Oso
|
|
189
148
|
#
|
190
149
|
# @param value [Object]
|
191
150
|
# @return [Hash<String, Object>]
|
192
|
-
def
|
151
|
+
def to_polar(value) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
193
152
|
value = case true # rubocop:disable Lint/LiteralAsCondition
|
194
153
|
when value.instance_of?(TrueClass) || value.instance_of?(FalseClass)
|
195
154
|
{ 'Boolean' => value }
|
196
155
|
when value.instance_of?(Integer)
|
197
156
|
{ 'Number' => { 'Integer' => value } }
|
198
157
|
when value.instance_of?(Float)
|
158
|
+
if value == Float::INFINITY
|
159
|
+
value = 'Infinity'
|
160
|
+
elsif value == -Float::INFINITY
|
161
|
+
value = '-Infinity'
|
162
|
+
elsif value.nan?
|
163
|
+
value = 'NaN'
|
164
|
+
end
|
199
165
|
{ 'Number' => { 'Float' => value } }
|
200
166
|
when value.instance_of?(String)
|
201
167
|
{ 'String' => value }
|
202
168
|
when value.instance_of?(Array)
|
203
|
-
{ 'List' => value.map { |el|
|
169
|
+
{ 'List' => value.map { |el| to_polar(el) } }
|
204
170
|
when value.instance_of?(Hash)
|
205
|
-
{ 'Dictionary' => { 'fields' => value.transform_values { |v|
|
171
|
+
{ 'Dictionary' => { 'fields' => value.transform_values { |v| to_polar(v) } } }
|
206
172
|
when value.instance_of?(Predicate)
|
207
|
-
{ 'Call' => { 'name' => value.name, 'args' => value.args.map { |el|
|
173
|
+
{ 'Call' => { 'name' => value.name, 'args' => value.args.map { |el| to_polar(el) } } }
|
208
174
|
when value.instance_of?(Variable)
|
209
175
|
# This is supported so that we can query for unbound variables
|
210
176
|
{ 'Variable' => value }
|
@@ -222,13 +188,28 @@ module Oso
|
|
222
188
|
# @option data [Hash<String, Object>] :value
|
223
189
|
# @return [Object]
|
224
190
|
# @raise [UnexpectedPolarTypeError] if type cannot be converted to Ruby.
|
225
|
-
def to_ruby(data) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
191
|
+
def to_ruby(data) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
226
192
|
tag, value = data['value'].first
|
227
193
|
case tag
|
228
194
|
when 'String', 'Boolean'
|
229
195
|
value
|
230
196
|
when 'Number'
|
231
|
-
value.values.first
|
197
|
+
num = value.values.first
|
198
|
+
if value.key? 'Float'
|
199
|
+
case num
|
200
|
+
when 'Infinity'
|
201
|
+
return Float::INFINITY
|
202
|
+
when '-Infinity'
|
203
|
+
return -Float::INFINITY
|
204
|
+
when 'NaN'
|
205
|
+
return Float::NAN
|
206
|
+
else
|
207
|
+
unless value['Float'].is_a? Float # rubocop:disable Metrics/BlockNesting
|
208
|
+
raise PolarRuntimeError, "Expected a floating point number, got \"#{value['Float']}\""
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
num
|
232
213
|
when 'List'
|
233
214
|
value.map { |el| to_ruby(el) }
|
234
215
|
when 'Dictionary'
|
data/lib/oso/polar/polar.rb
CHANGED
@@ -1,10 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'json'
|
4
|
-
require 'pp'
|
5
|
-
require 'set'
|
6
|
-
require 'digest/md5'
|
7
|
-
|
8
3
|
# Missing Ruby type.
|
9
4
|
module PolarBoolean; end
|
10
5
|
# Monkey-patch Ruby true type.
|
@@ -42,8 +37,6 @@ module Oso
|
|
42
37
|
def initialize
|
43
38
|
@ffi_polar = FFI::Polar.create
|
44
39
|
@host = Host.new(ffi_polar)
|
45
|
-
@loaded_names = {}
|
46
|
-
@loaded_contents = {}
|
47
40
|
|
48
41
|
# Register built-in classes.
|
49
42
|
register_class PolarBoolean, name: 'Boolean'
|
@@ -54,11 +47,12 @@ module Oso
|
|
54
47
|
register_class String
|
55
48
|
end
|
56
49
|
|
57
|
-
#
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
50
|
+
# Clear all rules and rule sources from the current Polar instance
|
51
|
+
#
|
52
|
+
# @return [self] for chaining.
|
53
|
+
def clear_rules
|
54
|
+
ffi_polar.clear_rules
|
55
|
+
self
|
62
56
|
end
|
63
57
|
|
64
58
|
# Load a Polar policy file.
|
@@ -66,23 +60,12 @@ module Oso
|
|
66
60
|
# @param name [String]
|
67
61
|
# @raise [PolarFileExtensionError] if provided filename has invalid extension.
|
68
62
|
# @raise [PolarFileNotFoundError] if provided filename does not exist.
|
69
|
-
|
63
|
+
# @return [self] for chaining.
|
64
|
+
def load_file(name)
|
70
65
|
raise PolarFileExtensionError, name unless File.extname(name) == '.polar'
|
71
66
|
|
72
67
|
file_data = File.open(name, &:read)
|
73
|
-
|
74
|
-
|
75
|
-
if loaded_names.key?(name)
|
76
|
-
raise PolarFileAlreadyLoadedError, name if loaded_names[name] == hash
|
77
|
-
|
78
|
-
raise PolarFileContentsChangedError, name
|
79
|
-
elsif loaded_contents.key?(hash)
|
80
|
-
raise PolarFileNameChangedError, name, loaded_contents[hash]
|
81
|
-
else
|
82
|
-
load_str(file_data, filename: name)
|
83
|
-
loaded_names[name] = hash
|
84
|
-
loaded_contents[hash] = name
|
85
|
-
end
|
68
|
+
load_str(file_data, filename: name)
|
86
69
|
rescue Errno::ENOENT
|
87
70
|
raise PolarFileNotFoundError, name
|
88
71
|
end
|
@@ -94,10 +77,11 @@ module Oso
|
|
94
77
|
# @raise [NullByteInPolarFileError] if str includes a non-terminating null byte.
|
95
78
|
# @raise [InlineQueryFailedError] on the first failed inline query.
|
96
79
|
# @raise [Error] if any of the FFI calls raise one.
|
80
|
+
# @return [self] for chaining.
|
97
81
|
def load_str(str, filename: nil) # rubocop:disable Metrics/MethodLength
|
98
82
|
raise NullByteInPolarFileError if str.chomp("\0").include?("\0")
|
99
83
|
|
100
|
-
ffi_polar.
|
84
|
+
ffi_polar.load(str, filename: filename)
|
101
85
|
loop do
|
102
86
|
next_query = ffi_polar.next_inline_query
|
103
87
|
break if next_query.nil?
|
@@ -105,9 +89,10 @@ module Oso
|
|
105
89
|
begin
|
106
90
|
Query.new(next_query, host: host).results.next
|
107
91
|
rescue StopIteration
|
108
|
-
raise InlineQueryFailedError
|
92
|
+
raise InlineQueryFailedError, next_query.source
|
109
93
|
end
|
110
94
|
end
|
95
|
+
self
|
111
96
|
end
|
112
97
|
|
113
98
|
# Query for a Polar predicate or string.
|
@@ -126,7 +111,7 @@ module Oso
|
|
126
111
|
when String
|
127
112
|
ffi_query = ffi_polar.new_query_from_str(query)
|
128
113
|
when Predicate
|
129
|
-
ffi_query = ffi_polar.new_query_from_term(new_host.
|
114
|
+
ffi_query = ffi_polar.new_query_from_term(new_host.to_polar(query))
|
130
115
|
else
|
131
116
|
raise InvalidQueryTypeError
|
132
117
|
end
|
@@ -145,18 +130,26 @@ module Oso
|
|
145
130
|
|
146
131
|
# Register a Ruby class with Polar.
|
147
132
|
#
|
148
|
-
# @param cls [Class]
|
149
|
-
# @param name [String]
|
150
|
-
# @
|
151
|
-
#
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
133
|
+
# @param cls [Class] the class to register.
|
134
|
+
# @param name [String] the name to register the class as. Defaults to the name of the class.
|
135
|
+
# @raise [DuplicateClassAliasError] if attempting to register a class
|
136
|
+
# under a previously-registered name.
|
137
|
+
# @raise [FFI::Error] if the FFI call returns an error.
|
138
|
+
# @return [self] for chaining.
|
139
|
+
def register_class(cls, name: nil)
|
140
|
+
name = host.cache_class(cls, name: name || cls.name)
|
141
|
+
register_constant(cls, name: name)
|
156
142
|
end
|
157
143
|
|
158
|
-
|
159
|
-
|
144
|
+
# Register a Ruby object with Polar.
|
145
|
+
#
|
146
|
+
# @param value [Object] the object to register.
|
147
|
+
# @param name [String] the name to register the object as.
|
148
|
+
# @return [self] for chaining.
|
149
|
+
# @raise [FFI::Error] if the FFI call returns an error.
|
150
|
+
def register_constant(value, name:)
|
151
|
+
ffi_polar.register_constant(host.to_polar(value), name: name)
|
152
|
+
self
|
160
153
|
end
|
161
154
|
|
162
155
|
# Start a REPL session.
|
@@ -178,10 +171,6 @@ module Oso
|
|
178
171
|
|
179
172
|
# @return [FFI::Polar]
|
180
173
|
attr_reader :ffi_polar
|
181
|
-
# @return [Hash<String, String>]
|
182
|
-
attr_reader :loaded_names
|
183
|
-
# @return [Hash<String, String>]
|
184
|
-
attr_reader :loaded_contents
|
185
174
|
|
186
175
|
# The R and L in REPL for systems where readline is available.
|
187
176
|
def repl_readline(prompt)
|
@@ -236,7 +225,7 @@ module Oso
|
|
236
225
|
puts true
|
237
226
|
else
|
238
227
|
result.each do |variable, value|
|
239
|
-
puts "#{variable}
|
228
|
+
puts "#{variable} = #{value.inspect}"
|
240
229
|
end
|
241
230
|
end
|
242
231
|
end
|
data/lib/oso/polar/query.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'json'
|
4
|
+
|
3
5
|
module Oso
|
4
6
|
module Polar
|
5
7
|
# A single Polar query.
|
@@ -43,16 +45,18 @@ module Oso
|
|
43
45
|
# @param args [Array<Hash>]
|
44
46
|
# @raise [InvalidCallError] if the method doesn't exist on the instance or
|
45
47
|
# the args passed to the method are invalid.
|
46
|
-
def register_call(attribute, call_id:, instance:, args:) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
48
|
+
def register_call(attribute, call_id:, instance:, args:, kwargs:) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
47
49
|
return if calls.key?(call_id)
|
48
50
|
|
49
51
|
instance = host.to_ruby(instance)
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
52
|
+
args = args.map { |a| host.to_ruby(a) }
|
53
|
+
kwargs = Hash[kwargs.map { |k, v| [k.to_sym, host.to_ruby(v)] }]
|
54
|
+
# The kwargs.empty? check is for Ruby < 2.7.
|
55
|
+
result = if kwargs.empty?
|
56
|
+
instance.__send__(attribute, *args)
|
57
|
+
else
|
58
|
+
instance.__send__(attribute, *args, **kwargs)
|
59
|
+
end
|
56
60
|
result = [result].to_enum unless result.is_a? Enumerator # Call must be a generator.
|
57
61
|
calls[call_id] = result.lazy
|
58
62
|
rescue ArgumentError, NoMethodError
|
@@ -68,13 +72,13 @@ module Oso
|
|
68
72
|
ffi_query.call_result(result, call_id: call_id)
|
69
73
|
end
|
70
74
|
|
71
|
-
# Retrieve the next result from a registered call and pass it to {#
|
75
|
+
# Retrieve the next result from a registered call and pass it to {#to_polar}.
|
72
76
|
#
|
73
77
|
# @param id [Integer]
|
74
78
|
# @return [Hash]
|
75
79
|
# @raise [StopIteration] if the call has been exhausted.
|
76
80
|
def next_call_result(id)
|
77
|
-
host.
|
81
|
+
host.to_polar(calls[id].next)
|
78
82
|
end
|
79
83
|
|
80
84
|
# Send application error across FFI boundary.
|
@@ -93,8 +97,8 @@ module Oso
|
|
93
97
|
# @param call_id [Integer]
|
94
98
|
# @param instance [Hash<String, Object>]
|
95
99
|
# @raise [Error] if the FFI call raises one.
|
96
|
-
def handle_call(attribute, call_id:, instance:, args:)
|
97
|
-
register_call(attribute, call_id: call_id, instance: instance, args: args)
|
100
|
+
def handle_call(attribute, call_id:, instance:, args:, kwargs:)
|
101
|
+
register_call(attribute, call_id: call_id, instance: instance, args: args, kwargs: kwargs)
|
98
102
|
result = JSON.dump(next_call_result(call_id))
|
99
103
|
call_result(result, call_id: call_id)
|
100
104
|
rescue InvalidCallError => e
|
@@ -104,6 +108,20 @@ module Oso
|
|
104
108
|
call_result(nil, call_id: call_id)
|
105
109
|
end
|
106
110
|
|
111
|
+
def handle_make_external(data) # rubocop:disable Metrics/AbcSize
|
112
|
+
id = data['instance_id']
|
113
|
+
raise DuplicateInstanceRegistrationError, id if host.instance? id
|
114
|
+
|
115
|
+
constructor = data['constructor']['value']
|
116
|
+
raise InvalidConstructorError unless constructor.key? 'Call'
|
117
|
+
|
118
|
+
cls_name = constructor['Call']['name']
|
119
|
+
args = constructor['Call']['args'].map { |arg| host.to_ruby(arg) }
|
120
|
+
kwargs = constructor['Call']['kwargs'] || {}
|
121
|
+
kwargs = Hash[kwargs.map { |k, v| [k.to_sym, host.to_ruby(v)] }]
|
122
|
+
host.make_instance(cls_name, args: args, kwargs: kwargs, id: id)
|
123
|
+
end
|
124
|
+
|
107
125
|
# Create a generator that can be polled to advance the query loop.
|
108
126
|
#
|
109
127
|
# @yieldparam [Hash<String, Object>]
|
@@ -119,27 +137,14 @@ module Oso
|
|
119
137
|
when 'Result'
|
120
138
|
yielder << event.data['bindings'].transform_values { |v| host.to_ruby(v) }
|
121
139
|
when 'MakeExternal'
|
122
|
-
|
123
|
-
raise DuplicateInstanceRegistrationError, id if host.instance? id
|
124
|
-
|
125
|
-
constructor = event.data['constructor']['value']
|
126
|
-
if constructor.key? 'InstanceLiteral'
|
127
|
-
cls_name = constructor['InstanceLiteral']['tag']
|
128
|
-
fields = constructor['InstanceLiteral']['fields']['fields']
|
129
|
-
initargs = Hash[fields.map { |k, v| [k.to_sym, host.to_ruby(v)] }]
|
130
|
-
elsif constructor.key? 'Call'
|
131
|
-
cls_name = constructor['Call']['name']
|
132
|
-
initargs = constructor['Call']['args'].map { |arg| host.to_ruby(arg) }
|
133
|
-
else
|
134
|
-
raise InvalidConstructorError
|
135
|
-
end
|
136
|
-
host.make_instance(cls_name, initargs: initargs, id: id)
|
140
|
+
handle_make_external(event.data)
|
137
141
|
when 'ExternalCall'
|
138
142
|
call_id = event.data['call_id']
|
139
143
|
instance = event.data['instance']
|
140
144
|
attribute = event.data['attribute']
|
141
|
-
args = event.data['args']
|
142
|
-
|
145
|
+
args = event.data['args'] || []
|
146
|
+
kwargs = event.data['kwargs'] || {}
|
147
|
+
handle_call(attribute, call_id: call_id, instance: instance, args: args, kwargs: kwargs)
|
143
148
|
when 'ExternalIsSubSpecializer'
|
144
149
|
instance_id = event.data['instance_id']
|
145
150
|
left_tag = event.data['left_class_tag']
|
@@ -164,8 +169,10 @@ module Oso
|
|
164
169
|
rescue EOFError
|
165
170
|
next
|
166
171
|
end
|
167
|
-
command = JSON.dump(host.
|
172
|
+
command = JSON.dump(host.to_polar(input))
|
168
173
|
ffi_query.debug_command(command)
|
174
|
+
when 'ExternalOp'
|
175
|
+
raise UnimplementedOperationError, 'comparison operators'
|
169
176
|
else
|
170
177
|
raise "Unhandled event: #{JSON.dump(event.inspect)}"
|
171
178
|
end
|
data/lib/oso/version.rb
CHANGED
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.7.1
|
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: 2020-
|
11
|
+
date: 2020-10-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ffi
|
@@ -141,6 +141,7 @@ files:
|
|
141
141
|
- lib/oso/polar/ffi/polar.rb
|
142
142
|
- lib/oso/polar/ffi/query.rb
|
143
143
|
- lib/oso/polar/ffi/query_event.rb
|
144
|
+
- lib/oso/polar/ffi/source.rb
|
144
145
|
- lib/oso/polar/host.rb
|
145
146
|
- lib/oso/polar/polar.rb
|
146
147
|
- lib/oso/polar/predicate.rb
|