oso-oso 0.3.0 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 946cb3358c69be79685eefbc291b3d0dea8cd04d
4
- data.tar.gz: c9877050c9d34d31875bf1d3e55c7218b336833a
3
+ metadata.gz: eb279f6dd2c9ea6d862b47f1e6cc109bab429d19
4
+ data.tar.gz: 8c998ace146905bfb32574fe7d233fa0cb69e240
5
5
  SHA512:
6
- metadata.gz: 3cdf0a2de23b6fbcf36655e4c432ae3146c2f05e016d2560285efbb85eee840aa155c53a95fe16997e074232cea7b0e04fd7152b71319c55182084940620b95f
7
- data.tar.gz: 506ca24f5b9ff1c62f69defafb963ac16f9871715f302512dd4791d388495a36cf1dbc8b15a8b8a0e530fad836dc7a5cbba817e0c9b467faa47b7a230f4861b3
6
+ metadata.gz: 1bd787012da6991d59d14877111b4022bc8855d16e3556faf859eaa1e09dcb41cb0e6d91d6d7e52faba7cf2c0a8b7993a784553221afcb6fa9dc7633ea40ec1b
7
+ data.tar.gz: c26f0285df44ac39e6f10717f0ca5143cf32ca1193133ce1a977885688fe72f0665de74d0c13cbc0d3ab71f4f85aa6fd39719b0872ebc4e738c25aa798fd6887
@@ -0,0 +1,7 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.4
3
+ Exclude:
4
+ - '**/*~'
5
+ - 'bin/oso'
6
+ - 'vendor/**/*'
7
+ NewCops: enable
data/Gemfile CHANGED
@@ -3,4 +3,3 @@
3
3
  source 'https://rubygems.org'
4
4
 
5
5
  gemspec
6
-
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- oso-oso (0.3.0)
4
+ oso-oso (0.5.2)
5
5
  ffi (~> 1.0)
6
6
 
7
7
  GEM
@@ -12,18 +12,18 @@ GEM
12
12
  benchmark (0.1.0)
13
13
  byebug (11.1.3)
14
14
  coderay (1.1.3)
15
- diff-lcs (1.3)
15
+ diff-lcs (1.4.4)
16
16
  e2mmap (0.1.0)
17
17
  ffi (1.13.1)
18
18
  jaro_winkler (1.5.4)
19
19
  maruku (0.7.3)
20
20
  method_source (1.0.0)
21
21
  mini_portile2 (2.4.0)
22
- nokogiri (1.10.9)
22
+ nokogiri (1.10.10)
23
23
  mini_portile2 (~> 2.4.0)
24
- parallel (1.19.1)
25
- parser (2.7.1.3)
26
- ast (~> 2.4.0)
24
+ parallel (1.19.2)
25
+ parser (2.7.1.4)
26
+ ast (~> 2.4.1)
27
27
  pry (0.13.1)
28
28
  coderay (~> 1.1)
29
29
  method_source (~> 1.0)
@@ -49,19 +49,19 @@ GEM
49
49
  diff-lcs (>= 1.2.0, < 2.0)
50
50
  rspec-support (~> 3.9.0)
51
51
  rspec-support (3.9.3)
52
- rubocop (0.85.1)
52
+ rubocop (0.89.1)
53
53
  parallel (~> 1.10)
54
- parser (>= 2.7.0.1)
54
+ parser (>= 2.7.1.1)
55
55
  rainbow (>= 2.2.2, < 4.0)
56
56
  regexp_parser (>= 1.7)
57
57
  rexml
58
- rubocop-ast (>= 0.0.3)
58
+ rubocop-ast (>= 0.3.0, < 1.0)
59
59
  ruby-progressbar (~> 1.7)
60
60
  unicode-display_width (>= 1.4.0, < 2.0)
61
- rubocop-ast (0.0.3)
62
- parser (>= 2.7.0.1)
61
+ rubocop-ast (0.3.0)
62
+ parser (>= 2.7.1.4)
63
63
  ruby-progressbar (1.10.1)
64
- solargraph (0.39.8)
64
+ solargraph (0.39.14)
65
65
  backport (~> 1.1)
66
66
  benchmark
67
67
  bundler (>= 1.17.2)
@@ -88,7 +88,8 @@ DEPENDENCIES
88
88
  pry-byebug (~> 3.9.0)
89
89
  rake (~> 12.0)
90
90
  rspec (~> 3.0)
91
- solargraph (~> 0.39.8)
91
+ rubocop (~> 0.89.1)
92
+ solargraph (~> 0.39.14)
92
93
  yard (~> 0.9.25)
93
94
 
94
95
  BUNDLED WITH
data/Makefile CHANGED
@@ -1,13 +1,19 @@
1
- .PHONY: install rust test
1
+ .PHONY: rust install test lint typecheck repl
2
2
 
3
3
  rust:
4
4
  $(MAKE) -C ../.. rust-build
5
5
 
6
- install: rust
6
+ install:
7
7
  bundle install
8
8
 
9
- test: install
9
+ test: install rust
10
10
  bundle exec rake spec
11
11
 
12
- repl:
12
+ lint: install
13
+ bundle exec rubocop
14
+
15
+ typecheck: install
16
+ bundle exec solargraph typecheck
17
+
18
+ repl: install rust
13
19
  bundle exec oso
data/Rakefile CHANGED
@@ -6,4 +6,3 @@ require 'rspec/core/rake_task'
6
6
  RSpec::Core::RakeTask.new(:spec)
7
7
 
8
8
  task default: :spec
9
-
data/bin/oso CHANGED
@@ -4,4 +4,4 @@
4
4
  require 'bundler/setup'
5
5
  require 'oso'
6
6
 
7
- Oso.new.repl(load: true)
7
+ Oso.new.repl(ARGV)
Binary file
Binary file
@@ -3,7 +3,7 @@
3
3
  module Oso
4
4
  # An HTTP resource.
5
5
  class Http
6
- def initialize(hostname: nil, path: nil, query: nil)
6
+ def initialize(hostname, path, query)
7
7
  @hostname = hostname
