zen-engine-ruby 0.0.2-x86_64-linux

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 100c7e2ecb0fac469968ea66c4fb6267792cf0de0d891368b36a84b5974b27ad
4
+ data.tar.gz: 4a42d3f448215d911e3068d40a78b53142f290496770eb16fbcd453283dccdde
5
+ SHA512:
6
+ metadata.gz: 97b4a69779bb31eeb926c824533fd0995ee5988baaaa56d9aa8971e84e78b90c773be5c380082e0f7f2b19d3b6213822f4d124141b7bdd4b92dbdd905b1d4950
7
+ data.tar.gz: 9858ec4a4bb6a0fe9c54d0df1b21b6c4a1a95aa3511613086b9d0b1f64f2d4f06fcb6df6a27f9cebe6dcb8f151be26a578ecbc17bee153cfd05066f17bac2ed0
data/LICENSE ADDED
@@ -0,0 +1,31 @@
1
+ ZEN-Ruby License (MIT)
2
+
3
+ Copyright 2024 FutureProof Retail
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
6
+ files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy,
7
+ modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
8
+ is furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
11
+
12
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
13
+ WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
14
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
15
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16
+
17
+ GoRules ZEN Engine License Reproduced Below:
18
+
19
+ Copyright 2024 GoRules.io
20
+
21
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
22
+ files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy,
23
+ modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
24
+ is furnished to do so, subject to the following conditions:
25
+
26
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
27
+
28
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
29
+ WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
30
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
31
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,330 @@
1
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
2
+
3
+ # ZEN Rules Engine for Ruby (ALPHA)
4
+
5
+ Ruby Bindings for the [GoRules Zen Business Rules Engine](https://github.com/gorules/zen).
6
+
7
+ ZEN Engine is a cross-platform, Open-Source Business Rules Engine (BRE). It is written in Rust and provides native
8
+ bindings for **Ruby**, **NodeJS**, **Python**, **Ruby**, and **Go**. ZEN Engine allows to load and execute JSON Decision Model (JDM) from JSON files.
9
+
10
+ <img width="800" alt="Open-Source Rules Engine" src="https://gorules.io/images/jdm-editor.gif">
11
+
12
+ An open-source React editor is available on our [JDM Editor](https://github.com/gorules/jdm-editor) repo.
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ gem install zen-engine-ruby
18
+ ```
19
+
20
+ Or add `zen-engine-ruby` to your `Gemfile`.
21
+
22
+ ## Usage
23
+
24
+ ZEN Engine is built as embeddable BRE for your **Ruby**, **Rust**, **NodeJS**, **Python** or **Go** applications.
25
+ It parses JDM from JSON content. It is up to you to obtain the JSON content, e.g. from file system, database or service call.
26
+
27
+ ### Load and Execute Rules
28
+
29
+ TODO: write this section. In the meantime, take a look at [test.rb](./test.rb) for how to use this API.
30
+
31
+ ### Supported Platforms
32
+
33
+ List of platforms where Zen Engine is natively available:
34
+
35
+ * **Ruby** - [GitHub](https://github.com/FutureProofRetail/zen-engine-ruby/README.md)
36
+ * **NodeJS** - [GitHub](https://github.com/gorules/zen/blob/master/bindings/nodejs/README.md) | [Documentation](https://gorules.io/docs/developers/bre/engines/nodejs) | [npmjs](https://www.npmjs.com/package/@gorules/zen-engine)
37
+ * **Python** - [GitHub](https://github.com/gorules/zen/blob/master/bindings/python/README.md) | [Documentation](https://gorules.io/docs/developers/bre/engines/python) | [pypi](https://pypi.org/project/zen-engine/)
38
+ * **Go** - [GitHub](https://github.com/gorules/zen-go) | [Documentation](https://gorules.io/docs/developers/bre/engines/go)
39
+ * **Rust (Core)** - [GitHub](https://github.com/gorules/zen) | [Documentation](https://gorules.io/docs/developers/bre/engines/rust) | [crates.io](https://crates.io/crates/zen-engine)
40
+
41
+ For a complete **Business Rules Management Systems (BRMS)** solution:
42
+
43
+ * [Self-hosted BRMS](https://gorules.io)
44
+ * [GoRules Cloud BRMS](https://gorules.io/signin/verify-email)
45
+
46
+ ## JSON Decision Model (JDM)
47
+
48
+ GoRules JDM (JSON Decision Model) is a modeling framework designed to streamline the representation and implementation
49
+ of decision models.
50
+
51
+ #### Understanding GoRules JDM
52
+
53
+ At its core, GoRules JDM revolves around the concept of decision models as interconnected graphs stored in JSON format.
54
+ These graphs capture the intricate relationships between various decision points, conditions, and outcomes in a GoRules
55
+ Zen-Engine.
56
+
57
+ Graphs are made by linking nodes with edges, which act like pathways for moving information from one node to another,
58
+ usually from the left to the right.
59
+
60
+ The Input node serves as an entry for all data relevant to the context, while the Output nodes produce the result of
61
+ decision-making process. The progression of data follows a path from the Input Node to the Output Node, traversing all
62
+ interconnected nodes in between. As the data flows through this network, it undergoes evaluation at each node, and
63
+ connections determine where the data is passed along the graph.
64
+
65
+ To see JDM Graph in action you can use [Free Online Editor](https://editor.gorules.io) with built in Simulator.
66
+
67
+ There are 5 main node types in addition to a graph Input Node (Request) and Output Node (Response):
68
+
69
+ * Decision Table Node
70
+ * Switch Node
71
+ * Function Node
72
+ * Expression Node
73
+ * Decision Node
74
+
75
+ ### Decision Table Node
76
+
77
+ #### Overview
78
+
79
+ Tables provide a structured representation of decision-making processes, allowing developers and business users to
80
+ express complex rules in a clear and concise manner.
81
+
82
+ <img width="960" alt="Decision Table" src="https://gorules.io/images/decision-table.png">
83
+
84
+ #### Structure
85
+
86
+ At the core of the Decision Table is its schema, defining the structure with inputs and outputs. Inputs encompass
87
+ business-friendly expressions using the ZEN Expression Language, accommodating a range of conditions such as equality,
88
+ numeric comparisons, boolean values, date time functions, array functions and more. The schema's outputs dictate the
89
+ form of results generated by the Decision Table.
90
+ Inputs and outputs are expressed through a user-friendly interface, often resembling a spreadsheet. This facilitates
91
+ easy modification and addition of rules, enabling business users to contribute to decision logic without delving into
92
+ intricate code.
93
+
94
+ #### Evaluation Process
95
+
96
+ Decision Tables are evaluated row by row, from top to bottom, adhering to a specified hit policy.
97
+ Single row is evaluated via Inputs columns, from left to right. Each input column represents `AND` operator. If cell is
98
+ empty that column is evaluated **truthfully**, independently of the value.
99
+
100
+ If a single cell within a row fails (due to error, or otherwise), the row is skipped.
101
+
102
+ **HitPolicy**
103
+
104
+ The hit policy determines the outcome calculation based on matching rules.
105
+
106
+ The result of the evaluation is:
107
+
108
+ * **an object** if the hit policy of the decision table is `first` and a rule matched. The structure is defined by the
109
+ output fields. Qualified field names with a dot (.) inside lead to nested objects.
110
+ * **`null`/`undefined`** if no rule matched in `first` hit policy
111
+ * **an array of objects** if the hit policy of the decision table is `collect` (one array item for each matching rule)
112
+ or empty array if no rules match
113
+
114
+ #### Inputs
115
+
116
+ In the assessment of rules or rows, input columns embody the `AND` operator. The values typically consist of (qualified)
117
+ names, such as `customer.country` or `customer.age`.
118
+
119
+ There are two types of evaluation of inputs, `Unary` and `Expression`.
120
+
121
+ **Unary Evaluation**
122
+
123
+ Unary evaluation is usually used when we would like to compare single fields from incoming context separately, for
124
+ example `customer.country` and `cart.total` . It is activated when a column has `field` defined in its schema.
125
+
126
+ ***Example***
127
+
128
+ For the input:
129
+
130
+ ```json
131
+ {
132
+ "customer": {
133
+ "country": "US"
134
+ },
135
+ "cart": {
136
+ "total": 1500
137
+ }
138
+ }
139
+ ```
140
+
141
+ <img width="960" alt="Decision Table Unary Test" src="https://gorules.io/images/decision-table.png">
142
+
143
+ This evaluation translates to
144
+
145
+ ```
146
+ IF customer.country == 'US' AND cart.total > 1000 THEN {"fees": {"percent": 2}}
147
+ ELSE IF customer.country == 'US' THEN {"fees": {"flat": 30}}
148
+ ELSE IF customer.country == 'CA' OR customer.country == 'MX' THEN {"fees": {"flat": 50}}
149
+ ELSE {"fees": {"flat": 150}}
150
+ ```
151
+
152
+ List shows basic example of the unary tests in the Input Fields:
153
+
154
+ | Input entry | Input Expression |
155
+ |-------------|------------------------------------------------|
156
+ | "A" | the field equals "A" |
157
+ | "A", "B" | the field is either "A" or "B" |
158
+ | 36 | the numeric value equals 36 |
159
+ | < 36 | a value less than 36 |
160
+ | > 36 | a value greater than 36 |
161
+ | [20..39] | a value between 20 and 39 (inclusive) |
162
+ | 20,39 | a value either 20 or 39 |
163
+ | <20, >39 | a value either less than 20 or greater than 39 |
164
+ | true | the boolean value true |
165
+ | false | the boolean value false |
166
+ | | any value, even null/undefined |
167
+ | null | the value null or undefined |
168
+
169
+ Note: For the full list please
170
+ visit [ZEN Expression Language](https://gorules.io/docs/rules-engine/expression-language/).
171
+
172
+ **Expression Evaluation**
173
+
174
+ Expression evaluation is used when we would like to create more complex evaluation logic inside single cell. It allows
175
+ us to compare multiple fields from the incoming context inside same cell.
176
+
177
+ It can be used by providing an empty `Selector (field)` inside column configuration.
178
+
179
+ ***Example***
180
+
181
+ For the input:
182
+
183
+ ```json
184
+ {
185
+ "transaction": {
186
+ "country": "US",
187
+ "createdAt": "2023-11-20T19:00:25Z",
188
+ "amount": 10000
189
+ }
190
+ }
191
+ ```
192
+
193
+ <img width="960" alt="Decision Table Expression" src="https://gorules.io/images/decision-table-expression.png">
194
+
195
+ ```
196
+ IF time(transaction.createdAt) > time("17:00:00") AND transaction.amount > 1000 THEN {"status": "reject"}
197
+ ELSE {"status": "approve"}
198
+ ```
199
+
200
+ Note: For the full list please
201
+ visit [ZEN Expression Language](https://gorules.io/docs/rules-engine/expression-language/).
202
+
203
+ **Outputs**
204
+
205
+ Output columns serve as the blueprint for the data that the decision table will generate when the conditions are met
206
+ during evaluation.
207
+
208
+ When a row in the decision table satisfies its specified conditions, the output columns determine the nature and
209
+ structure of the information that will be returned. Each output column represents a distinct field, and the collective
210
+ set of these fields forms the output or result associated with the validated row. This mechanism allows decision tables
211
+ to precisely define and control the data output.
212
+
213
+ ***Example***
214
+
215
+ <img width="860" alt="Decision Table Output" src="https://gorules.io/images/decision-table-output.png">
216
+
217
+ And the result would be:
218
+
219
+ ```json
220
+ {
221
+ "flatProperty": "A",
222
+ "output": {
223
+ "nested": {
224
+ "property": "B"
225
+ },
226
+ "property": 36
227
+ }
228
+ }
229
+ ```
230
+
231
+ ### Switch Node (NEW)
232
+
233
+ The Switch node in GoRules JDM introduces a dynamic branching mechanism to decision models, enabling the graph to
234
+ diverge based on conditions.
235
+
236
+ Conditions are written in a Zen Expression Language.
237
+
238
+ By incorporating the Switch node, decision models become more flexible and context-aware. This capability is
239
+ particularly valuable in scenarios where diverse decision logic is required based on varying inputs. The Switch node
240
+ efficiently manages branching within the graph, enhancing the overall complexity and realism of decision models in
241
+ GoRules JDM, making it a pivotal component for crafting intelligent and adaptive systems.
242
+
243
+ The Switch node preserves the incoming data without modification; it forwards the entire context to the output branch(
244
+ es).
245
+
246
+ <img width="960" alt="Switch / Branching" src="https://gorules.io/images/decision-graph.png">
247
+
248
+ #### HitPolicy
249
+
250
+ There are two HitPolicy options for the switch node, `first` and `collect`.
251
+
252
+ In the context of a first hit policy, the graph branches to the initial matching condition, analogous to the behavior
253
+ observed in a table. Conversely, under a collect hit policy, the graph extends to all branches where conditions hold
254
+ true, allowing branching to multiple paths.
255
+
256
+ Note: If there are multiple edges from the same condition, there is no guaranteed order of execution.
257
+
258
+ *Available from:*
259
+
260
+ * Python 0.16.0
261
+ * NodeJS 0.13.0
262
+ * Rust 0.16.0
263
+ * Go 0.1.0
264
+
265
+ ### Functions Node
266
+
267
+ Function nodes are JavaScript snippets that allow for quick and easy parsing, re-mapping or otherwise modifying the data
268
+ using JavaScript. Inputs of the node are provided as function's arguments. Functions are executed on top of QuickJS
269
+ Engine that is bundled into the ZEN Engine.
270
+
271
+ Function timeout is set to a 50ms.
272
+
273
+ ```js
274
+ const handler = (input, {dayjs, Big}) => {
275
+ return {
276
+ ...input,
277
+ someField: 'hello'
278
+ };
279
+ };
280
+ ```
281
+
282
+ There are two built in libraries:
283
+
284
+ * [dayjs](https://www.npmjs.com/package/dayjs) - for Date Manipulation
285
+ * [big.js](https://www.npmjs.com/package/big.js) - for arbitrary-precision decimal arithmetic.
286
+
287
+ ### Expression Node
288
+
289
+ The Expression node serves as a tool for transforming input objects into alternative objects using the Zen Expression
290
+ Language. When specifying the output properties, each property requires a separate row. These rows are defined by two
291
+ fields:
292
+
293
+ - Key - qualified name of the output property
294
+ - Value - value expressed through the Zen Expression Language
295
+
296
+ Note: Any errors within the Expression node will bring the graph to a halt.
297
+
298
+ <img width="960" alt="Decision Table" src="https://gorules.io/images/expression.png">
299
+
300
+ ### Decision Node
301
+
302
+ The "Decision" node is designed to extend the capabilities of decision models. Its function is to invoke and reuse other
303
+ decision models during execution.
304
+
305
+ By incorporating the "Decision" node, developers can modularize decision logic, promoting reusability and
306
+ maintainability in complex systems.
307
+
308
+ ## Support matrix
309
+
310
+ | Arch | Rust | NodeJS | Python | Go |
311
+ |:----------------|:-------------------|:-------------------|:-------------------|:-------------------|
312
+ | linux-x64-gnu | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
313
+ | linux-arm64-gnu | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
314
+ | darwin-x64 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
315
+ | darwin-arm64 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
316
+ | win32-x64-msvc | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
317
+
318
+ We do not support linux-musl currently.
319
+
320
+ ## Contribution
321
+
322
+ JDM standard is growing and we need to keep tight control over its development and roadmap as there are number of
323
+ companies that are using GoRules Zen-Engine and GoRules BRMS.
324
+ For this reason we can't accept any code contributions at this moment, apart from help with documentation and additional
325
+ tests.
326
+
327
+ ## License
328
+
329
+ [MIT License]()
330
+
@@ -0,0 +1,3 @@
1
+ module ZenRuby
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,316 @@
1
+ require "ffi"
2
+ require "json"
3
+
4
+ module ZenRuby
5
+ module LibC
6
+ extend FFI::Library
7
+ ffi_lib FFI::Library::LIBC
8
+
9
+ attach_function :strdup, [:string], :pointer
10
+ end
11
+
12
+ extend FFI::Library
13
+
14
+ # Determine platform-specific library name and path
15
+ def self.find_library_path
16
+ os = case RbConfig::CONFIG['host_os']
17
+ when /darwin|mac os/i
18
+ 'darwin'
19
+ when /linux/i
20
+ 'linux'
21
+ when /mswin|mingw|windows/i
22
+ 'windows'
23
+ else
24
+ raise "Unsupported operating system: #{RbConfig::CONFIG['host_os']}"
25
+ end
26
+
27
+ arch = case RbConfig::CONFIG['host_cpu']
28
+ when /arm64|aarch64/i
29
+ 'arm64'
30
+ when /x86_64|amd64/i
31
+ 'amd64'
32
+ else
33
+ raise "Unsupported architecture: #{RbConfig::CONFIG['host_cpu']}"
34
+ end
35
+
36
+ extension = os == 'windows' ? 'dll' : (os == 'darwin' ? 'dylib' : 'so')
37
+ lib_name = "libzen_ffi.#{extension}"
38
+
39
+ # Get the gem's vendor path
40
+ gem_spec = Gem::Specification.find_by_name('zen-engine-ruby')
41
+ lib_path = File.join(gem_spec.gem_dir, 'vendor', "#{os}_#{arch}", lib_name)
42
+
43
+ unless File.exist?(lib_path)
44
+ raise "Library not found for #{os}_#{arch} platform at #{lib_path}"
45
+ end
46
+
47
+ lib_path
48
+ end
49
+
50
+ ffi_lib find_library_path
51
+
52
+ # typedef struct ZenDecisionLoaderResult {
53
+ # char *content;
54
+ # char *error;
55
+ # } ZenDecisionLoaderResult;
56
+ class ZenDecisionLoaderResult < FFI::Struct
57
+ layout :content, :pointer,
58
+ :error, :pointer
59
+ end
60
+
61
+ # typedef struct ZenCustomNodeResult {
62
+ # char *content;
63
+ # char *error;
64
+ # } ZenCustomNodeResult;
65
+ class ZenCustomNodeResult < FFI::Struct
66
+ layout :content, :pointer,
67
+ :error, :pointer
68
+ end
69
+
70
+ # typedef struct ZenEngineEvaluationOptions {
71
+ # bool trace;
72
+ # uint8_t max_depth;
73
+ # } ZenEngineEvaluationOptions;
74
+ class ZenEngineEvaluationOptions < FFI::Struct
75
+ layout :trace, :bool,
76
+ :max_depth, :uint8
77
+ end
78
+
79
+ # typedef struct ZenResult_c_char {
80
+ # char *result;
81
+ # uint8_t error;
82
+ # char *details;
83
+ # } ZenResult_c_char;
84
+ class ZenResult_c_char < FFI::Struct
85
+ layout :result, :pointer,
86
+ :error, :uint8,
87
+ :details, :pointer
88
+ end
89
+
90
+ # typedef struct ZenResult_c_int {
91
+ # int *result;
92
+ # uint8_t error;
93
+ # char *details;
94
+ # } ZenResult_c_int;
95
+ class ZenResult_c_int < FFI::Struct
96
+ layout :result, :pointer,
97
+ :error, :uint8,
98
+ :details, :pointer
99
+ end
100
+
101
+ # typedef struct ZenResult_ZenDecisionStruct {
102
+ # struct ZenDecisionStruct *result;
103
+ # uint8_t error;
104
+ # char *details;
105
+ # } ZenResult_ZenDecisionStruct;
106
+ class ZenResult_ZenDecisionStruct < FFI::Struct
107
+ layout :result, :pointer,
108
+ :error, :uint8,
109
+ :details, :pointer
110
+ end
111
+
112
+ # typedef struct ZenDecisionLoaderResult (*ZenDecisionLoaderNativeCallback)(const char *key);
113
+ callback :zen_decision_loader_native_callback, [:string], ZenDecisionLoaderResult.by_value
114
+
115
+ # typedef struct ZenCustomNodeResult (*ZenCustomNodeNativeCallback)(const char *request);
116
+ callback :zen_custom_node_native_callback, [:string, :pointer], ZenCustomNodeResult.by_value
117
+
118
+ attach_function :zen_engine_new_native,
119
+ [:zen_decision_loader_native_callback, :zen_custom_node_native_callback],
120
+ :pointer
121
+
122
+ # void zen_engine_free(struct ZenEngineStruct *engine);
123
+ attach_function :zen_engine_free, [:pointer], :void
124
+
125
+ # struct ZenResult_c_char zen_engine_evaluate(const struct ZenEngineStruct *engine,
126
+ # const char *key,
127
+ # const char *context,
128
+ # struct ZenEngineEvaluationOptions options);
129
+ attach_function :zen_engine_evaluate,
130
+ [:pointer, :string, :string, ZenEngineEvaluationOptions.by_value],
131
+ ZenResult_c_char.by_value
132
+
133
+ # struct ZenResult_ZenDecisionStruct zen_engine_get_decision(const struct ZenEngineStruct *engine,
134
+ # const char *key);
135
+ attach_function :zen_engine_get_decision,
136
+ [:pointer, :string],
137
+ ZenResult_ZenDecisionStruct.by_value
138
+
139
+ # struct ZenResult_ZenDecisionStruct zen_engine_create_decision(const struct ZenEngineStruct *engine,
140
+ # const char *content);
141
+ attach_function :zen_engine_create_decision,
142
+ [:pointer, :string],
143
+ ZenResult_ZenDecisionStruct.by_value
144
+
145
+ # struct ZenResult_c_char zen_decision_evaluate(const struct ZenDecisionStruct *decision,
146
+ # const char *context_ptr,
147
+ # struct ZenEngineEvaluationOptions options);
148
+ attach_function :zen_decision_evaluate,
149
+ [:pointer, :string, ZenEngineEvaluationOptions.by_value],
150
+ ZenResult_c_char.by_value
151
+
152
+ # struct ZenResult_c_char zen_evaluate_expression(const char *expression, const char *context);
153
+ attach_function :zen_evaluate_expression,
154
+ [:string, :string],
155
+ ZenResult_c_char.by_value
156
+
157
+ # struct ZenResult_c_int zen_evaluate_unary_expression(const char *expression, const char *context);
158
+ attach_function :zen_evaluate_unary_expression,
159
+ [:string, :string],
160
+ ZenResult_c_int.by_value
161
+
162
+ # struct ZenResult_c_char zen_evaluate_template(const char *template_, const char *context);
163
+ attach_function :zen_evaluate_template,
164
+ [:string, :string],
165
+ ZenResult_c_char.by_value
166
+
167
+ class Engine
168
+ def initialize(loader: nil, custom_handler: nil)
169
+ if loader
170
+ @loader_callback = Proc.new do |key|
171
+ content_json = loader.call(key)
172
+
173
+ loader_result = ZenDecisionLoaderResult.new
174
+
175
+ # Allocate memory that won't be managed by Ruby
176
+ # (if we use MemoryPointer here, it'll cause double-free errors)
177
+ loader_result[:content] = LibC.strdup(content_json)
178
+ loader_result
179
+ end
180
+ end
181
+
182
+ if custom_handler
183
+ raise NotImplementedError, "Custom Node handler not implemented yet"
184
+ # @custom_node_callback = Proc.new do |request_str|
185
+ # request = JSON.parse(request_str)
186
+ # custom_handler.call(request).tap do |a|
187
+ # puts a
188
+
189
+ # end
190
+ # end
191
+ end
192
+
193
+ raw_pointer = ZenRuby.zen_engine_new_native(@loader_callback, @custom_node_callback)
194
+
195
+ # Because we need to do custom cleanup (via `zen_engine_free`) rather than
196
+ # just the usual `free` performed by MemoryPointer, we use AutoPointer.
197
+ @engine_ptr = FFI::AutoPointer.new(raw_pointer, ZenRuby.method(:zen_engine_free))
198
+ end
199
+
200
+ def evaluate(key, context, trace: false, max_depth: 10)
201
+ evaluation_options = ZenEngineEvaluationOptions.new
202
+ evaluation_options[:trace] = trace
203
+ evaluation_options[:max_depth] = max_depth
204
+
205
+ raw_result = ZenRuby.zen_engine_evaluate(@engine_ptr, key, context.to_json, evaluation_options)
206
+ ZenRuby::Result.from_raw_result(raw_result)
207
+ end
208
+
209
+ def evaluate!(key, context, trace: false, max_depth: 10)
210
+ ZenRuby.unwrap_result!(evaluate(key, context, trace:, max_depth:))
211
+ end
212
+
213
+ # Fetch a decision by providing a key to the decision JSON file. Uses
214
+ # the loader to fetch/read the actual JSON data for the Decision graph.
215
+ def get_decision(key)
216
+ # Caller is responsible for freeing: key and ZenResult.
217
+ raw_result = ZenRuby.zen_engine_get_decision(@engine_ptr, key)
218
+ ZenDecisionResult.from_raw_result(raw_result)
219
+ end
220
+
221
+ def get_decision!(key)
222
+ result = get_decision(key)
223
+ raise "Error getting decision for #{key}: #{result.error_code} #{result.details}" if result.error
224
+ result
225
+ end
226
+
227
+ # Create a decision from a JSON string directly; bypasses the loader.
228
+ def create_decision(content)
229
+ # Caller is responsible for freeing: content and ZenResult.
230
+ raw_result = ZenRuby.zen_engine_create_decision(@engine_ptr, content)
231
+ ZenDecisionResult.from_raw_result(raw_result)
232
+ end
233
+
234
+ # Create a decision from a JSON string directly; bypasses the loader.
235
+ def create_decision!(content)
236
+ result = create_decision(content)
237
+ raise "Error creating decision: #{result.error_code} #{result.details}" if result.error
238
+ result
239
+ end
240
+ end
241
+
242
+ ZenDecisionResult = Struct.new(:result, :error, :error_code, :details) do
243
+ def evaluate(context, trace: false, max_depth: 10)
244
+ if error
245
+ raise "Error evaluating decision for #{key}: #{error_code} #{details}"
246
+ end
247
+
248
+ evaluation_options = ZenEngineEvaluationOptions.new
249
+ evaluation_options[:trace] = trace
250
+ evaluation_options[:max_depth] = max_depth
251
+
252
+ raw_result = ZenRuby.zen_decision_evaluate(result, context.to_json, evaluation_options)
253
+ ZenRuby::Result.from_raw_result(raw_result)
254
+ end
255
+
256
+ def evaluate!(...)
257
+ ZenRuby.unwrap_result!(evaluate(...))
258
+ end
259
+
260
+ def self.from_raw_result(raw_result)
261
+ if raw_result[:error] == 0
262
+ ZenDecisionResult.new(raw_result[:result], false, 0, nil)
263
+ else
264
+ ZenDecisionResult.new(nil, true, raw_result[:error], raw_result[:details].null? ? nil : raw_result[:details].read_string)
265
+ end
266
+ end
267
+ end
268
+
269
+ Result = Struct.new(:result, :error, :error_code, :details) do
270
+ def self.from_raw_result(raw_result)
271
+ if raw_result[:error] == 0
272
+ json_string = raw_result[:result].read_string
273
+ ZenRuby::Result.new(JSON.parse(json_string), false, 0, nil)
274
+ else
275
+ ZenRuby::Result.new(nil, true, raw_result[:error], raw_result[:details].read_string)
276
+ end
277
+ end
278
+ end
279
+
280
+ def self.unwrap_result!(result)
281
+ raise "Error evaluating: #{result.error_code} #{result.details}" if result.error
282
+ result.result
283
+ end
284
+
285
+ def self.evaluate_expression(expression, context)
286
+ raw_result = ZenRuby.zen_evaluate_expression(expression, context.to_json)
287
+ ZenRuby::Result.from_raw_result(raw_result)
288
+ end
289
+
290
+ def self.evaluate_expression!(expression, context)
291
+ ZenRuby.unwrap_result!(evaluate_expression(expression, context))
292
+ end
293
+
294
+ def self.evaluate_unary_expression(expression, context)
295
+ raw_result = ZenRuby.zen_evaluate_unary_expression(expression, context.to_json)
296
+ if raw_result[:error] == 0
297
+ value = raw_result[:result].read(:int)
298
+ ZenRuby::Result.new(value == 1, false, 0, nil)
299
+ else
300
+ ZenRuby::Result.new(nil, true, raw_result[:error], raw_result[:details].read_string)
301
+ end
302
+ end
303
+
304
+ def self.evaluate_unary_expression!(expression, context)
305
+ ZenRuby.unwrap_result!(evaluate_unary_expression(expression, context))
306
+ end
307
+
308
+ def self.render_template(template, context)
309
+ raw_result = ZenRuby.zen_evaluate_template(template, context.to_json)
310
+ ZenRuby::Result.from_raw_result(raw_result)
311
+ end
312
+
313
+ def self.render_template!(template, context)
314
+ ZenRuby.unwrap_result!(render_template(template, context))
315
+ end
316
+ end
Binary file
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: zen-engine-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: x86_64-linux
6
+ authors:
7
+ - Alex Matchneer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-11-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ffi
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.17'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.17'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.25'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.25'
41
+ description: Ruby FFI bindings for the Zen library
42
+ email:
43
+ - amatchneer@futureproofretail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - LICENSE
49
+ - README.md
50
+ - lib/zen-engine-ruby.rb
51
+ - lib/zen-engine-ruby/version.rb
52
+ - vendor/linux_amd64/libzen_ffi.so
53
+ homepage: https://github.com/FutureProofRetail/zen-engine-ruby
54
+ licenses:
55
+ - MIT
56
+ metadata: {}
57
+ post_install_message:
58
+ rdoc_options: []
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: 3.0.0
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ requirements: []
72
+ rubygems_version: 3.4.6
73
+ signing_key:
74
+ specification_version: 4
75
+ summary: Ruby bindings for GoRules ZEN engine
76
+ test_files: []