zen-engine-ruby 0.0.1-amd64-windows

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: 03337dd24d72c4d00aa3225a6ff7137a4b36caeded9f83a52e990764dcdecece
4
+ data.tar.gz: b3845dac2c989bb0f69d74c0b5f8306ceecbc7eb1459aadf116af84ac0104234
5
+ SHA512:
6
+ metadata.gz: b37f5b1e615f78deee516d1f56c962078b19d700e8e08793b4fd5ea2ce7fa380f551c128168a76660f8ac7e9bb1855793334c2cc4efda8aad13673fe74c9214b
7
+ data.tar.gz: 27cd280cda839d20c2468adecba074aba05b6bb98e7bdaf339695a14bc59f659cce54dd911567ac6b75f2e00018856ee75a8fca44f20d0656ef74f5af8ed868e
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,329 @@
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 **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
+ # TODO: deploy to rubygems
18
+ # gem install zen-engine-ruby
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ZEN Engine is built as embeddable BRE for your **Ruby**, **Rust**, **NodeJS**, **Python** or **Go** applications.
24
+ 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.
25
+
26
+ ### Load and Execute Rules
27
+
28
+ TODO: write this section. In the meantime, take a look at [test.rb](./test.rb) for how to use this API.
29
+
30
+ ### Supported Platforms
31
+
32
+ List of platforms where Zen Engine is natively available:
33
+
34
+ * **Ruby** - [GitHub](https://github.com/FutureProofRetail/zen-engine-ruby/README.md)
35
+ * **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)
36
+ * **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/)
37
+ * **Go** - [GitHub](https://github.com/gorules/zen-go) | [Documentation](https://gorules.io/docs/developers/bre/engines/go)
38
+ * **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)
39
+
40
+ For a complete **Business Rules Management Systems (BRMS)** solution:
41
+
42
+ * [Self-hosted BRMS](https://gorules.io)
43
+ * [GoRules Cloud BRMS](https://gorules.io/signin/verify-email)
44
+
45
+ ## JSON Decision Model (JDM)
46
+
47
+ GoRules JDM (JSON Decision Model) is a modeling framework designed to streamline the representation and implementation
48
+ of decision models.
49
+
50
+ #### Understanding GoRules JDM
51
+
52
+ At its core, GoRules JDM revolves around the concept of decision models as interconnected graphs stored in JSON format.
53
+ These graphs capture the intricate relationships between various decision points, conditions, and outcomes in a GoRules
54
+ Zen-Engine.
55
+
56
+ Graphs are made by linking nodes with edges, which act like pathways for moving information from one node to another,
57
+ usually from the left to the right.
58
+
59
+ The Input node serves as an entry for all data relevant to the context, while the Output nodes produce the result of
60
+ decision-making process. The progression of data follows a path from the Input Node to the Output Node, traversing all
61
+ interconnected nodes in between. As the data flows through this network, it undergoes evaluation at each node, and
62
+ connections determine where the data is passed along the graph.
63
+
64
+ To see JDM Graph in action you can use [Free Online Editor](https://editor.gorules.io) with built in Simulator.
65
+
66
+ There are 5 main node types in addition to a graph Input Node (Request) and Output Node (Response):
67
+
68
+ * Decision Table Node
69
+ * Switch Node
70
+ * Function Node
71
+ * Expression Node
72
+ * Decision Node
73
+
74
+ ### Decision Table Node
75
+
76
+ #### Overview
77
+
78
+ Tables provide a structured representation of decision-making processes, allowing developers and business users to
79
+ express complex rules in a clear and concise manner.
80
+
81
+ <img width="960" alt="Decision Table" src="https://gorules.io/images/decision-table.png">
82
+
83
+ #### Structure
84
+
85
+ At the core of the Decision Table is its schema, defining the structure with inputs and outputs. Inputs encompass
86
+ business-friendly expressions using the ZEN Expression Language, accommodating a range of conditions such as equality,
87
+ numeric comparisons, boolean values, date time functions, array functions and more. The schema's outputs dictate the
88
+ form of results generated by the Decision Table.
89
+ Inputs and outputs are expressed through a user-friendly interface, often resembling a spreadsheet. This facilitates
90
+ easy modification and addition of rules, enabling business users to contribute to decision logic without delving into
91
+ intricate code.
92
+
93
+ #### Evaluation Process
94
+
95
+ Decision Tables are evaluated row by row, from top to bottom, adhering to a specified hit policy.
96
+ Single row is evaluated via Inputs columns, from left to right. Each input column represents `AND` operator. If cell is
97
+ empty that column is evaluated **truthfully**, independently of the value.
98
+
99
+ If a single cell within a row fails (due to error, or otherwise), the row is skipped.
100
+
101
+ **HitPolicy**
102
+
103
+ The hit policy determines the outcome calculation based on matching rules.
104
+
105
+ The result of the evaluation is:
106
+
107
+ * **an object** if the hit policy of the decision table is `first` and a rule matched. The structure is defined by the
108
+ output fields. Qualified field names with a dot (.) inside lead to nested objects.
109
+ * **`null`/`undefined`** if no rule matched in `first` hit policy
110
+ * **an array of objects** if the hit policy of the decision table is `collect` (one array item for each matching rule)
111
+ or empty array if no rules match
112
+
113
+ #### Inputs
114
+
115
+ In the assessment of rules or rows, input columns embody the `AND` operator. The values typically consist of (qualified)
116
+ names, such as `customer.country` or `customer.age`.
117
+
118
+ There are two types of evaluation of inputs, `Unary` and `Expression`.
119
+
120
+ **Unary Evaluation**
121
+
122
+ Unary evaluation is usually used when we would like to compare single fields from incoming context separately, for
123
+ example `customer.country` and `cart.total` . It is activated when a column has `field` defined in its schema.
124
+
125
+ ***Example***
126
+
127
+ For the input:
128
+
129
+ ```json
130
+ {
131
+ "customer": {
132
+ "country": "US"
133
+ },
134
+ "cart": {
135
+ "total": 1500
136
+ }
137
+ }
138
+ ```
139
+
140
+ <img width="960" alt="Decision Table Unary Test" src="https://gorules.io/images/decision-table.png">
141
+
142
+ This evaluation translates to
143
+
144
+ ```
145
+ IF customer.country == 'US' AND cart.total > 1000 THEN {"fees": {"percent": 2}}
146
+ ELSE IF customer.country == 'US' THEN {"fees": {"flat": 30}}
147
+ ELSE IF customer.country == 'CA' OR customer.country == 'MX' THEN {"fees": {"flat": 50}}
148
+ ELSE {"fees": {"flat": 150}}
149
+ ```
150
+
151
+ List shows basic example of the unary tests in the Input Fields:
152
+
153
+ | Input entry | Input Expression |
154
+ |-------------|------------------------------------------------|
155
+ | "A" | the field equals "A" |
156
+ | "A", "B" | the field is either "A" or "B" |
157
+ | 36 | the numeric value equals 36 |
158
+ | < 36 | a value less than 36 |
159
+ | > 36 | a value greater than 36 |
160
+ | [20..39] | a value between 20 and 39 (inclusive) |
161
+ | 20,39 | a value either 20 or 39 |
162
+ | <20, >39 | a value either less than 20 or greater than 39 |
163
+ | true | the boolean value true |
164
+ | false | the boolean value false |
165
+ | | any value, even null/undefined |
166
+ | null | the value null or undefined |
167
+
168
+ Note: For the full list please
169
+ visit [ZEN Expression Language](https://gorules.io/docs/rules-engine/expression-language/).
170
+
171
+ **Expression Evaluation**
172
+
173
+ Expression evaluation is used when we would like to create more complex evaluation logic inside single cell. It allows
174
+ us to compare multiple fields from the incoming context inside same cell.
175
+
176
+ It can be used by providing an empty `Selector (field)` inside column configuration.
177
+
178
+ ***Example***
179
+
180
+ For the input:
181
+
182
+ ```json
183
+ {
184
+ "transaction": {
185
+ "country": "US",
186
+ "createdAt": "2023-11-20T19:00:25Z",
187
+ "amount": 10000
188
+ }
189
+ }
190
+ ```
191
+
192
+ <img width="960" alt="Decision Table Expression" src="https://gorules.io/images/decision-table-expression.png">
193
+
194
+ ```
195
+ IF time(transaction.createdAt) > time("17:00:00") AND transaction.amount > 1000 THEN {"status": "reject"}
196
+ ELSE {"status": "approve"}
197
+ ```
198
+
199
+ Note: For the full list please
200
+ visit [ZEN Expression Language](https://gorules.io/docs/rules-engine/expression-language/).
201
+
202
+ **Outputs**
203
+
204
+ Output columns serve as the blueprint for the data that the decision table will generate when the conditions are met
205
+ during evaluation.
206
+
207
+ When a row in the decision table satisfies its specified conditions, the output columns determine the nature and
208
+ structure of the information that will be returned. Each output column represents a distinct field, and the collective
209
+ set of these fields forms the output or result associated with the validated row. This mechanism allows decision tables
210
+ to precisely define and control the data output.
211
+
212
+ ***Example***
213
+
214
+ <img width="860" alt="Decision Table Output" src="https://gorules.io/images/decision-table-output.png">
215
+
216
+ And the result would be:
217
+
218
+ ```json
219
+ {
220
+ "flatProperty": "A",
221
+ "output": {
222
+ "nested": {
223
+ "property": "B"
224
+ },
225
+ "property": 36
226
+ }
227
+ }
228
+ ```
229
+
230
+ ### Switch Node (NEW)
231
+
232
+ The Switch node in GoRules JDM introduces a dynamic branching mechanism to decision models, enabling the graph to
233
+ diverge based on conditions.
234
+
235
+ Conditions are written in a Zen Expression Language.
236
+
237
+ By incorporating the Switch node, decision models become more flexible and context-aware. This capability is
238
+ particularly valuable in scenarios where diverse decision logic is required based on varying inputs. The Switch node
239
+ efficiently manages branching within the graph, enhancing the overall complexity and realism of decision models in
240
+ GoRules JDM, making it a pivotal component for crafting intelligent and adaptive systems.
241
+
242
+ The Switch node preserves the incoming data without modification; it forwards the entire context to the output branch(
243
+ es).
244
+
245
+ <img width="960" alt="Switch / Branching" src="https://gorules.io/images/decision-graph.png">
246
+
247
+ #### HitPolicy
248
+
249
+ There are two HitPolicy options for the switch node, `first` and `collect`.
250
+
251
+ In the context of a first hit policy, the graph branches to the initial matching condition, analogous to the behavior
252
+ observed in a table. Conversely, under a collect hit policy, the graph extends to all branches where conditions hold
253
+ true, allowing branching to multiple paths.
254
+
255
+ Note: If there are multiple edges from the same condition, there is no guaranteed order of execution.
256
+
257
+ *Available from:*
258
+
259
+ * Python 0.16.0
260
+ * NodeJS 0.13.0
261
+ * Rust 0.16.0
262
+ * Go 0.1.0
263
+
264
+ ### Functions Node
265
+
266
+ Function nodes are JavaScript snippets that allow for quick and easy parsing, re-mapping or otherwise modifying the data
267
+ using JavaScript. Inputs of the node are provided as function's arguments. Functions are executed on top of QuickJS
268
+ Engine that is bundled into the ZEN Engine.
269
+
270
+ Function timeout is set to a 50ms.
271
+
272
+ ```js
273
+ const handler = (input, {dayjs, Big}) => {
274
+ return {
275
+ ...input,
276
+ someField: 'hello'
277
+ };
278
+ };
279
+ ```
280
+
281
+ There are two built in libraries:
282
+
283
+ * [dayjs](https://www.npmjs.com/package/dayjs) - for Date Manipulation
284
+ * [big.js](https://www.npmjs.com/package/big.js) - for arbitrary-precision decimal arithmetic.
285
+
286
+ ### Expression Node
287
+
288
+ The Expression node serves as a tool for transforming input objects into alternative objects using the Zen Expression
289
+ Language. When specifying the output properties, each property requires a separate row. These rows are defined by two
290
+ fields:
291
+
292
+ - Key - qualified name of the output property
293
+ - Value - value expressed through the Zen Expression Language
294
+
295
+ Note: Any errors within the Expression node will bring the graph to a halt.
296
+
297
+ <img width="960" alt="Decision Table" src="https://gorules.io/images/expression.png">
298
+
299
+ ### Decision Node
300
+
301
+ The "Decision" node is designed to extend the capabilities of decision models. Its function is to invoke and reuse other
302
+ decision models during execution.
303
+
304
+ By incorporating the "Decision" node, developers can modularize decision logic, promoting reusability and
305
+ maintainability in complex systems.
306
+
307
+ ## Support matrix
308
+
309
+ | Arch | Rust | NodeJS | Python | Go |
310
+ |:----------------|:-------------------|:-------------------|:-------------------|:-------------------|
311
+ | linux-x64-gnu | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
312
+ | linux-arm64-gnu | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
313
+ | darwin-x64 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
314
+ | darwin-arm64 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
315
+ | win32-x64-msvc | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
316
+
317
+ We do not support linux-musl currently.
318
+
319
+ ## Contribution
320
+
321
+ JDM standard is growing and we need to keep tight control over its development and roadmap as there are number of
322
+ companies that are using GoRules Zen-Engine and GoRules BRMS.
323
+ For this reason we can't accept any code contributions at this moment, apart from help with documentation and additional
324
+ tests.
325
+
326
+ ## License
327
+
328
+ [MIT License]()
329
+
@@ -0,0 +1,3 @@
1
+ module ZenRuby
2
+ VERSION = "0.0.1"
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.1
5
+ platform: amd64-windows
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/windows_amd64/zen_ffi.dll
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: []