8
8
  @path = path
9
9
  @query = query
@@ -3,7 +3,7 @@
3
3
  require_relative 'polar/polar'
4
4
 
5
5
  module Oso
6
- # Oso authorization API.
6
+ # oso authorization API.
7
7
  class Oso < Polar::Polar
8
8
  def initialize
9
9
  super
@@ -11,6 +11,13 @@ module Oso
11
11
  register_class(PathMapper, name: 'PathMapper')
12
12
  end
13
13
 
14
+ # Query the knowledge base to determine whether an actor is allowed to
15
+ # perform an action upon a resource.
16
+ #
17
+ # @param actor [Object] Subject.
18
+ # @param action [Object] Verb.
19
+ # @param resource [Object] Object.
20
+ # @return [Boolean] An access control decision.
14
21
  def allowed?(actor:, action:, resource:)
15
22
  query_rule('allow', actor, action, resource).next
16
23
  true
@@ -4,7 +4,7 @@ module Oso
4
4
  # Map from a template string with capture groups of the form
5
5
  # `{name}` to a dictionary of the form `{name: captured_value}`
6
6
  class PathMapper
7
- def initialize(template:)
7
+ def initialize(template)
8
8
  capture_group = /({([^}]+)})/
9
9
 
10
10
  template = template.dup
@@ -5,16 +5,12 @@ module Oso
5
5
  # Base error type for Oso::Polar.
6
6
  class Error < ::RuntimeError
7
7
  attr_reader :stack_trace
8
-
8
+
9
9
  # @param message [String]
10
10
  # @param details [Hash]
11
11
  def initialize(message = nil, details: nil)
12
12
  @details = details
13
- if details and details.key?("stack_trace")
14
- @stack_trace = details['stack_trace']
15
- else
16
- @stack_trace = nil
17
- end
13
+ @stack_trace = details&.fetch('stack_trace', nil)
18
14
  super(message)
19
15
  end
20
16
  end
@@ -31,6 +27,7 @@ module Oso
31
27
  class UnsupportedError < PolarRuntimeError; end
32
28
  class PolarTypeError < PolarRuntimeError; end
33
29
  class StackOverflowError < PolarRuntimeError; end
30
+ class FileLoadingError < PolarRuntimeError; end
34
31
 
35
32
  # Errors originating from this side of the FFI boundary.
36
33
 
@@ -41,12 +38,17 @@ module Oso
41
38
  class InvalidCallError < PolarRuntimeError; end
42
39
  class InvalidConstructorError < PolarRuntimeError; end
43
40
  class InvalidQueryTypeError < PolarRuntimeError; end
44
- class InlineQueryFailedError < PolarRuntimeError; end
45
41
  class NullByteInPolarFileError < PolarRuntimeError; end
46
42
  class UnexpectedPolarTypeError < PolarRuntimeError; end
43
+ class InlineQueryFailedError < PolarRuntimeError; # rubocop:disable Style/Documentation
44
+ # @param source [String]
45
+ def initialize(source)
46
+ super("Inline query failed: #{source}")
47
+ end
48
+ end
47
49
  class PolarFileExtensionError < PolarRuntimeError # rubocop:disable Style/Documentation
48
- def initialize
49
- super('Polar files must have .pol or .polar extension.')
50
+ def initialize(file)
51
+ super("Polar files must have .polar extension. Offending file: #{file}")
50
52
  end
51
53
  end
52
54
  class PolarFileNotFoundError < PolarRuntimeError # rubocop:disable Style/Documentation
@@ -59,7 +61,7 @@ module Oso
59
61
  # @param as [String]
60
62
  # @param old [Class]
61
63
  # @param new [Class]
62
- def initialize(name:, old:, new:) # rubocop:disable Naming/MethodParameterName
64
+ def initialize(name:, old:, new:)
63
65
  super("Attempted to alias #{new} as '#{name}', but #{old} already has that alias.")
64
66
  end
65
67
  end
@@ -5,7 +5,7 @@ require 'ffi'
5
5
  module Oso
6
6
  module Polar
7
7
  module FFI
8
- LIB = ::FFI::Platform::LIBPREFIX + 'polar.' + ::FFI::Platform::LIBSUFFIX
8
+ LIB = "#{::FFI::Platform::LIBPREFIX}polar.#{::FFI::Platform::LIBSUFFIX}"
9
9
  RELEASE_PATH = File.expand_path(File.join(__dir__, "../../../ext/oso-oso/lib/#{LIB}"))
10
10
  DEV_PATH = File.expand_path(File.join(__dir__, "../../../../../target/debug/#{LIB}"))
11
11
  # If the lib exists in the ext/ dir, use it. Otherwise, fall back to
@@ -37,7 +37,20 @@ module Oso
37
37
  # Wrapper class for Error FFI pointer + operations.
38
38
  class Error < ::FFI::AutoPointer
39
39
  def self.release(ptr)
40
- Rust.free(ptr)
40
+ Rust.free(ptr) unless ptr.null?
41
+ end
42
+ end
43
+ # Wrapper class for Message FFI pointer + operations.
44
+ class Message < ::FFI::AutoPointer
45
+ def self.release(ptr)
46
+ Rust.free(ptr) unless ptr.null?
47
+ end
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?
41
54
  end
42
55
  end
43
56
  end
@@ -49,3 +62,5 @@ require 'oso/polar/ffi/polar'
49
62
  require 'oso/polar/ffi/query'
50
63
  require 'oso/polar/ffi/query_event'
51
64
  require 'oso/polar/ffi/error'
65
+ require 'oso/polar/ffi/message'
66
+ require 'oso/polar/ffi/source'
@@ -22,7 +22,7 @@ module Oso
22
22
  #
23
23
  # @return [::Oso::Polar::Error] if there's an FFI error.
24
24
  # @return [::Oso::Polar::FFIErrorNotFound] if there isn't one.
25
- def self.get # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
25
+ def self.get # rubocop:disable Metrics/MethodLength
26
26
  error = Rust.get
27
27
  return ::Oso::Polar::FFIErrorNotFound if error.null?
28
28
 
