equation 0.5.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f4f1469e22a3d098dfcb0c2ca974a2d081c0c7be90afddb20fea36f65bdc0804
4
+ data.tar.gz: 5925da1a74a22bfe8e1cc1b7f4dd43a61cf02b05a61ca2a15821f184ff45e5a7
5
+ SHA512:
6
+ metadata.gz: 5ef71408034ed55d48efadd5b99c6e3c41ae49680b673f658b5fa0689731834a0260e4810c3a189f670b97c6451845b3c082ef9ad82ff65c813de5b13a6dee27
7
+ data.tar.gz: 595552e82f8f4412b0a379025883669b0f05e1199948de2ed1221c14092a17cd2b296f396ad2e3e0091e1095307b392904866457222d21895c0c7b66978dda87
data/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # Equation [WIP]
2
+
3
+ A rules engine for your Ruby app! Use a constrained and relatively safe language to express logic for your Ruby app, without having to write Ruby. This allows you to use text (e.g. in a configuration file or database) to store logic in a way that can be updated independently from your code faster than it takes for a deploy without opening extra security vulnerabilities.
4
+
5
+ Use cases include:
6
+
7
+ * writing rules to describe HTTP traffic that then gets dropped, like a WAF
8
+ * writing policies to express authorization logic
9
+ * etc
10
+
11
+ Modeled loosely after [Symfony Expression Language](https://symfony.com/doc/current/components/expression_language.html).
12
+
13
+ ## Example
14
+
15
+ In this example, we'll use a rule to determine whether a request should be dropped or not. While the rule here is hardcoded into the program, it could just as easily be pulled from a database, some redis cache, etc instead. Rules can also be cached, saving you an extra parsing step.
16
+
17
+ ```ruby
18
+ require 'equation'
19
+
20
+ # set up the execution environment and give it access to the rails request object
21
+ engine = EquationEngine.new(default: {
22
+ age: 12,
23
+ username: "OMAR",
24
+ request: request
25
+ })
26
+
27
+ suspicious_request = engine.eval(rule: '$request.path == "/api/login" && $request.remote_ip == "1.2.3.4" && $username == "OMAR"')
28
+ if suspicious_request
29
+ # log some things, notify some people
30
+ end
31
+ ```
data/lib/equation.rb ADDED
@@ -0,0 +1,68 @@
1
+ require 'treetop'
2
+ require_relative 'equation_node_classes'
3
+ require_relative 'equation_grammar'
4
+
5
+ class Context
6
+ def initialize(default: {}, methods: {})
7
+ @symbol_table = default
8
+ @methods = methods
9
+ end
10
+
11
+ def set(identifier:, value:)
12
+ @symbol_table[identifier.to_sym] = value
13
+ end
14
+
15
+ def get(identifier:, path: {})
16
+ assert_defined!(identifier: identifier)
17
+ @symbol_table[identifier.to_sym]
18
+ root = @symbol_table[identifier.to_sym]
19
+ child = root
20
+ path.each{|segment|
21
+ segment_name = segment.elements[1].text_value.to_sym
22
+ if child.respond_to?(segment_name)
23
+ child = child.send(segment_name)
24
+ elsif child.include?(segment_name)
25
+ child = child[segment_name]
26
+ else
27
+ raise "no"
28
+ end
29
+ }
30
+
31
+ child
32
+ end
33
+
34
+ def call(method:, args:)
35
+ assert_method_exists!(method: method)
36
+ @methods[method.to_sym].call(*args)
37
+ end
38
+
39
+ private
40
+ def assert_defined!(identifier:)
41
+ raise "Undefined variable: #{identifier}" unless @symbol_table.has_key?(identifier.to_sym)
42
+ end
43
+
44
+ def assert_method_exists!(method:)
45
+ raise "Undefined method: #{method}" unless @methods.has_key?(method.to_sym)
46
+ end
47
+ end
48
+
49
+ class EquationEngine
50
+ def initialize(default: {}, methods: {})
51
+ @parser = EquationParser.new
52
+ @context = Context.new(default: default, methods: methods)
53
+ end
54
+
55
+ def parse(rule:)
56
+ parsed_rule = @parser.parse(rule)
57
+ raise "Parse Error: #{@parser.failure_reason}" unless parsed_rule
58
+
59
+ parsed_rule
60
+ end
61
+
62
+ def eval(rule:)
63
+ parsed_rule = @parser.parse(rule)
64
+ raise "Parse Error: #{rule}" unless parsed_rule
65
+
66
+ parsed_rule.value(ctx: @context)
67
+ end
68
+ end