zen-engine-ruby 0.0.2-x86_64-linux

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: 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: []