@@ -48,7 +48,7 @@ module Oso
48
48
  # @param msg [String]
49
49
  # @param details [Hash<String, Object>]
50
50
  # @return [::Oso::Polar::ParseError] the object converted into the expected format.
51
- private_class_method def self.parse_error(kind, msg:, details:) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
51
+ private_class_method def self.parse_error(kind, msg:, details:) # rubocop:disable Metrics/MethodLength
52
52
  case kind
53
53
  when 'ExtraToken'
54
54
  ::Oso::Polar::ParseError::ExtraToken.new(msg, details: details)
@@ -83,6 +83,8 @@ module Oso
83
83
  ::Oso::Polar::PolarTypeError.new(msg, details: details)
84
84
  when 'StackOverflow'
85
85
  ::Oso::Polar::StackOverflowError.new(msg, details: details)
86
+ when 'FileLoading'
87
+ ::Oso::Polar::FileLoadingError.new(msg, details: details)
86
88
  else
87
89
  ::Oso::Polar::PolarRuntimeError.new(msg, details: details)
88
90
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Oso
4
+ module Polar
5
+ module FFI
6
+ # Wrapper class for Message FFI pointer + operations.
7
+ class Message < ::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
+ def process
21
+ message = JSON.parse(to_s)
22
+ kind = message['kind']
23
+ msg = message['msg']
24
+
25
+ case kind
26
+ when 'Print'
27
+ puts(msg)
28
+ when 'Warning'
29
+ warn(format('[warning] %<msg>s', msg: msg))
30
+ end
31
+ end
32
+
33
+ private_constant :Rust
34
+ end
35
+ end
36
+ end
37
+ end
@@ -10,12 +10,13 @@ module Oso
10
10
  ffi_lib FFI::LIB_PATH
11
11
 
12
12
  attach_function :new, :polar_new, [], FFI::Polar
13
- attach_function :load_str, :polar_load, [FFI::Polar, :string, :string], :int32
14
- attach_function :next_inline_query, :polar_next_inline_query, [FFI::Polar], FFI::Query
13
+ attach_function :load, :polar_load, [FFI::Polar, :string, :string], :int32
14
+ attach_function :next_inline_query, :polar_next_inline_query, [FFI::Polar, :uint32], FFI::Query
15
15
  attach_function :new_id, :polar_get_external_id, [FFI::Polar], :uint64
16
- attach_function :new_query_from_str, :polar_new_query, [FFI::Polar, :string], FFI::Query
17
- attach_function :new_query_from_term, :polar_new_query_from_term, [FFI::Polar, :string], FFI::Query
16
+ attach_function :new_query_from_str, :polar_new_query, [FFI::Polar, :string, :uint32], FFI::Query
17
+ attach_function :new_query_from_term, :polar_new_query_from_term, [FFI::Polar, :string, :uint32], FFI::Query
18
18
  attach_function :register_constant, :polar_register_constant, [FFI::Polar, :string, :string], :int32
19
+ attach_function :next_message, :polar_next_polar_message, [FFI::Polar], FFI::Message
19
20
  attach_function :free, :polar_free, [FFI::Polar], :int32
20
21
  end
21
22
  private_constant :Rust
@@ -32,15 +33,18 @@ module Oso
32
33
  # @param src [String]
33
34
  # @param filename [String]
34
35
  # @raise [FFI::Error] if the FFI call returns an error.
35
- def load_str(src, filename: nil)
36
- raise FFI::Error.get if Rust.load_str(self, src, filename).zero?
36
+ def load(src, filename: nil)
37
+ loaded = Rust.load(self, src, filename)
38
+ process_messages
39
+ raise FFI::Error.get if loaded.zero?
37
40
  end
38
41
 
39
42
  # @return [FFI::Query] if there are remaining inline queries.
40
43
  # @return [nil] if there are no remaining inline queries.
41
44
  # @raise [FFI::Error] if the FFI call returns an error.
42
45
  def next_inline_query
43
- query = Rust.next_inline_query(self)
46
+ query = Rust.next_inline_query(self, 0)
47
+ process_messages
44
48
  query.null? ? nil : query
45
49
  end
46
50
 
@@ -59,7 +63,8 @@ module Oso
59
63
  # @return [FFI::Query]
60
64
  # @raise [FFI::Error] if the FFI call returns an error.
61
65
  def new_query_from_str(str)
62
- query = Rust.new_query_from_str(self, str)
66
+ query = Rust.new_query_from_str(self, str, 0)
67
+ process_messages
63
68
  raise FFI::Error.get if query.null?
64
69
 
65
70
  query
@@ -69,7 +74,8 @@ module Oso
69
74
  # @return [FFI::Query]
70
75
  # @raise [FFI::Error] if the FFI call returns an error.
71
76
  def new_query_from_term(term)
72
- query = Rust.new_query_from_term(self, JSON.dump(term))
77
+ query = Rust.new_query_from_term(self, JSON.dump(term), 0)
78
+ process_messages
73
79
  raise FFI::Error.get if query.null?
74
80
 
75
81
  query
@@ -79,7 +85,21 @@ module Oso
79
85
  # @param value [Hash<String, Object>]
80
86
  # @raise [FFI::Error] if the FFI call returns an error.
81
87
  def register_constant(name, value:)
82
- raise FFI::Error.get if Rust.register_constant(self, name, JSON.dump(value)).zero?
88
+ registered = Rust.register_constant(self, name, JSON.dump(value))
89
+ raise FFI::Error.get if registered.zero?
90
+ end
91
+
92
+ def next_message
93
+ Rust.next_message(self)
94
+ end
95
+
96
+ def process_messages
97
+ loop do
98
+ message = next_message
99
+ break if message.null?
100
+
101
+ message.process
102
+ end
83
103
  end
84
104
  end
85
105
  end
@@ -14,6 +14,8 @@ module Oso
14
14
  attach_function :question_result, :polar_question_result, [FFI::Query, :uint64, :int32], :int32
15
15
  attach_function :application_error, :polar_application_error, [FFI::Query, :string], :int32
