datalogic-rs 3.0.31

A fast, type-safe Rust implementation of JSONLogic for evaluating logical rules as JSON. Perfect for business rules engines and dynamic filtering in Rust applications.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
# DataLogic-rs API Guide

This guide documents the public API for the DataLogic-rs crate, which provides a Rust implementation for evaluating JSON Logic rules.

## Core Types

The library exposes several key types that most users will need:

- `DataLogic`: The main entry point for parsing and evaluating logic rules
- `DataValue`: A memory-efficient value type for representing JSON-like data
- `Logic`: Represents a compiled logic rule ready for evaluation
- `LogicError`: Error type for all operations in the library
- `Result<T>`: Alias for `std::result::Result<T, LogicError>`

## API Overview

The DataLogic-rs library provides multiple ways to evaluate rules, depending on your specific needs:

| Method | Input Types | Output Type | Use Case |
|--------|------------|-------------|----------|
| `evaluate` | `Logic`, `DataValue` | `&DataValue` | Best for reusing parsed rules and data |
| `evaluate_json` | `&JsonValue`, `&JsonValue` | `JsonValue` | Working directly with JSON values |
| `evaluate_str` | `&str`, `&str` | `JsonValue` | One-step parsing and evaluation from strings |

## Basic Usage

Here's a simple example of using the library with `evaluate_str`:

```rust
use datalogic_rs::DataLogic;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create a new DataLogic instance
    let dl = DataLogic::new();
    
    // Parse and evaluate in one step
    let result = dl.evaluate_str(
        r#"{ ">": [{"var": "temp"}, 100] }"#,  // Logic rule
        r#"{"temp": 110, "name": "user"}"#,    // Data
        None                                    // Use default parser
    )?;
    
    println!("Result: {}", result);  // Output: true
    Ok(())
}
```

## Core API Methods

### Method 1: `evaluate` - For Maximum Reusability

When you need to reuse rules or data across multiple evaluations:

```rust
use datalogic_rs::DataLogic;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let dl = DataLogic::new();
    
    // Parse the rule and data separately
    let rule = dl.parse_logic(r#"{ ">": [{"var": "temp"}, 100] }"#, None)?;
    let data = dl.parse_data(r#"{"temp": 110}"#)?;
    
    // Evaluate the rule against the data
    let result = dl.evaluate(&rule, &data)?;
    
    println!("Result: {}", result); // Prints: true
    Ok(())
}
```

This approach is most efficient when:
- Evaluating the same rule against different data sets
- Evaluating different rules against the same data
- You need fine-grained control over the parsing and evaluation steps

### Method 2: `evaluate_str` - One-Step Evaluation

For quick, one-time evaluations from string inputs:

```rust
use datalogic_rs::DataLogic;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let dl = DataLogic::new();
    
    // Parse and evaluate in one step
    let result = dl.evaluate_str(
        r#"{ "abs": -42 }"#,
        r#"{}"#,
        None
    )?;
    
    println!("Result: {}", result); // Prints: 42
    Ok(())
}
```

### Method 3: `evaluate_json` - Working with JSON Values

When your application already has the rule and data as `serde_json::Value` objects:

```rust
use datalogic_rs::DataLogic;
use serde_json::json;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let dl = DataLogic::new();
    
    // Use serde_json values directly
    let logic = json!({"ceil": 3.14});
    let data = json!({});
    
    // Evaluate using the JSON values
    let result = dl.evaluate_json(&logic, &data, None)?;
    
    println!("Result: {}", result); // Prints: 4
    Ok(())
}
```

## Parsing Methods

DataLogic-rs provides methods to parse rules and data separately:

### Logic Parsing

- `parse_logic(&self, source: &str, format: Option<&str>) -> Result<Logic>`: Parse a logic rule from a string
- `parse_logic_json(&self, source: &JsonValue, format: Option<&str>) -> Result<Logic>`: Parse a logic rule from a JSON value

### Data Parsing

- `parse_data(&self, source: &str) -> Result<DataValue>`: Parse data from a string
- `parse_data_json(&self, source: &JsonValue) -> Result<DataValue>`: Parse data from a JSON value

## Arena-Based Memory Management

DataLogic-rs uses an arena-based memory management system for efficient allocation and deallocation of values during rule evaluation. This approach significantly improves performance and reduces memory overhead.

### Memory Management Methods

