Skip to content

Commit cb7843a

Browse files
aepfliclaude
andauthored
chore: improvements (#44)
## Description <!-- Provide a brief description of your changes --> ## Related Issue <!-- Link to the related issue(s) --> Closes # ## Type of Change <!-- Mark the relevant option with an "x" --> - [ ] `feat`: New feature (minor version bump) - [ ] `fix`: Bug fix (patch version bump) - [ ] `docs`: Documentation only changes - [ ] `chore`: Maintenance tasks, dependency updates - [ ] `refactor`: Code refactoring without functional changes - [ ] `test`: Adding or updating tests - [ ] `ci`: CI/CD changes - [ ] `perf`: Performance improvements - [ ] `build`: Build system changes - [ ] `style`: Code style/formatting changes ## PR Title Format **IMPORTANT**: Since we use squash and merge, your PR title will become the commit message. Please ensure your PR title follows the [Conventional Commits](https://www.conventionalcommits.org/) format: ``` <type>(<optional-scope>): <description> ``` ### Examples: - `feat(operators): add new string comparison operator` - `fix(wasm): correct memory allocation bug` - `docs: update API examples in README` - `chore(deps): update rust dependencies` For breaking changes, use `!` after the type/scope or include `BREAKING CHANGE:` in the PR description: - `feat(api)!: redesign evaluation API` ## Testing <!-- Describe the testing you've performed --> - [ ] Unit tests added/updated - [ ] Integration tests added/updated - [ ] Manual testing performed - [ ] All tests pass (`cargo test`) - [ ] Code is formatted (`cargo fmt`) - [ ] Clippy checks pass (`cargo clippy -- -D warnings`) - [ ] WASM builds successfully (if applicable) ## Breaking Changes <!-- If this introduces breaking changes, describe them here --> - [ ] This PR includes breaking changes - [ ] Documentation has been updated to reflect breaking changes - [ ] Migration guide included (if needed) ## Additional Notes <!-- Any additional information, context, or screenshots --> --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent f34d55d commit cb7843a

52 files changed

Lines changed: 354 additions & 35116 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CLAUDE.md

Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
flagd-evaluator is a **WebAssembly-based JSON Logic evaluator** designed for feature flag evaluation. It's written in Rust and compiled to WASM (~2.4MB optimized binary) to provide consistent evaluation logic across all OpenFeature flagd providers (Java, JavaScript, .NET, Go, Python, PHP, etc.).
8+
9+
**Key Purpose**: This is the shared evaluation engine used by all in-process flagd providers to ensure uniform behavior across polyglot architectures. See the [flagd providers specification](https://github.com/open-feature/flagd/blob/main/docs/reference/specifications/providers.md) for integration details.
10+
11+
## Essential Commands
12+
13+
### Development
14+
15+
```bash
16+
# Build for native development/testing
17+
cargo build
18+
19+
# Build optimized WASM (production)
20+
cargo build --target wasm32-unknown-unknown --no-default-features --release --lib
21+
22+
# WASM output location
23+
# target/wasm32-unknown-unknown/release/flagd_evaluator.wasm
24+
25+
# Build CLI tool
26+
cargo build --release --bin flagd-eval
27+
```
28+
29+
### Testing
30+
31+
```bash
32+
# Run all tests
33+
cargo test
34+
35+
# Run specific test file
36+
cargo test --test integration_tests
37+
cargo test --test cli_tests
38+
39+
# Run specific test by name
40+
cargo test test_fractional_operator
41+
42+
# Run with output visible
43+
cargo test -- --nocapture
44+
```
45+
46+
### Code Quality
47+
48+
```bash
49+
# Format code (required before commit)
50+
cargo fmt
51+
52+
# Check formatting without changing files
53+
cargo fmt -- --check
54+
55+
# Lint code (must pass with no warnings)
56+
cargo clippy -- -D warnings
57+
```
58+
59+
### CLI Tool Usage
60+
61+
```bash
62+
# Evaluate a JSON Logic rule
63+
cargo run --bin flagd-eval -- eval --rule '{"==": [1, 1]}' --data '{}'
64+
65+
# Load rule/data from file (use @ prefix)
66+
cargo run --bin flagd-eval -- eval --rule @examples/rules/basic.json --data '{"age": 25}'
67+
68+
# Run test suite
69+
cargo run --bin flagd-eval -- test examples/rules/test-suite.json
70+
71+
# List available operators
72+
cargo run --bin flagd-eval -- operators
73+
```
74+
75+
## Architecture
76+
77+
### Core Design Principles
78+
79+
1. **WASM-First**: Compiled to WebAssembly for cross-language portability
80+
2. **No External Dependencies**: Single WASM file, no JNI, no JavaScript bindings
81+
3. **Chicory Compatible**: Works with pure Java WASM runtimes (no native code)
82+
4. **Memory Safe**: Explicit alloc/dealloc, no panics, all errors returned as JSON
83+
5. **Size Optimized**: Aggressive compilation flags (`opt-level = "z"`, LTO, panic = "abort")
84+
85+
### Module Organization
86+
87+
```
88+
src/
89+
├── lib.rs # Main entry point, WASM exports (evaluate_logic, update_state, evaluate)
90+
├── evaluation.rs # Core flag evaluation logic, context enrichment ($flagd properties)
91+
├── memory.rs # WASM memory management (alloc/dealloc, pointer packing)
92+
├── storage/ # Thread-local flag state storage
93+
├── operators/ # Custom JSON Logic operators
94+
│ ├── fractional.rs # MurmurHash3-based consistent bucketing for A/B testing
95+
│ ├── sem_ver.rs # Semantic version comparison (=, !=, <, <=, >, >=, ^, ~)
96+
│ ├── starts_with.rs # String prefix matching
97+
│ └── ends_with.rs # String suffix matching
98+
├── model/ # Flag configuration data structures
99+
├── validation.rs # JSON Schema validation against flagd schemas
100+
└── bin/
101+
└── flagd-eval.rs # CLI tool for testing rules without WASM
102+
```
103+
104+
### Key Architectural Concepts
105+
106+
**WASM Exports** (lib.rs:138-625):
107+
- `evaluate_logic(rule_ptr, rule_len, data_ptr, data_len) -> u64` - Direct JSON Logic evaluation
108+
- `update_state(config_ptr, config_len) -> u64` - Store flag configuration, returns changed flags
109+
- `evaluate(flag_key_ptr, flag_key_len, context_ptr, context_len) -> u64` - Evaluate stored flag
110+
- `alloc(len) -> *mut u8` - Allocate WASM memory
111+
- `dealloc(ptr, len)` - Free WASM memory
112+
- `set_validation_mode(mode) -> u64` - Set strict (0) or permissive (1) validation
113+
114+
All functions return **packed u64**: upper 32 bits = pointer, lower 32 bits = length.
115+
116+
**Memory Model**: Caller allocates input buffers, callee allocates result buffers. Caller must free all allocations. UTF-8 JSON strings for all inputs/outputs.
117+
118+
**Context Enrichment** (evaluation.rs): The evaluator automatically injects standard `$flagd` properties into evaluation context:
119+
- `$flagd.flagKey` - The flag being evaluated
120+
- `$flagd.timestamp` - Unix timestamp (seconds) at evaluation time
121+
- `targetingKey` - Defaults to empty string if not provided in context
122+
123+
**Custom Operators**: All registered via `datalogic_rs::Operator` trait (operators/mod.rs). See [flagd custom operations spec](https://flagd.dev/reference/specifications/custom-operations/) for full details.
124+
125+
**Validation**: Uses `jsonschema` crate to validate flag configs against [flagd-schemas](https://github.com/open-feature/flagd-schemas). Two modes:
126+
- Strict (default): Reject invalid configs
127+
- Permissive: Accept with warnings (for legacy compatibility)
128+
129+
**Flag State Management** (storage/mod.rs): Thread-local storage for flag configurations. `update_state` detects and reports changed flags (added, removed, or mutated).
130+
131+
## Important Implementation Details
132+
133+
### Building for WASM
134+
135+
**Critical Build Flags** (Cargo.toml:46-51):
136+
```toml
137+
[profile.release]
138+
opt-level = "z" # Optimize for size
139+
lto = true # Link-time optimization
140+
codegen-units = 1 # Single codegen unit for better optimization
141+
strip = true # Strip symbols
142+
panic = "abort" # Remove panic unwinding infrastructure
143+
```
144+
145+
**No Default Features for WASM**: Always build with `--no-default-features` to exclude CLI dependencies (clap, colored) from WASM binary.
146+
147+
### Memory Safety Rules
148+
149+
1. **Never panic in WASM exports**: All errors must be returned as JSON error responses
150+
2. **Always validate UTF-8**: Use `string_from_memory()` which returns Result
151+
3. **Pointer lifetime**: WASM memory is stable within a single function call but may be reallocated between calls
152+
4. **Safety comments required**: All `unsafe` blocks must have `// SAFETY:` comments explaining why they're safe
153+
154+
### Error Handling Patterns
155+
156+
**JSON Logic Evaluation** (lib.rs:173-175):
157+
```rust
158+
match logic.evaluate_json(&rule_str, &data_str) {
159+
Ok(result) => EvaluationResponse::success(result),
160+
Err(e) => EvaluationResponse::error(format!("{}", e)),
161+
}
162+
```
163+
164+
**Flag Evaluation** (evaluation.rs): Returns `EvaluationResult` with standardized error codes:
165+
- `FLAG_NOT_FOUND` - Flag key not in configuration
166+
- `PARSE_ERROR` - JSON parsing or rule evaluation error
167+
- `TYPE_MISMATCH` - Resolved value doesn't match expected type
168+
- `GENERAL` - Other errors
169+
170+
Resolution reasons: `STATIC`, `DEFAULT`, `TARGETING_MATCH`, `DISABLED`, `ERROR`, `FLAG_NOT_FOUND`
171+
172+
### Testing Philosophy
173+
174+
**Integration Tests** (tests/integration_tests.rs): 72 comprehensive tests covering:
175+
- Basic JSON Logic operations
176+
- All custom operators (fractional, sem_ver, starts_with, ends_with)
177+
- Memory management
178+
- Edge cases and error handling
179+
- State management and flag evaluation
180+
- Type-specific evaluation functions
181+
- Context enrichment ($flagd properties)
182+
183+
**CLI Tests** (tests/cli_tests.rs): End-to-end tests for the flagd-eval binary.
184+
185+
**When to Run Tests**:
186+
- ✅ After making code changes that affect behavior
187+
- ✅ Before creating a PR
188+
- ✅ When explicitly requested by user
189+
- ❌ During initial exploration or code reading
190+
- ❌ When just browsing documentation
191+
192+
### Common Workflows
193+
194+
**Adding a New Custom Operator**:
195+
1. Create new file in `src/operators/` (e.g., `my_operator.rs`)
196+
2. Implement `datalogic_rs::Operator` trait
197+
3. Register in `src/operators/mod.rs` via `create_evaluator()`
198+
4. Add tests in both unit tests and `tests/integration_tests.rs`
199+
5. Document in README.md under "Custom Operators" section
200+
6. Consider adding to CLI's `operators` command output
201+
202+
**Modifying Flag Evaluation Logic**:
203+
1. Primary logic is in `src/evaluation.rs`
204+
2. Context enrichment happens in `evaluate_flag()` function
205+
3. State retrieval uses thread-local storage via `get_flag_state()`
206+
4. Always maintain backward compatibility with flagd provider specification
207+
5. Test with targeting rules, disabled flags, and missing flags
208+
209+
**Memory Management Changes**:
210+
1. All WASM-facing functions must use packed u64 returns
211+
2. Use `string_to_memory()` to allocate and pack results
212+
3. Use `string_from_memory()` to read inputs (handles UTF-8 validation)
213+
4. Document caller responsibilities in function doc comments
214+
5. Test with the Java example in `examples/java/`
215+
216+
## Git Workflow & Commit Practices
217+
218+
**Make Regular Commits**: Commit your changes frequently with clear, descriptive messages. Don't wait until the end of a large feature to commit.
219+
220+
**Follow Conventional Commits Format**: Use the same format for regular commits as required for PR titles:
221+
222+
```
223+
<type>(<scope>): <description>
224+
225+
[optional body]
226+
227+
[optional footer]
228+
```
229+
230+
**Examples**:
231+
```bash
232+
# Feature commits
233+
git commit -m "feat(operators): add new string matching operator"
234+
git commit -m "feat(evaluation): support nested context properties"
235+
236+
# Bug fix commits
237+
git commit -m "fix(memory): correct pointer alignment in alloc function"
238+
git commit -m "fix(fractional): handle empty bucket key correctly"
239+
240+
# Documentation commits
241+
git commit -m "docs: update API examples in README"
242+
git commit -m "docs(operators): clarify sem_ver caret range behavior"
243+
244+
# Refactoring commits
245+
git commit -m "refactor(storage): simplify flag state management"
246+
247+
# Test commits
248+
git commit -m "test(fractional): add edge case for zero-weight buckets"
249+
250+
# Chore commits
251+
git commit -m "chore(deps): update datalogic-rs to 4.1"
252+
git commit -m "chore: remove unused chrono shim"
253+
```
254+
255+
**Commit Message Guidelines**:
256+
- Use imperative mood ("add feature" not "added feature")
257+
- Keep subject line under 72 characters
258+
- Add body for complex changes explaining why, not what
259+
- Reference issues when relevant: `Closes #123`
260+
261+
**When to Commit**:
262+
- ✅ After completing a logical unit of work
263+
- ✅ Before switching to a different task
264+
- ✅ After fixing a bug
265+
- ✅ After adding tests
266+
- ✅ Before taking a break or ending work session
267+
- ❌ Don't commit broken code (unless marked with WIP)
268+
- ❌ Don't commit commented-out code or debug statements
269+
270+
## Release Process
271+
272+
This project uses [Release Please](https://github.com/googleapis/release-please) for automated releases:
273+
274+
- **PR Titles Must Follow Conventional Commits**: The PR title becomes the commit message (squash merge)
275+
- Format: `<type>(<scope>): <description>`
276+
- Types that trigger releases:
277+
- `feat:` - Minor version bump (new feature)
278+
- `fix:` - Patch version bump (bug fix)
279+
- `perf:` - Patch version bump (performance improvement)
280+
- `feat!:` or `BREAKING CHANGE:` - Major version bump
281+
- Types for changelog only (no release): `docs:`, `chore:`, `test:`, `ci:`, `refactor:`, `style:`, `build:`
282+
283+
**PR Title Validation**: GitHub workflow (`.github/workflows/pr-title.yml`) automatically validates format.
284+
285+
## Dependencies
286+
287+
**Core Production**:
288+
- `datalogic-rs` (4.0) - JSON Logic implementation
289+
- `serde`, `serde_json` - JSON serialization (no_std compatible with alloc)
290+
- `jsonschema` (0.37) - Flag configuration validation
291+
292+
**CLI Only** (excluded from WASM):
293+
- `clap` - Command-line argument parsing
294+
- `colored` - Terminal colors
295+
296+
**Dev**:
297+
- `assert_cmd`, `predicates` - CLI integration testing
298+
299+
## Cross-Language Integration
300+
301+
This WASM module is embedded in multiple language providers. Key integration patterns:
302+
303+
**Java (Chicory)**:
304+
```java
305+
// Load WASM, get exports for alloc/dealloc/evaluate_logic
306+
// Allocate memory for inputs, write UTF-8 JSON
307+
// Call function, unpack u64 result
308+
// Read result from memory, deallocate all memory
309+
```
310+
311+
See `examples/java/FlagdEvaluatorExample.java` for complete working example.
312+
313+
**General Pattern**:
314+
1. Load WASM module
315+
2. Get function exports (alloc, dealloc, evaluate_logic, update_state, evaluate)
316+
3. For each call:
317+
- Allocate memory for inputs using `alloc()`
318+
- Write UTF-8 encoded JSON strings to WASM memory
319+
- Call evaluation function with pointers and lengths
320+
- Unpack returned u64 (ptr = upper 32 bits, len = lower 32 bits)
321+
- Read result JSON from WASM memory
322+
- Free all allocations using `dealloc()`
323+
324+
**Memory Lifecycle**: Host application owns all memory allocation/deallocation decisions. WASM module only allocates result memory internally.
325+
326+
## Related Documentation
327+
328+
- **Flagd Provider Specification**: https://github.com/open-feature/flagd/blob/main/docs/reference/specifications/providers.md
329+
- **In-Process Resolver**: https://github.com/open-feature/flagd/blob/main/docs/reference/specifications/providers.md#in-process-resolver
330+
- **Custom Operations Spec**: https://flagd.dev/reference/specifications/custom-operations/
331+
- **Flag Definitions Schema**: https://flagd.dev/reference/flag-definitions/
332+
- **JSON Logic**: https://jsonlogic.com/
333+
- **datalogic-rs**: https://github.com/cozylogic/datalogic-rs
334+
- **Chicory WASM Runtime**: https://github.com/nicknisi/chicory
335+
336+
## GitHub Copilot Instructions
337+
338+
The `.github/copilot-instructions.md` file contains extensive context about this project including:
339+
- Architecture and purpose
340+
- WASM function exports and memory management
341+
- Testing guidelines (when to run tests vs when to just read them)
342+
- Custom operators implementation details
343+
- Pull request conventions
344+
- Integration patterns with host languages
345+
346+
Refer to that file for additional context when working on this repository.

Cargo.toml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,6 @@ wasm-bindgen-test = "0.3"
3636
assert_cmd = "2.0"
3737
predicates = "3.0"
3838

39-
# Patch chrono to disable wasm-bindgen/js-sys for pure WASM runtimes like Chicory.
40-
# The patched chrono removes "wasmbind" from default features, preventing js-sys and
41-
# wasm-bindgen imports that aren't available in non-browser WASM environments.
42-
# See: https://github.com/chronotope/chrono/blob/main/Cargo.toml for original features.
43-
[patch.crates-io]
44-
chrono = { path = "third-party/chrono" }
45-
4639
[profile.release]
4740
opt-level = "z"
4841
lto = true

0 commit comments

Comments
 (0)