16
16
  attach_function :next_event, :polar_next_query_event, [FFI::Query], FFI::QueryEvent
17
+ attach_function :next_message, :polar_next_query_message, [FFI::Query], FFI::Message
18
+ attach_function :source, :polar_query_source_info, [FFI::Query], FFI::Source
17
19
  attach_function :free, :query_free, [FFI::Query], :int32
18
20
  end
19
21
  private_constant :Rust
@@ -22,6 +24,7 @@ module Oso
22
24
  # @raise [FFI::Error] if the FFI call returns an error.
23
25
  def debug_command(cmd)
24
26
  res = Rust.debug_command(self, cmd)
27
+ process_messages
25
28
  raise FFI::Error.get if res.zero?
26
29
  end
27
30
 
@@ -54,10 +57,33 @@ module Oso
54
57
  # @raise [FFI::Error] if the FFI call returns an error.
55
58
  def next_event
56
59
  event = Rust.next_event(self)
60
+ process_messages
57
61
  raise FFI::Error.get if event.null?
58
62
 
59
63
  ::Oso::Polar::QueryEvent.new(JSON.parse(event.to_s))
60
64
  end
65
+
66
+ def next_message
67
+ Rust.next_message(self)
68
+ end
69
+
70
+ def process_messages
71
+ loop do
72
+ message = next_message
73
+ break if message.null?
74
+
75
+ message.process
76
+ end
77
+ end
78
+
79
+ # @return [String]
80
+ # @raise [FFI::Error] if the FFI call returns an error.
81
+ def source
82
+ res = Rust.source(self)
83
+ raise FFI::Error.get if res.null?
84
+
85
+ res.to_s
86
+ end
61
87
  end
62
88
  end
63
89
  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
@@ -3,8 +3,9 @@
3
3
  module Oso
4
4
  module Polar
5
5
  # Translate between Polar and the host language (Ruby).
6
- class Host
6
+ class Host # rubocop:disable Metrics/ClassLength
7
7
  protected
8
+
8
9
  # @return [FFI::Polar]
9
10
  attr_reader :ffi_polar
10
11
  # @return [Hash<String, Class>]
@@ -15,6 +16,7 @@ module Oso
15
16
  attr_reader :instances
16
17
 
17
18
  public
19
+
18
20
  def initialize(ffi_polar)
19
21
  @ffi_polar = ffi_polar
20
22
  @classes = {}
@@ -44,9 +46,10 @@ module Oso
44
46
  #
45
47
  # @param cls [Class] the class to cache
46
48
  # @param name [String] the name to cache the class as. Defaults to the name of the class.
49
+ # @param constructor [Proc] optional custom constructor function. Defaults to the :new method.
47
50
  # @return [String] the name the class is cached as.
48
51
  # @raise [UnregisteredClassError] if the class has not been registered.
49
- def cache_class(cls, name:, constructor:)
52
+ def cache_class(cls, name:, constructor:) # rubocop:disable Metrics/MethodLength
50
53
  name = cls.name if name.nil?
51
54
  raise DuplicateClassAliasError, name: name, old: get_class(name), new: cls if classes.key? name
52
55
 
@@ -73,7 +76,7 @@ module Oso
73
76
  constructors[name]
74
77
  end
75
78
 
76
- # Check if an instance has been cached.
79
+ # Check if an instance exists in the {#instances} cache.
77
80
  #
78
81
  # @param id [Integer]
79
82
  # @return [Boolean]
@@ -97,7 +100,8 @@ module Oso
97
100
  instances[id]
98
101
  end
99
102
 
100
- # Cache a Ruby instance, fetching a {#new_id} if one isn't provided.
103
+ # Cache a Ruby instance in the {#instances} cache, fetching a {#new_id}
104
+ # if one isn't provided.
101
105
  #
102
106
  # @param instance [Object]
103
107
  # @param id [Integer]
@@ -111,22 +115,22 @@ module Oso
111
115
  # Construct and cache a Ruby instance.
112
116
  #
113
117
  # @param cls_name [String]
114
- # @param fields [Hash<String, Hash>]
118
+ # @param args [Array<Object>]
119
+ # @param kwargs [Hash<String, Object>]
115
120
  # @param id [Integer]
116
121
  # @raise [PolarRuntimeError] if instance construction fails.
117
- def make_instance(cls_name, fields:, id:) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
122
+ def make_instance(cls_name, args:, kwargs:, id:) # rubocop:disable Metrics/MethodLength
118
123
  constructor = get_constructor(cls_name)
119
- fields = Hash[fields.map { |k, v| [k.to_sym, to_ruby(v)] }]
120
124
  instance = if constructor == :new
121
- if fields.empty?
122
- get_class(cls_name).__send__(:new)
125
+ if kwargs.empty?
126
+ get_class(cls_name).__send__(:new, *args)
123
127
  else
124
- get_class(cls_name).__send__(:new, **fields)
128
+ get_class(cls_name).__send__(:new, *args, **kwargs)
125
129
  end
126
- elsif fields.empty?
127
- constructor.call
130
+ elsif kwargs.empty?
131
+ constructor.call(*args)
128
132
  else
129
- constructor.call(**fields)
133
+ constructor.call(*args, **kwargs)
130
134
  end
131
135
  cache_instance(instance, id: id)
132
136
  rescue StandardError => e
@@ -149,11 +153,11 @@ module Oso
149
153
 
150
154
  # Check if instance is an instance of class.
151
155
  #
152
- # @param instance_id [Integer]
156
+ # @param instance [Hash<String, Object>]
153
157
  # @param class_tag [String]
154
158
  # @return [Boolean]
155
- def isa?(instance_id, class_tag:)
156
- instance = get_instance(instance_id)
159
+ def isa?(instance, class_tag:)
160
+ instance = to_ruby(instance)
157
161
  cls = get_class(class_tag)
158
162
  instance.is_a? cls
159
163
  rescue PolarRuntimeError
@@ -178,22 +182,29 @@ module Oso
178
182
  #
179
183
  # @param value [Object]
180
184
  # @return [Hash<String, Object>]
