jpx-engine 0.1.2

JMESPath query engine with introspection, discovery, and advanced features
Documentation

jpx-engine

Protocol-agnostic JMESPath query engine with 400+ functions.

This crate provides the core "brain" of jpx - everything you can do with JMESPath beyond basic compile and evaluate. It's designed to be transport-agnostic, allowing the CLI (jpx), MCP server (jpx-server), or any future REST/gRPC adapters to be thin wrappers over this engine.

Features

Category Description
Evaluation Single, batch, and string-based evaluation with validation
Introspection List functions, search by keyword, describe, find similar
Discovery Cross-server tool discovery with BM25 search indexing
Query Store Named queries for session-scoped reuse
JSON Utilities Format, diff, patch, merge, stats, paths, keys
Arrow Apache Arrow conversion (optional, via arrow feature)

Cargo Features

  • arrow - Enables Apache Arrow support for columnar data conversion. This adds the [arrow] module with functions to convert between Arrow RecordBatches and JSON Values. Used by the CLI for Parquet I/O.

Quick Start

use jpx_engine::JpxEngine;
use serde_json::json;

let engine = JpxEngine::new();

// Evaluate a JMESPath expression
let result = engine.evaluate("users[*].name", &json!({
    "users": [{"name": "alice"}, {"name": "bob"}]
})).unwrap();
assert_eq!(result, json!(["alice", "bob"]));

Evaluation

The engine supports multiple evaluation modes:

use jpx_engine::JpxEngine;
use serde_json::json;

let engine = JpxEngine::new();

// From parsed JSON
let data = json!({"items": [1, 2, 3]});
let result = engine.evaluate("length(items)", &data).unwrap();
assert_eq!(result, json!(3));

// From JSON string
let result = engine.evaluate_str("length(@)", r#"[1, 2, 3]"#).unwrap();
assert_eq!(result, json!(3));

// Batch evaluation (multiple expressions, same input)
let exprs = vec!["a".to_string(), "b".to_string()];
let batch = engine.batch_evaluate(&exprs, &json!({"a": 1, "b": 2}));
assert_eq!(batch.results[0].result, Some(json!(1)));

// Validation without evaluation
let valid = engine.validate("users[*].name");
assert!(valid.valid);

Function Introspection

Discover and explore the 400+ available functions:

use jpx_engine::JpxEngine;

let engine = JpxEngine::new();

// List all categories
let categories = engine.categories();
assert!(categories.contains(&"String".to_string()));

// List functions in a category
let string_funcs = engine.functions(Some("String"));
assert!(string_funcs.iter().any(|f| f.name == "upper"));

// Search by keyword (fuzzy matching, synonyms)
let results = engine.search_functions("upper", 5);
assert!(results.iter().any(|r| r.function.name == "upper"));

// Get detailed function info
let info = engine.describe_function("upper").unwrap();
assert_eq!(info.category, "String");

// Find similar functions
let similar = engine.similar_functions("upper").unwrap();
assert!(!similar.same_category.is_empty());

JSON Utilities

Beyond JMESPath evaluation, the engine provides JSON manipulation tools:

use jpx_engine::JpxEngine;

let engine = JpxEngine::new();

// Pretty-print JSON
let formatted = engine.format_json(r#"{"a":1}"#, 2).unwrap();
assert!(formatted.contains('\n'));

// Generate JSON Patch (RFC 6902)
let patch = engine.diff(r#"{"a": 1}"#, r#"{"a": 2}"#).unwrap();

// Apply JSON Patch
let result = engine.patch(
    r#"{"a": 1}"#,
    r#"[{"op": "replace", "path": "/a", "value": 2}]"#
).unwrap();

// Apply JSON Merge Patch (RFC 7396)
let merged = engine.merge(
    r#"{"a": 1, "b": 2}"#,
    r#"{"b": 3, "c": 4}"#
).unwrap();

// Analyze JSON structure
let stats = engine.stats(r#"[1, 2, 3]"#).unwrap();
assert_eq!(stats.root_type, "array");

Query Store

Store and reuse named queries within a session:

use jpx_engine::JpxEngine;
use serde_json::json;

let engine = JpxEngine::new();

// Define a reusable query
engine.define_query(
    "active_users".to_string(),
    "users[?active].name".to_string(),
    Some("Get names of active users".to_string())
).unwrap();

// Run it by name
let data = json!({"users": [
    {"name": "alice", "active": true},
    {"name": "bob", "active": false}
]});
let result = engine.run_query("active_users", &data).unwrap();
assert_eq!(result, json!(["alice"]));

// List all stored queries
let queries = engine.list_queries().unwrap();
assert_eq!(queries.len(), 1);

Tool Discovery

Register and search tools across multiple servers (for MCP integration):

use jpx_engine::{JpxEngine, DiscoverySpec};
use serde_json::json;

let engine = JpxEngine::new();

// Register a server's tools
let spec: DiscoverySpec = serde_json::from_value(json!({
    "server": {"name": "my-server", "version": "1.0.0"},
    "tools": [
        {"name": "create_user", "description": "Create a new user", "tags": ["write"]}
    ]
})).unwrap();

let result = engine.register_discovery(spec, false).unwrap();
assert!(result.ok);

// Search across registered tools
let tools = engine.query_tools("user", 10).unwrap();
assert!(!tools.is_empty());

Strict Mode

For standard JMESPath compliance without extensions:

use jpx_engine::JpxEngine;
use serde_json::json;

let engine = JpxEngine::strict();
assert!(engine.is_strict());

// Standard functions work
let result = engine.evaluate("length(@)", &json!([1, 2, 3])).unwrap();
assert_eq!(result, json!(3));

// Extension functions are not available for evaluation
// (but introspection still works for documentation purposes)

Architecture

jmespath-extensions    (400+ functions, registry)
        |
   jpx-engine          (this crate - evaluation, search, discovery)
        |
   +----+----+
   |         |
  jpx    jpx-server    (CLI and MCP transport)

Thread Safety

The engine uses interior mutability (Arc<RwLock<...>>) for the discovery registry and query store, making it safe to share across threads. The function registry is immutable after construction.