Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement full features as a derivative work of serde_json_path #20

Merged
merged 44 commits into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
c266dbc
test: move out spec tests
tisonkun Feb 5, 2025
e0bcd1e
do the reimpl
tisonkun Feb 5, 2025
1896b91
impl Ord for PathElement
tisonkun Feb 5, 2025
6641541
fixup
tisonkun Feb 5, 2025
ddbb918
core concept
tisonkun Feb 5, 2025
a6d928b
factor out select_wildcard
tisonkun Feb 5, 2025
662216f
clippy
tisonkun Feb 5, 2025
e73dfbd
drop Integer wrapper
tisonkun Feb 5, 2025
e70a90f
for slice
tisonkun Feb 5, 2025
a51e315
filters
tisonkun Feb 5, 2025
f2bd6e1
functions and ops
tisonkun Feb 5, 2025
a0d478e
fixup
tisonkun Feb 6, 2025
6a6325e
figure how to deal with literals
tisonkun Feb 6, 2025
3fe6ec8
clippy
tisonkun Feb 6, 2025
d635005
impl compare
tisonkun Feb 6, 2025
d4ef567
impl functions
tisonkun Feb 6, 2025
a40711b
drop type parameter from functions
tisonkun Feb 6, 2025
0c57d2c
propagate function registry
tisonkun Feb 6, 2025
41da1b9
break down functions
tisonkun Feb 6, 2025
e660d7c
builtin functions
tisonkun Feb 6, 2025
5344557
add spath facade
tisonkun Feb 6, 2025
e80f015
prepare for parser
tisonkun Feb 6, 2025
a0ac663
impl parse
tisonkun Feb 6, 2025
6395e4c
Revert "impl parse"
tisonkun Feb 6, 2025
3ee0cb9
impl parser
tisonkun Feb 7, 2025
c29fe6d
more parser
tisonkun Feb 7, 2025
334d3d0
remake function type
tisonkun Feb 7, 2025
5cd369d
impl parser
tisonkun Feb 7, 2025
67185b4
finish parser
tisonkun Feb 7, 2025
f652def
try stateful
tisonkun Feb 10, 2025
b16710c
properly use stateful
tisonkun Feb 12, 2025
05dbe40
rename R to Registry
tisonkun Feb 12, 2025
24a3714
impl parser
tisonkun Feb 12, 2025
c2db42b
fixup descend visit order
tisonkun Feb 12, 2025
af158f0
parse filter exprs
tisonkun Feb 12, 2025
0cc8fd2
comparable and function
tisonkun Feb 12, 2025
b4f07ff
fmt
tisonkun Feb 12, 2025
03f7126
impl filters
tisonkun Feb 12, 2025
5818211
add test
tisonkun Feb 12, 2025
a8a92e2
workaround ref
tisonkun Feb 13, 2025
97e46a6
fixup compare
tisonkun Feb 13, 2025
6e9fe97
finish builtin functions
tisonkun Feb 13, 2025
ff6cf91
fixup comp
tisonkun Feb 13, 2025
1916bc8
test
tisonkun Feb 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ license = "Apache-2.0"
readme = "README.md"
repository = "https://github.com/cratesland/spath"
rust-version = "1.80.0"
version = "0.2.0"

[workspace.dependencies]
spath = { version = "0.2.0", path = "spath" }