181
- def to_polar_term(value) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
185
+ def to_polar(value) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
182
186
  value = case true # rubocop:disable Lint/LiteralAsCondition
183
187
  when value.instance_of?(TrueClass) || value.instance_of?(FalseClass)
184
188
  { 'Boolean' => value }
185
189
  when value.instance_of?(Integer)
186
190
  { 'Number' => { 'Integer' => value } }
187
191
  when value.instance_of?(Float)
192
+ if value == Float::INFINITY
193
+ value = 'Infinity'
194
+ elsif value == -Float::INFINITY
195
+ value = '-Infinity'
196
+ elsif value.nan?
197
+ value = 'NaN'
198
+ end
188
199
  { 'Number' => { 'Float' => value } }
189
200
  when value.instance_of?(String)
190
201
  { 'String' => value }
191
202
  when value.instance_of?(Array)
192
- { 'List' => value.map { |el| to_polar_term(el) } }
203
+ { 'List' => value.map { |el| to_polar(el) } }
193
204
  when value.instance_of?(Hash)
194
- { 'Dictionary' => { 'fields' => value.transform_values { |v| to_polar_term(v) } } }
205
+ { 'Dictionary' => { 'fields' => value.transform_values { |v| to_polar(v) } } }
195
206
  when value.instance_of?(Predicate)
196
- { 'Call' => { 'name' => value.name, 'args' => value.args.map { |el| to_polar_term(el) } } }
207
+ { 'Call' => { 'name' => value.name, 'args' => value.args.map { |el| to_polar(el) } } }
197
208
  when value.instance_of?(Variable)
198
209
  # This is supported so that we can query for unbound variables
199
210
  { 'Variable' => value }
@@ -211,13 +222,28 @@ module Oso
211
222
  # @option data [Hash<String, Object>] :value
212
223
  # @return [Object]
213
224
  # @raise [UnexpectedPolarTypeError] if type cannot be converted to Ruby.
214
- def to_ruby(data) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
225
+ def to_ruby(data) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
215
226
  tag, value = data['value'].first
216
227
  case tag
217
228
  when 'String', 'Boolean'
218
229
  value
219
230
  when 'Number'
220
- value.values.first
231
+ num = value.values.first
232
+ if value.key? 'Float'
233
+ case num
234
+ when 'Infinity'
235
+ return Float::INFINITY
236
+ when '-Infinity'
237
+ return -Float::INFINITY
238
+ when 'NaN'
239
+ return Float::NAN
240
+ else
241
+ unless value['Float'].is_a? Float # rubocop:disable Metrics/BlockNesting
242
+ raise PolarRuntimeError, "Expected a floating point number, got \"#{value['Float']}\""
243
+ end
244
+ end
245
+ end
246
+ num
221
247
  when 'List'
222
248
  value.map { |el| to_ruby(el) }
223
249
  when 'Dictionary'
@@ -3,36 +3,72 @@
3
3
  require 'json'
4
4
  require 'pp'
5
5
  require 'set'
6
+ require 'digest/md5'
7
+
8
+ # Missing Ruby type.
9
+ module PolarBoolean; end
10
+ # Monkey-patch Ruby true type.
11
+ class TrueClass; include PolarBoolean; end
12
+ # Monkey-patch Ruby false type.
13
+ class FalseClass; include PolarBoolean; end
14
+
15
+ # https://github.com/ruby/ruby/blob/bb9ecd026a6cadd5d0f85ac061649216806ed935/lib/bundler/vendor/thor/lib/thor/shell/color.rb#L99-L105
16
+ def supports_color
17
+ $stdout.tty? && $stderr.tty? && ENV['NO_COLOR'].nil?
18
+ end
19
+
20
+ if supports_color
21
+ RESET = "\x1b[0m"
22
+ FG_BLUE = "\x1b[34m"
23
+ FG_RED = "\x1b[31m"
24
+ else
25
+ RESET = ''
26
+ FG_BLUE = ''
27
+ FG_RED = ''
28
+ end
29
+
30
+ def print_error(error)
31
+ warn FG_RED + error.class.name.split('::').last + RESET
32
+ warn error.message
33
+ end
6
34
 
7
35
  module Oso
8
36
  module Polar
9
37
  # Create and manage an instance of the Polar runtime.
10
- class Polar
38
+ class Polar # rubocop:disable Metrics/ClassLength
11
39
  # @return [Host]
12
40
  attr_reader :host
13
41
 
14
42
  def initialize
15
43
  @ffi_polar = FFI::Polar.create
16
44
  @host = Host.new(ffi_polar)
17
- @load_queue = Set.new
45
+
46
+ # Register built-in classes.
47
+ register_class PolarBoolean, name: 'Boolean'
48
+ register_class Integer
49
+ register_class Float
50
+ register_class Array, name: 'List'
51
+ register_class Hash, name: 'Dictionary'
52
+ register_class String
18
53
  end
19
54
 
20
55
  # Replace the current Polar instance but retain all registered classes and constructors.
21
56
  def clear
22
- load_queue.clear
23
57
  @ffi_polar = FFI::Polar.create
24
58
  end
25
59
 
26
- # Enqueue a Polar policy file for loading into the KB.
60
+ # Load a Polar policy file.
27
61
  #
28
62
  # @param name [String]
29
63
  # @raise [PolarFileExtensionError] if provided filename has invalid extension.
30
64
  # @raise [PolarFileNotFoundError] if provided filename does not exist.
31
65
  def load_file(name)
32
- raise PolarFileExtensionError unless ['.pol', '.polar'].include? File.extname(name)
33
- raise PolarFileNotFoundError, name unless File.file?(name)
66
+ raise PolarFileExtensionError, name unless File.extname(name) == '.polar'
34
67
 
35
- load_queue << name
68
+ file_data = File.open(name, &:read)
69
+ load_str(file_data, filename: name)
70
+ rescue Errno::ENOENT
71
+ raise PolarFileNotFoundError, name
36
72
  end
37
73
 
38
74
  # Load a Polar string into the KB.
@@ -45,7 +81,7 @@ module Oso
45
81
  def load_str(str, filename: nil) # rubocop:disable Metrics/MethodLength