- `DataLogic::with_chunk_size(size: usize) -> Self`: Create a new instance with a specific arena chunk size
- `reset_arena(&mut self)`: Reset the arena to free all allocated memory

### Using the Arena in Long-Running Applications

For long-running applications or when processing many rules, periodically reset the arena to prevent excessive memory usage:

```rust
use datalogic_rs::{DataLogic, Result};

fn process_batches(batches: Vec<(String, String)>) -> Result<()> {
    let mut dl = DataLogic::new();
    
    for (rule_str, data_str) in batches {
        // Process each batch
        let result = dl.evaluate_str(&rule_str, &data_str, None)?;
        println!("Result: {}", result);
        
        // Reset the arena to free memory after processing a batch
        dl.reset_arena();
    }
    
    Ok(())
}
```

### Best Practices for Arena Management

1. **Reset Periodically**: Call `reset_arena()` after processing batches of rules to free memory
2. **Tune Chunk Size**: For memory-sensitive applications, customize the arena chunk size
3. **Reuse Parsed Rules**: Parse rules once and reuse them to avoid repeated parsing costs
4. **Beware of Dangling References**: After `reset_arena()` is called, all previously returned values become invalid

For more detailed information on using the arena, see the [ARENA.md](ARENA.md) document.

## Error Handling

All operations that can fail return a `Result<T, LogicError>` which should be properly handled:

```rust
use datalogic_rs::{DataLogic, LogicError, Result};

fn process_input(rule: &str, data: &str) -> Result<()> {
    let dl = DataLogic::new();
    
    match dl.evaluate_str(rule, data, None) {
        Ok(result) => {
            println!("Success: {}", result);
            Ok(())
        },
        Err(LogicError::ParseError { reason }) => {
            eprintln!("Parse error: {}", reason);
            Err(LogicError::ParseError { reason })
        },
        Err(err) => {
            eprintln!("Other error: {}", err);
            Err(err)
        }
    }
}
```

## Performance Considerations

- Use `DataLogic::with_chunk_size()` to tune memory allocation for your workload
- Parse rules once and reuse them with different data inputs using the `evaluate` method
- Use `reset_arena()` periodically for long-running applications
- Choose the most appropriate method based on your input format:
  - Already have `serde_json::Value`? Use `evaluate_json`
  - Working with strings? Use `evaluate_str`
  - Need to reuse rules/data? Parse separately and use `evaluate`

## Custom Operators

The library supports extending its functionality with custom operators. These operators need to be arena-aware to properly interact with the memory management system.

### Implementing Custom Operators

Custom operators in DataLogic-rs implement the `CustomOperator` trait, which requires an `evaluate` method that takes arguments and returns a result allocated within the arena:

```rust
use datalogic_rs::{CustomOperator, DataLogic, DataValue, Result};
use datalogic_rs::value::NumberValue;
use datalogic_rs::arena::DataArena;
use std::fmt::Debug;

// 1. Define a struct that implements the CustomOperator trait
#[derive(Debug)]
struct PowerOperator;

impl CustomOperator for PowerOperator {
    fn evaluate<'a>(&self, args: &'a [DataValue<'a>], arena: &'a DataArena) -> Result<&'a DataValue<'a>> {
        if args.len() != 2 {
            return Err(LogicError::InvalidArgument {
                reason: "Power operator requires exactly 2 arguments".to_string(),
            });
        }
        
        if let (Some(base), Some(exp)) = (args[0].as_f64(), args[1].as_f64()) {
            // Allocate the result in the arena
            return Ok(arena.alloc(DataValue::Number(NumberValue::from_f64(base.powf(exp)))));
        }
        
        Err(LogicError::InvalidArgument {
            reason: "Arguments must be numbers".to_string(),
        })
    }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut dl = DataLogic::new();
    
    // 2. Register the custom operator with DataLogic
    dl.register_custom_operator("pow", Box::new(PowerOperator));
    
    // 3. Use the custom operator in your logic expressions
    let result = dl.evaluate_str(
        r#"{"pow": [2, 3]}"#,
        r#"{}"#,
        None
    )?;
    
    println!("2^3 = {}", result); // Prints: 2^3 = 8
    Ok(())
}
```

### Simple Custom Operators

For simpler use cases, DataLogic-rs provides a more convenient way to implement custom operators:

```rust
use datalogic_rs::{DataLogic, DataValue, SimpleOperatorFn};
use datalogic_rs::value::NumberValue;

// Define a custom operator function with access to data context
fn pow<'r>(args: Vec<DataValue<'r>>, data: DataValue<'r>) -> std::result::Result<DataValue<'r>, String> {
    // Check if we have the expected number of arguments
    if args.len() != 2 {
        // If arguments are missing, try to find them in the data context
        if args.len() == 1 && args[0].is_number() {
            if let Some(obj) = data.as_object() {
                for (key, val) in obj {
                    if *key == "exponent" && val.is_number() {
                        if let (Some(base), Some(exp)) = (args[0].as_f64(), val.as_f64()) {
                            return Ok(DataValue::Number(NumberValue::from_f64(base.powf(exp))));
                        }
                    }
                }
            }
        }
        return Err("Power operator requires 2 arguments or second arg in data context".to_string());
    }
    
    // Process the arguments
    if let (Some(base), Some(exp)) = (args[0].as_f64(), args[1].as_f64()) {
        return Ok(DataValue::Number(NumberValue::from_f64(base.powf(exp))));
    }
    
    Err("Arguments must be numbers".to_string())
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut dl = DataLogic::new();
    
    // Register the custom operator
    dl.register_simple_operator("pow", pow);
    
    // Use the custom operator with explicit arguments
    let result = dl.evaluate_str(
        r#"{"pow": [2, 3]}"#,
        r#"{}"#,
        None
    )?;
    println!("2^3 = {}", result); // Prints: 2^3 = 8
    
    // Use the custom operator with data context
    let result = dl.evaluate_str(
        r#"{"pow": [2]}"#,
        r#"{"exponent": 4}"#,
        None
    )?;
    println!("2^4 = {}", result); // Prints: 2^4 = 16
    
    Ok(())
}
```

The SimpleOperatorFn approach:
- Has access to both arguments and the current data context
- Works with owned DataValues
- Can return scalar types (numbers, strings, booleans, null)
- Handles arena allocation automatically
- Is ideal for most use cases

### Arena Allocation in Custom Operators

When implementing custom operators, follow these arena allocation best practices:

1. **Always allocate results in the arena**: Use `arena.alloc()` for any values you return
2. **Use arena helper methods for collections**:
   - `arena.get_data_value_vec()` - Get a temporary vector for building collections
   - `arena.bump_vec_into_slice()` - Convert a temporary vector to a permanent slice
   - `arena.alloc_str()` - Allocate string values
   - `arena.alloc_slice_copy()` - Allocate arrays of copyable types

3. **Return references from the arena**: The return type must be a reference to a value in the arena

### Working with Collections in Custom Operators

For operators that need to build collections:

```rust
fn evaluate<'a>(&self, args: &'a [DataValue<'a>], arena: &'a DataArena) -> Result<&'a DataValue<'a>> {
    // Create a temporary vector backed by the arena
    let mut temp_vec = arena.get_data_value_vec();
    
    // Add elements to it
    for i in 1..=5 {
        temp_vec.push(DataValue::Number(i.into()));
    }
    
    // Convert to a permanent slice in the arena
    let result_slice = arena.bump_vec_into_slice(temp_vec);
    
    // Create and return a DataValue array allocated in the arena
    Ok(arena.alloc(DataValue::Array(result_slice)))
}
```

### Accessing Data Context

Both custom operator types provide access to the current data context:

1. **SimpleOperatorFn**: The data context is directly passed as the second parameter
2. **CustomOperator trait**: The data context can be accessed via `arena.current_context(0)`

This allows custom operators to access values from the data independent of the arguments provided, enabling more flexible and powerful operations.

### Registration

To register a custom operator with DataLogic:

```rust
// For custom operator trait implementations
dl.register_custom_operator("operator_name", Box::new(OperatorImplementation));

// For simple function-based operators
dl.register_simple_operator("operator_name", function_name);
```

### Advanced Use Cases

Custom operators can be combined with built-in operators and data access:

```rust
// Calculate 2 * (base^2) * 3 where base comes from input data
let rule = r#"{
    "*": [
        2,
        {"pow": [{"var": "base"}, 2]},
        3
    ]
}"#;

let data = r#"{"base": 4}"#;

// With base = 4, this calculates 2 * 4² * 3 = 2 * 16 * 3 = 96
let result = dl.evaluate_str(rule, data, None)?;
```

For more examples, see the `examples/custom.rs` and `examples/custom_simple.rs` files in the repository.

## Complete API Reference

For a full list of available methods and types, refer to the Rust documentation:

```
cargo doc --open
```