[workspace.lints.rust]
unknown_lints = "deny"
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,7 @@ fn main() {
## License

This project is licensed under [Apache License, Version 2.0](LICENSE).

## History

From 0.3.0, this crate is reimplemented as a fork of [serde_json_path](https://crates.io/crates/serde_json_path), with modifications to support other semi-structured data values.
8 changes: 6 additions & 2 deletions spath/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

[package]
name = "spath"
version = "0.2.0"

description = """
SPath is query expressions for semi-structured data. You can use it
Expand All @@ -28,6 +27,7 @@ license.workspace = true
readme.workspace = true
repository.workspace = true
rust-version.workspace = true
version.workspace = true

[package.metadata.docs.rs]
all-features = true
Expand All @@ -40,17 +40,21 @@ toml = ["dep:toml"]

[dependencies]
logos = { version = "0.15.0" }
num-cmp = { version = "0.1.0" }
num-traits = { version = "0.2.19" }
thiserror = { version = "2.0.8" }
winnow = { version = "0.7.2" }

# optional dependencies
serde_json = { version = "1.0.133", optional = true }
toml = { version = "0.8.19", optional = true }
toml = { version = "0.8.20", optional = true }

[dev-dependencies]
googletest = { version = "0.13.0" }
insta = { version = "1.41.1", features = ["json"] }
serde_json = { version = "1.0.133" }
spath = { workspace = true, features = ["json", "toml"] }
toml = { version = "0.8.19" }

[lints]
workspace = true
103 changes: 98 additions & 5 deletions spath/src/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,62 @@
// limitations under the License.

use serde_json::Map;
use serde_json::Number;
use serde_json::Value;

use crate::ConcreteVariantArray;
use crate::ConcreteVariantObject;
use crate::VariantValue;
use crate::spec::function::builtin::count;
use crate::spec::function::builtin::length;
use crate::spec::function::Function;
use crate::spec::function::FunctionRegistry;
use crate::value::ConcreteVariantArray;
use crate::value::ConcreteVariantObject;
use crate::value::VariantValue;
use crate::FromLiteral;
use crate::Literal;

#[derive(Debug, Clone, Copy)]
pub struct BuiltinFunctionRegistry;

impl FunctionRegistry for BuiltinFunctionRegistry {
type Value = Value;

fn get(&self, name: &str) -> Option<Function<Self::Value>> {
match name.to_lowercase().as_str() {
"length" => Some(length()),
"count" => Some(count()),
_ => None,
}
}
}

impl FromLiteral for Value {
fn from_literal(literal: Literal) -> Option<Self> {
match literal {
Literal::Int(v) => Some(Value::Number(Number::from(v))),
Literal::Float(v) => Number::from_f64(v).map(Value::Number),
Literal::String(v) => Some(Value::String(v)),
Literal::Bool(v) => Some(Value::Bool(v)),
Literal::Null => Some(Value::Null),
}
}
}

impl VariantValue for Value {
type VariantArray = Vec<Value>;
type VariantObject = Map<String, Value>;

fn is_null(&self) -> bool {
self.is_null()
}

fn is_boolean(&self) -> bool {
self.is_boolean()
}

fn is_string(&self) -> bool {
self.is_string()
}

fn is_array(&self) -> bool {
self.is_array()
}
Expand All @@ -31,6 +77,14 @@ impl VariantValue for Value {
self.is_object()
}

fn as_bool(&self) -> Option<bool> {
self.as_bool()
}

fn as_str(&self) -> Option<&str> {
self.as_str()
}

fn as_array(&self) -> Option<&Self::VariantArray> {
self.as_array()
}
Expand All @@ -39,8 +93,39 @@ impl VariantValue for Value {
self.as_object()
}

fn make_array(iter: impl IntoIterator<Item = Self>) -> Self {
Value::Array(iter.into_iter().collect())
fn is_less_than(&self, other: &Self) -> bool {
fn number_less_than(left: &Number, right: &Number) -> bool {
if let (Some(l), Some(r)) = (left.as_i128(), right.as_i128()) {
l < r
} else if let (Some(l), Some(r)) = (left.as_f64(), right.as_f64()) {
l < r
} else {
false
}
}

match (self, other) {
(Value::Number(n1), Value::Number(n2)) => number_less_than(n1, n2),
(Value::String(s1), Value::String(s2)) => s1 < s2,
_ => false,
}
}

fn is_equal_to(&self, other: &Self) -> bool {
fn number_equal_to(left: &Number, right: &Number) -> bool {
if let (Some(l), Some(r)) = (left.as_i128(), right.as_i128()) {
l == r
} else if let (Some(l), Some(r)) = (left.as_f64(), right.as_f64()) {
l == r
} else {
false
}
}

match (self, other) {
(Value::Number(a), Value::Number(b)) => number_equal_to(a, b),
_ => self == other,
}
}
}

Expand Down Expand Up @@ -79,6 +164,14 @@ impl ConcreteVariantObject for Map<String, Value> {
self.get(key)
}

fn get_key_value(&self, key: &str) -> Option<(&String, &Self::Value)> {
self.get_key_value(key)
}

fn iter(&self) -> impl Iterator<Item = (&String, &Self::Value)> {
self.iter()
}

fn values(&self) -> impl Iterator<Item = &Self::Value> {
self.values()
}
Expand Down
99 changes: 17 additions & 82 deletions spath/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,96 +13,31 @@
// limitations under the License.

//! # SPath: Query expressions for semi-structured data
//!
//! You can use it as a drop-in replacement for JSONPath, but also for other semi-structured data
//! formats like TOML or user-defined variants.
//!
//! ## Examples
//!
//! ### JSON
//!
//! Here is a quick example that shows how to use the `spath` crate to query
//! JSONPath alike expression over JSON data:
//!
//! ```rust
//! # #[cfg(feature = "json")]
//! # {
//! use serde_json::json;
//! use serde_json::Value;
//! use spath::SPath;
//! use spath::VariantValue;
//!
//! let data = json!({
//! "name": "John Doe",
//! "age": 43,
//! "phones": [
//! "+44 1234567",
//! "+44 2345678"
//! ]
//! });
//!
//! let spath = SPath::new("$.phones[1]").unwrap();
//! let result = spath.eval(&data).unwrap();
//! assert_eq!(result, json!("+44 2345678"));
//! # }
//! ```
//!
//! ### TOML
//!
//! Here is a quick example that shows how to use the `spath` crate to query
//! JSONPath alike expression over TOML data:
//!
//! ```rust
//! # #[cfg(feature = "toml")]
//! # {
//! use spath::SPath;
//! use spath::VariantValue;
//! use toml::Value;
//!
//! let data = r#"
//! [package]
//! name = "spath"
//! version = "0.1.0"
//! authors = ["tison"]
//!
//! [dependencies]
//! serde = "1.0"
//! "#;
//!
//! let data = data.parse::<Value>().unwrap();
//! let spath = SPath::new("$.package.name").unwrap();
//! let result = spath.eval(&data).unwrap();
//! assert_eq!(result, Value::String("spath".to_string()));
//! # }
//! ```
//!
//! ## Feature flags
//!
//! - `json`: impl [`VariantValue`] for `serde_json::Value`.
//! - `toml`: impl [`VariantValue`] for `toml::Value`.

#![cfg_attr(docsrs, feature(doc_auto_cfg))]

mod value;
pub use value::*;
mod node;
pub use node::*;

mod error;
pub use error::*;
mod path;
pub use path::*;

mod spath;
pub use spath::*;

#[cfg(any(feature = "json", test))]
mod json;
pub mod spec;

mod value;
pub use value::*;

mod parser;
#[cfg(feature = "toml")]
mod toml;

#[cfg(test)]
mod tests;
#[cfg(feature = "json")]
pub mod json;
#[cfg(feature = "toml")]
pub mod toml;

#[cfg(test)]
fn manifest_dir() -> std::path::PathBuf {
let dir = env!("CARGO_MANIFEST_DIR");
std::path::PathBuf::from(dir).canonicalize().unwrap()
}
/// An error that can occur during parsing the SPath query.
#[derive(Debug, thiserror::Error)]
#[error("{0}")]
pub struct ParseError(pub String);
Loading