46
82
  raise NullByteInPolarFileError if str.chomp("\0").include?("\0")
47
83
 
48
- ffi_polar.load_str(str, filename: filename)
84
+ ffi_polar.load(str, filename: filename)
49
85
  loop do
50
86
  next_query = ffi_polar.next_inline_query
51
87
  break if next_query.nil?
@@ -53,24 +89,28 @@ module Oso
53
89
  begin
54
90
  Query.new(next_query, host: host).results.next
55
91
  rescue StopIteration
56
- raise InlineQueryFailedError
92
+ raise InlineQueryFailedError, next_query.source
57
93
  end
58
94
  end
59
95
  end
60
96
 
61
- # Query for a predicate, parsing it if necessary.
97
+ # Query for a Polar predicate or string.
62
98
  #
63
- # @param query [String or Predicate]
64
- # @return Enumerator of resulting bindings
65
- # @raise [Error] if the FFI call raises one.
99
+ # @overload query(query)
100
+ # @param query [String]
101
+ # @return [Enumerator] of resulting bindings
102
+ # @raise [Error] if the FFI call raises one.
103
+ # @overload query(query)
104
+ # @param query [Predicate]
105
+ # @return [Enumerator] of resulting bindings
106
+ # @raise [Error] if the FFI call raises one.
66
107
  def query(query)
67
- load_queued_files
68
108
  new_host = host.dup
69
109
  case query
70
110
  when String
71
111
  ffi_query = ffi_polar.new_query_from_str(query)
72
112
  when Predicate
73
- ffi_query = ffi_polar.new_query_from_term(new_host.to_polar_term(query))
113
+ ffi_query = ffi_polar.new_query_from_term(new_host.to_polar(query))
74
114
  else
75
115
  raise InvalidQueryTypeError
76
116
  end
@@ -81,54 +121,12 @@ module Oso
81
121
  #
82
122
  # @param name [String]
83
123
  # @param args [Array<Object>]
124
+ # @return [Enumerator] of resulting bindings
84
125
  # @raise [Error] if the FFI call raises one.
85
126
  def query_rule(name, *args)
86
127
  query(Predicate.new(name, args: args))
87
128
  end
88
129
 
89
- # Start a REPL session.
90
- #
91
- # @raise [Error] if the FFI call raises one.
92
- def repl(load: false) # rubocop:disable Metrics/MethodLength
93
- ARGV.map { |f| load_file(f) } if load
94
- load_queued_files
95
-
96
- loop do
97
- print('query> ')
98
- begin
99
- query = STDIN.readline.chomp.chomp(';')
100
- rescue EOFError
101
- return
102
- end
103
-
104
- begin
105
- ffi_query = ffi_polar.new_query_from_str(query)
106
- rescue ParseError => e
107
- puts("Parse error: " + e.to_s)
108
- next
109
- end
110
-
111
- begin
112
- results = Query.new(ffi_query, host: host).results.to_a
113
- rescue PolarRuntimeError => e
114
- puts(e.to_s)
115
- next
116
- end
117
-
118
- if results.empty?
119
- pp false
120
- else
121
- results.each do |result|
122
- if result.empty?
123
- pp true
124
- else
125
- pp result
126
- end
127
- end
128
- end
129
- end
130
- end
131
-
132
130
  # Register a Ruby class with Polar.
133
131
  #
134
132
  # @param cls [Class]
@@ -136,31 +134,98 @@ module Oso
136
134
  # @param from_polar [Proc]
137
135
  # @raise [InvalidConstructorError] if provided an invalid 'from_polar' constructor.
138
136
  def register_class(cls, name: nil, from_polar: nil)
139
- if block_given?
140
- from_polar = Proc.new
141
- end
137
+ from_polar = Proc.new if block_given?
142
138
  name = host.cache_class(cls, name: name, constructor: from_polar)
143
139
  register_constant(name, value: cls)
144
140
  end
145
141
 
146
142
  def register_constant(name, value:)
147
- ffi_polar.register_constant(name, value: host.to_polar_term(value))
143
+ ffi_polar.register_constant(name, value: host.to_polar(value))
148
144
  end
149
145
 
150
- # Load all queued files, flushing the {#load_queue}.
151
- def load_queued_files
152
- load_queue.reject! do |filename|
153
- File.open(filename) { |file| load_str(file.read, filename: filename) }
154
- true
155
- end
146
+ # Start a REPL session.
147
+ #
148
+ # @param files [Array<String>]
149
+ # @raise [Error] if the FFI call raises one.
150
+ def repl(files = [])
151
+ files.map { |f| load_file(f) }
152
+ prompt = "#{FG_BLUE}query>#{RESET} "
153
+ # Try loading the readline module from the Ruby stdlib. If we get a
154
+ # LoadError, fall back to the standard REPL with no readline support.
155
+ require 'readline'
156
+ repl_readline(prompt)
157
+ rescue LoadError
158
+ repl_standard(prompt)
156
159
  end
157
160
 
158
161
  private
159
162
 
160
163
  # @return [FFI::Polar]
161
164
  attr_reader :ffi_polar
162
- # @return [Array<String>]
163
- attr_reader :load_queue
165
+ # @return [Hash<String, String>]
166
+ attr_reader :loaded_names
167
+ # @return [Hash<String, String>]
168
+ attr_reader :loaded_contents
169
+
170
+ # The R and L in REPL for systems where readline is available.
171
+ def repl_readline(prompt)
172
+ while (buf = Readline.readline(prompt, true))
173
+ if /^\s*$/ =~ buf # Don't add empty entries to history.
174
+ Readline::HISTORY.pop
175
+ next
176
+ end
177
+ process_line(buf)
178
+ end
179
+ rescue Interrupt # rubocop:disable Lint/SuppressedException
180
+ end
181
+
182
+ # The R and L in REPL for systems where readline is not available.
183
+ def repl_standard(prompt)
184
+ loop do
185
+ puts prompt
186
+ begin
187
+ buf = $stdin.readline
188
+ rescue EOFError
189
+ return
190
+ end
191
+ process_line(buf)
192
+ end
193
+ rescue Interrupt # rubocop:disable Lint/SuppressedException
194
+ end
195
+
196
+ # Process a line of user input in the REPL.
197
+ #
198
+ # @param buf [String]
199
+ def process_line(buf) # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/AbcSize
200
+ query = buf.chomp.chomp(';')
201
+ begin
202
+ ffi_query = ffi_polar.new_query_from_str(query)
203
+ rescue ParseError => e
204
+ print_error(e)
205
+ return
206
+ end
207
+
208
+ begin
209
+ results = Query.new(ffi_query, host: host).results.to_a
210
+ rescue PolarRuntimeError => e
211
+ print_error(e)
212
+ return
213
+ end
214
+
215
+ if results.empty?
216
+ puts false
217
+ else
218
+ results.each do |result|
219
+ if result.empty?
220
+ puts true
221
+ else
222
+ result.each do |variable, value|
223
+ puts "#{variable} = #{value.inspect}"
224
+ end
225
+ end
226
+ end
227
+ end
228
+ end
164
229
  end
165
230
  end
166
231
  end
@@ -3,11 +3,12 @@
3
3
  module Oso
4
4
  module Polar
5
5
  # A single Polar query.
6
- class Query
6
+ class Query # rubocop:disable Metrics/ClassLength
7
+ # @return [Enumerator]
7
8
  attr_reader :results
8
9
 
9
10
  # @param ffi_query [FFI::Query]
10
- # @param ffi_polar [FFI::Polar]
11
+ # @param host [Oso::Polar::Host]
11
12
  def initialize(ffi_query, host:)
12
13
  @calls = {}
13
14
  @ffi_query = ffi_query
@@ -38,21 +39,20 @@ module Oso
38
39
  #
39
40
  # @param method [#to_sym]
40
41
  # @param call_id [Integer]
41
- # @param instance [Hash]
42
+ # @param instance [Hash<String, Object>]
42
43
  # @param args [Array<Hash>]
43
44
  # @raise [InvalidCallError] if the method doesn't exist on the instance or
44
45
  # the args passed to the method are invalid.
45
- def register_call(method, call_id:, instance:, args:)
46
+ def register_call(attribute, call_id:, instance:, args:) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
46
47
  return if calls.key?(call_id)
47
48
 
48
- args = args.map { |a| host.to_ruby(a) }
49
- if instance['value'].key? 'ExternalInstance'
50
- instance_id = instance['value']['ExternalInstance']['instance_id']
51
- instance = host.get_instance(instance_id)
49
+ instance = host.to_ruby(instance)
50
+ if args.nil?
51
+ result = instance.__send__(attribute)
52
52
  else
53
- instance = host.to_ruby(instance)
53
+ args = args.map { |a| host.to_ruby(a) }
54
+ result = instance.__send__(attribute, *args)
54
55
  end
55
- result = instance.__send__(method, *args)
56
56
  result = [result].to_enum unless result.is_a? Enumerator # Call must be a generator.
57
57
  calls[call_id] = result.lazy
58
58
  rescue ArgumentError, NoMethodError
@@ -68,19 +68,18 @@ module Oso
68
68
  ffi_query.call_result(result, call_id: call_id)
69
69
  end
70
70
 
71
- # Retrieve the next result from a registered call and pass it to {#to_polar_term}.
71
+ # Retrieve the next result from a registered call and pass it to {#to_polar}.
72
72
  #
73
73
  # @param id [Integer]
74
74
  # @return [Hash]
75
75
  # @raise [StopIteration] if the call has been exhausted.
76
76
  def next_call_result(id)
77
- host.to_polar_term(calls[id].next)
77
+ host.to_polar(calls[id].next)
78
78
  end
79
79
 
80
- # Send result of predicate check across FFI boundary.
80
+ # Send application error across FFI boundary.
81
81
  #
82
- # @param result [Boolean]
83
- # @param call_id [Integer]
82
+ # @param message [String]
84
83
  # @raise [Error] if the FFI call raises one.
85
84
  def application_error(message)
86
85
  ffi_query.application_error(message)
@@ -92,10 +91,10 @@ module Oso
92
91
  # @param method [#to_sym]
93
92
  # @param args [Array<Hash>]
94
93
  # @param call_id [Integer]
95
- # @param instance_id [Integer]
94
+ # @param instance [Hash<String, Object>]
96
95
  # @raise [Error] if the FFI call raises one.
97
- def handle_call(method, call_id:, instance:, args:)
98
- register_call(method, call_id: call_id, instance: instance, args: args)
96
+ def handle_call(attribute, call_id:, instance:, args:)
97
+ register_call(attribute, call_id: call_id, instance: instance, args: args)
99
98
  result = JSON.dump(next_call_result(call_id))
100
99
  call_result(result, call_id: call_id)
101
100
  rescue InvalidCallError => e
@@ -105,6 +104,31 @@ module Oso
105
104
  call_result(nil, call_id: call_id)
106
105
  end
107
106
 
107
+ def handle_make_external(data) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
108
+ id = data['instance_id']
109
+ raise DuplicateInstanceRegistrationError, id if host.instance? id
110
+
111
+ constructor = data['constructor']['value']
112
+ if constructor.key? 'InstanceLiteral'
113
+ cls_name = constructor['InstanceLiteral']['tag']
114
+ fields = constructor['InstanceLiteral']['fields']['fields']
115
+ kwargs = Hash[fields.map { |k, v| [k.to_sym, host.to_ruby(v)] }]
116
+ args = []
117
+ elsif constructor.key? 'Call'
118
+ cls_name = constructor['Call']['name']
119
+ args = constructor['Call']['args'].map { |arg| host.to_ruby(arg) }
120
+ kwargs = constructor['Call']['kwargs']
121
+ kwargs = if kwargs.nil?
122
+ {}
123
+ else
124
+ Hash[kwargs.map { |k, v| [k.to_sym, host.to_ruby(v)] }]
125
+ end
126
+ else
127
+ raise InvalidConstructorError
128
+ end
129
+ host.make_instance(cls_name, args: args, kwargs: kwargs, id: id)
130
+ end
131
+
108
132
  # Create a generator that can be polled to advance the query loop.
109
133
  #
110
134
  # @yieldparam [Hash<String, Object>]
@@ -120,18 +144,13 @@ module Oso
120
144
  when 'Result'
121
145
  yielder << event.data['bindings'].transform_values { |v| host.to_ruby(v) }
122
146
  when 'MakeExternal'
123
- id = event.data['instance_id']
124
- raise DuplicateInstanceRegistrationError, id if host.instance? id
125
-
126
- cls_name = event.data['instance']['tag']
127
- fields = event.data['instance']['fields']['fields']
128
- host.make_instance(cls_name, fields: fields, id: id)
147
+ handle_make_external(event.data)
129
148
  when 'ExternalCall'
130
149
  call_id = event.data['call_id']
131
150
  instance = event.data['instance']
132
- method = event.data['attribute']
151
+ attribute = event.data['attribute']
133
152
  args = event.data['args']
134
- handle_call(method, call_id: call_id, instance: instance, args: args)
153
+ handle_call(attribute, call_id: call_id, instance: instance, args: args)
135
154
  when 'ExternalIsSubSpecializer'
136
155
  instance_id = event.data['instance_id']
137
156
  left_tag = event.data['left_class_tag']
@@ -139,9 +158,9 @@ module Oso
139
158
  answer = host.subspecializer?(instance_id, left_tag: left_tag, right_tag: right_tag)
140
159
  question_result(answer, call_id: event.data['call_id'])
141
160
  when 'ExternalIsa'
142
- instance_id = event.data['instance_id']
161
+ instance = event.data['instance']
143
162
  class_tag = event.data['class_tag']
144
- answer = host.isa?(instance_id, class_tag: class_tag)
163
+ answer = host.isa?(instance, class_tag: class_tag)
145
164
  question_result(answer, call_id: event.data['call_id'])
146
165
  when 'ExternalUnify'
147
166
  left_instance_id = event.data['left_instance_id']
@@ -152,11 +171,11 @@ module Oso
152
171
  puts event.data['message'] if event.data['message']
153
172
  print 'debug> '
154
173
  begin
155
- input = STDIN.readline.chomp.chomp(';')
174
+ input = $stdin.readline.chomp.chomp(';')
156
175
  rescue EOFError
157
176
  next
158
177
  end
159
- command = JSON.dump(host.to_polar_term(input))
178
+ command = JSON.dump(host.to_polar(input))
160
179
  ffi_query.debug_command(command)
161
180
  else
162
181
  raise "Unhandled event: #{JSON.dump(event.inspect)}"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Oso
4
- VERSION = '0.3.0'
4
+ VERSION = '0.5.2'
5
5
  end
@@ -34,6 +34,7 @@ Gem::Specification.new do |spec|
34
34
  spec.add_development_dependency 'pry-byebug', '~> 3.9.0'
35
35
  spec.add_development_dependency 'rake', '~> 12.0'
36
36
  spec.add_development_dependency 'rspec', '~> 3.0'
37
- spec.add_development_dependency 'solargraph', '~> 0.39.8'
37
+ spec.add_development_dependency 'rubocop', '~> 0.89.1'
38
+ spec.add_development_dependency 'solargraph', '~> 0.39.14'
38
39
  spec.add_development_dependency 'yard', '~> 0.9.25'
39
40
  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.3.0
4
+ version: 0.5.2
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-07-28 00:00:00.000000000 Z
11
+ date: 2020-09-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi
@@ -66,20 +66,34 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.89.1
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.89.1
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: solargraph
71
85
  requirement: !ruby/object:Gem::Requirement
72
86
  requirements:
73
87
  - - "~>"
74
88
  - !ruby/object:Gem::Version
75
- version: 0.39.8
89
+ version: 0.39.14
76
90
  type: :development
77
91
  prerelease: false
78
92
  version_requirements: !ruby/object:Gem::Requirement
79
93
  requirements:
80
94
  - - "~>"
81
95
  - !ruby/object:Gem::Version
82
- version: 0.39.8
96
+ version: 0.39.14
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: yard
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -104,6 +118,7 @@ extra_rdoc_files: []
104
118
  files:
105
119
  - ".gitignore"
106
120
  - ".rspec"
121
+ - ".rubocop.yml"
107
122
  - ".solargraph.yml"
108
123
  - Gemfile
109
124
  - Gemfile.lock
@@ -111,7 +126,6 @@ files:
111
126
  - README.md
112
127
  - Rakefile
113
128
  - bin/oso
114
- - expense.rb
115
129
  - ext/oso-oso/lib/libpolar.dylib
116
130
  - ext/oso-oso/lib/libpolar.so
117
131
  - ext/oso-oso/lib/polar.dll
@@ -123,9 +137,11 @@ files:
123
137
  - lib/oso/polar/errors.rb
124
138
  - lib/oso/polar/ffi.rb
125
139
  - lib/oso/polar/ffi/error.rb
140
+ - lib/oso/polar/ffi/message.rb
126
141
  - lib/oso/polar/ffi/polar.rb
127
142
  - lib/oso/polar/ffi/query.rb
128
143
  - lib/oso/polar/ffi/query_event.rb
144
+ - lib/oso/polar/ffi/source.rb
129
145
  - lib/oso/polar/host.rb
130
146
  - lib/oso/polar/polar.rb
131
147
  - lib/oso/polar/predicate.rb
data/expense.rb DELETED
@@ -1,15 +0,0 @@
1
- class Expense
2
- attr_reader :amount, :description, :submitted_by
3
-
4
- def initialize(amount, description, submitted_by)
5
- @amount = amount
6
- @description = description
7
- @submitted_by = submitted_by
8
- end
9
- end
10
-
11
- EXPENSES = {
12
- 1 => Expense.new(500, 'coffee', 'alice@example.com'),
13
- 2 => Expense.new(5000, 'software', 'alice@example.com'),
14
- 3 => Expense.new(50_000, 'flight', 'bhavik@example.com')
15
- }.freeze