diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8fecf25..0000000 --- a/.travis.yml +++ /dev/null @@ -1,17 +0,0 @@ -language: rust -rust: - - stable - - beta - - nightly -os: - - linux - - osx -script: - - cargo build --verbose - - cargo test --verbose - - cargo run --example create_from_str_array - - cargo doc -env: - - QUICKCHECK_TESTS=10000 QUICKCHECK_MAX_TESTS=1000000 -matrix: - fast_finish: true diff --git a/Cargo.toml b/Cargo.toml index ec1dfc5..620e4ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,24 +1,21 @@ [package] -authors = ["Nathaniel Ringo "] -description = "A crate for parsing and using JSON pointers, as specified in RFC 6901." -documentation = "https://docs.rs/json-pointer" -homepage = "https://github.com/remexre/json-pointer" +authors = ["Nathaniel Ringo ", "Martin Bartlett "] +description = "A crate for parsing and using JSON pointers with simd_json, as specified in RFC 6901." +documentation = "https://docs.rs/json-pointer-simd" +homepage = "https://github.com/bassmanitram/json-pointer-simd" license = "MIT" -name = "json-pointer" +name = "json-pointer-simd" readme = "README.md" -repository = "https://github.com/remexre/json-pointer" -version = "0.3.2" - -[badges] - -[badges.travis-ci] -branch = "master" -repository = "remexre/json-pointer" +repository = "https://github.com/bassmanitram/json-pointer-simd" +version = "0.3.3" +edition = "2021" [dependencies] -serde_json = "^1.0.2" +serde_json = "1.0" +simd-json = "0" [dev-dependencies] lazy_static = "^0.2.8" -quickcheck = "^0.4.1" -regex = "^0.2.2" +regex = "1.10.2" +quickcheck = "1.0.3" +once_cell = "1.19.0" diff --git a/README.md b/README.md index 3ddd41e..e6fd5e6 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,29 @@ # json-pointer +## Preamble +This crate is a generalization of the [json-pointer](https://github.com/remexre/json-pointer) crate. + +It opens up the target of the JSON pointer to anything that can be adapted using the `JsonPointerTarget` +trait. The odd name of the crate (_.simd_) comes from the first use case and first attempt at implementation - +- using JSON Pointers with [simd-json](https://docs.rs/simd-json/latest/simd_json) Values. + +But one learns in these efforts and the back-implementation of the `JsonPointerTarget` trait to re-include +[serde_json] values became pretty obvious pretty quickly! + +HOPEfully, then, this crate is a stop-gap to getting all this merged back into `json-pointer` at some point in +the future. Before then there is a lot to do -features, tests, docs, better semantics ... + +Apart from the `JsonPointerTarget`-related refactoring, I have also made some updates to the code to use the 2021 +semantics of Rust. + +Otherwise, all the code, examples, and tests are those of the original author. + +## Read me A crate for parsing and using JSON pointers, as specified in [RFC 6901](https://tools.ietf.org/html/rfc6901). Unlike the `pointer` method built into `serde_json`, this handles both validating JSON Pointers before use and the URI Fragment Identifier Representation. -[![Build Status](https://travis-ci.org/remexre/json-pointer.svg?branch=master)](https://travis-ci.org/remexre/json-pointer) -[![crates.io](https://img.shields.io/crates/v/json-pointer.svg)](https://crates.io/crates/json-pointer) -[![Documentation](https://docs.rs/json-pointer/badge.svg)](https://docs.rs/json-pointer) - ## Creating a JSON Pointer JSON pointers can be created with a literal `[&str]`, or parsed from a `String`. @@ -19,9 +34,9 @@ let from_strs = JsonPointer::new([ "bar", ]); let parsed = "/foo/bar".parse::>().unwrap(); +let from_dotted_notation = JsonPointer::new("foo.bar".split('.').collect::>()); assert_eq!(from_strs.to_string(), parsed.to_string()); -} ``` ## Using a JSON Pointer @@ -40,7 +55,7 @@ let document = json!({ "quux": "xyzzy" }); -let indexed = ptr.get(&document).unwrap(); +let indexed = document.get(&ptr).unwrap(); assert_eq!(indexed, &json!(0)); ``` diff --git a/examples/create_from_str_array.rs b/examples/create_from_str_array.rs index 7ccb416..dd09c7f 100644 --- a/examples/create_from_str_array.rs +++ b/examples/create_from_str_array.rs @@ -1,8 +1,5 @@ -extern crate json_pointer; -#[macro_use] -extern crate serde_json; - -use json_pointer::JsonPointer; +use json_pointer_simd::{JsonPointer,JsonPointerTarget}; +use simd_json::json; // or serde_json::json fn main() { let ptr = JsonPointer::new([ @@ -20,7 +17,7 @@ fn main() { "quux": "xyzzy" }); - let indexed = ptr.get(&document).unwrap(); + let indexed = document.get(&ptr).unwrap(); assert_eq!(indexed, &json!(0)); } diff --git a/src/borrowed.rs b/src/borrowed.rs new file mode 100644 index 0000000..f38da97 --- /dev/null +++ b/src/borrowed.rs @@ -0,0 +1,84 @@ +use std::ops::{Index, IndexMut}; +use crate::{JsonPointer, JsonPointerTarget, IndexError}; +use simd_json::value::borrowed::Value; + +/// Implement getting for SIMD JSON Borrowed values +/// +impl<'a> JsonPointerTarget for Value<'a> + where Self: Sized { + fn get<'json, S: AsRef, C: AsRef<[S]>>(&'json self, ptr: &JsonPointer) -> Result<&'json Self, IndexError> { + ptr.ref_toks.as_ref().iter().try_fold(self, |val, tok| { + let tok = tok.as_ref(); + match *val { + Value::Object(ref obj) => obj.get(tok).ok_or_else(|| IndexError::NoSuchKey(tok.to_owned())), + Value::Array(ref arr) => { + let idx = if tok == "-" { + arr.len() + } else if let Ok(idx) = tok.parse() { + idx + } else { + return Err(IndexError::NoSuchKey(tok.to_owned())); + }; + arr.get(idx).ok_or(IndexError::OutOfBounds(idx)) + }, + _ => Err(IndexError::NotIndexable), + } + }) + } + + fn get_mut<'json, S: AsRef, C: AsRef<[S]>>(&'json mut self, ptr: &JsonPointer) -> Result<&'json mut Self, IndexError> { + ptr.ref_toks.as_ref().iter().try_fold(self, |val, tok| { + let tok = tok.as_ref(); + match *val { + Value::Object(ref mut obj) => obj.get_mut(tok).ok_or_else(|| IndexError::NoSuchKey(tok.to_owned())), + Value::Array(ref mut arr) => { + let idx = if tok == "-" { + arr.len() + } else if let Ok(idx) = tok.parse() { + idx + } else { + return Err(IndexError::NoSuchKey(tok.to_owned())); + }; + arr.get_mut(idx).ok_or(IndexError::OutOfBounds(idx)) + }, + _ => Err(IndexError::NotIndexable), + } + }) + } + + fn get_owned<'json, S: AsRef, C: AsRef<[S]>>(self, ptr: &JsonPointer) -> Result { + ptr.ref_toks.as_ref().iter().try_fold(self, |val, tok| { + let tok = tok.as_ref(); + match val { + Value::Object(mut obj) => obj.remove(tok).ok_or_else(|| IndexError::NoSuchKey(tok.to_owned())), + Value::Array(mut arr) => { + let idx = if tok == "-" { + arr.len() + } else if let Ok(idx) = tok.parse() { + idx + } else { + return Err(IndexError::NoSuchKey(tok.to_owned())); + }; + if idx >= arr.len() { + Err(IndexError::OutOfBounds(idx)) + } else { + Ok(arr.swap_remove(idx)) + } + }, + _ => Err(IndexError::NotIndexable), + } + }) + } +} +impl<'a, S: AsRef, C: AsRef<[S]>> Index<&'a JsonPointer> for Value<'a> { + type Output = Value<'a>; + fn index(&self, ptr: &'a JsonPointer) -> &Value<'a> { + self.get(ptr).unwrap() + } +} + +impl<'a, S: AsRef, C: AsRef<[S]>> IndexMut<&'a JsonPointer> for Value<'a> { + fn index_mut(&mut self, ptr: &'a JsonPointer) -> &mut Value<'a> { + self.get_mut(ptr).unwrap() + } +} diff --git a/src/lib.rs b/src/lib.rs index ee1ab05..eed939e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,59 +1,79 @@ -//! A crate for parsing and using JSON pointers, as specified in [RFC -//! 6901](https://tools.ietf.org/html/rfc6901). Unlike the `pointer` method -//! built into `serde_json`, this handles both validating JSON Pointers before -//! use and the URI Fragment Identifier Representation. -//! -//! [![Build Status](https://travis-ci.org/remexre/json-pointer.svg?branch=master)](https://travis-ci.org/remexre/json-pointer) -//! [![crates.io](https://img.shields.io/crates/v/json-pointer.svg)](https://crates.io/crates/json-pointer) -//! [![Documentation](https://docs.rs/json-pointer/badge.svg)](https://docs.rs/json-pointer) +//! A crate for parsing and using JSON pointers with [simd_json] and [serde_json] values. +//! +//! The functionality is specified in [RFC 6901](https://tools.ietf.org/html/rfc6901). +//! +//! In the case of [serde_json], unlike nlike the `pointer` method, this handles both +//! validating JSON Pointers before use and the URI Fragment Identifier Representation. //! +//! In the case of [simd_json], this crate provides that missing functionality. +//! //! ## Creating a JSON Pointer //! -//! JSON pointers can be created with a literal `[&str]`, or parsed from a `String`. +//! JSON pointers can be parsed from any thing that can interpreted a s string slice +//! expressed in standard JSON Pointer syntax, or created from anything that can be +//! loosely represented as a vector or array of `&str`. //! //! ```rust -//! extern crate json_pointer; -//! -//! use json_pointer::JsonPointer; +//! use json_pointer_simd::{JsonPointer,JsonPointerTarget}; //! -//! fn main() { -//! let from_strs = JsonPointer::new([ -//! "foo", -//! "bar", -//! ]); -//! let parsed = "/foo/bar".parse::>().unwrap(); -//! -//! assert_eq!(from_strs.to_string(), parsed.to_string()); -//! } +//! let from_strs = JsonPointer::new([ +//! "foo", +//! "bar", +//! ]); +//! let parsed = "/foo/bar".parse::>().unwrap(); +//! let from_dotted_notation = JsonPointer::new("foo.bar".split('.').collect::>()); +//! +//! assert_eq!(from_strs.to_string(), parsed.to_string()); +//! assert_eq!(from_strs.to_string(), from_dotted_notation.to_string()); //! ``` //! //! ## Using a JSON Pointer //! -//! The `JsonPointer` type provides `.get()` and `.get_mut()`, to get references +//! The `JsonPointerTarget` trait provides `.get()` and `.get_mut()`, to get references //! and mutable references to the appropriate value, respectively. //! +//! As delivered, this is implemented on [serde_json] values and [simd_json] values, though +//! the former is a little more verbose to use than the latter due to the pre-existence of +//! these methods on [serde_json] values +//! +//! For [simd_json]: +//! //! ```rust -//! extern crate json_pointer; -//! #[macro_use] -//! extern crate serde_json; +//! use simd_json::json; +//! use json_pointer_simd::{JsonPointer,JsonPointerTarget}; //! -//! use json_pointer::JsonPointer; -//! -//! fn main() { -//! let ptr = "/foo/bar".parse::>().unwrap(); +//! let ptr = "/foo/bar".parse::>().unwrap(); //! -//! let document = json!({ -//! "foo": { -//! "bar": 0, -//! "baz": 1, -//! }, -//! "quux": "xyzzy" -//! }); +//! let document = json!({ +//! "foo": { +//! "bar": 0, +//! "baz": 1, +//! }, +//! "quux": "xyzzy" +//! }); +//! let indexed = document.get(&ptr).unwrap(); //! -//! let indexed = ptr.get(&document).unwrap(); +//! assert_eq!(indexed, &json!(0)); +//! ``` //! -//! assert_eq!(indexed, &json!(0)); -//! } +//! For [serde_json]: +//! +//! ```rust +//! use serde_json::{json, Value}; +//! use json_pointer_simd::{JsonPointer,JsonPointerTarget}; +//! +//! let ptr = "/foo/bar".parse::>().unwrap(); +//! +//! let document = json!({ +//! "foo": { +//! "bar": 0, +//! "baz": 1, +//! }, +//! "quux": "xyzzy" +//! }); +//! let indexed = ::get(&document,&ptr).unwrap(); +//! +//! assert_eq!(indexed, &json!(0)); //! ``` //! //! ## URI Fragment Identifier Representation @@ -65,25 +85,39 @@ //! crate does not support parsing full URIs. //! //! ```rust -//! extern crate json_pointer; +//! use json_pointer_simd::{JsonPointer,JsonPointerTarget}; //! -//! use json_pointer::JsonPointer; -//! -//! fn main() { -//! let str_ptr = "/f%o".parse::>().unwrap(); -//! let uri_ptr = "#/f%25o".parse::>().unwrap(); +//! let str_ptr = "/f%o".parse::>().unwrap(); +//! let uri_ptr = "#/f%25o".parse::>().unwrap(); //! -//! assert_eq!(str_ptr, uri_ptr); -//! } +//! assert_eq!(str_ptr, uri_ptr); //! ``` #![deny(missing_docs)] -extern crate serde_json; - mod parser; +mod owned; +mod borrowed; +mod value; mod ptr; pub use parser::ParseError; pub use ptr::IndexError; pub use ptr::JsonPointer; + +/// +/// The trait that provides access to the data referenced by the JsonPointer. +/// +pub trait JsonPointerTarget + where Self: Sized{ + + /// Attempts to get a reference to a value from self, + /// returning an error if it can't be found. + fn get<'json,S: AsRef, C: AsRef<[S]>>(&'json self, ptr: &JsonPointer) -> Result<&'json Self, IndexError>; + /// Attempts to get a mutable reference to a value from self + /// returning an error if it can't be found. + fn get_mut<'json,S: AsRef, C: AsRef<[S]>>(&'json mut self, ptr: &JsonPointer) -> Result<&'json mut Self, IndexError>; + /// Attempts to get an owned value from self, returning an + /// error if it can't be found. + fn get_owned, C: AsRef<[S]>>(self, ptr: &JsonPointer) -> Result; +} diff --git a/src/owned.rs b/src/owned.rs new file mode 100644 index 0000000..d85b789 --- /dev/null +++ b/src/owned.rs @@ -0,0 +1,92 @@ +use std::ops::{Index, IndexMut}; +use crate::{JsonPointer, JsonPointerTarget, IndexError}; +use simd_json::value::owned::Value; + +/// +/// Implement getting for SIMD JSON Owned values +/// +impl JsonPointerTarget for Value { + + /// Attempts to get a reference to a value from the given JSON value, + /// returning an error if it can't be found. + fn get<'value, S: AsRef, C: AsRef<[S]>>(&'value self, ptr: &JsonPointer) -> Result<&'value Self, IndexError> { + ptr.ref_toks.as_ref().iter().try_fold(self, |val, tok| { + let tok = tok.as_ref(); + match *val { + Value::Object(ref obj) => obj.get(tok).ok_or_else(|| IndexError::NoSuchKey(tok.to_owned())), + Value::Array(ref arr) => { + let idx = if tok == "-" { + arr.len() + } else if let Ok(idx) = tok.parse() { + idx + } else { + return Err(IndexError::NoSuchKey(tok.to_owned())); + }; + arr.get(idx).ok_or(IndexError::OutOfBounds(idx)) + }, + _ => Err(IndexError::NotIndexable), + } + }) + } + + /// Attempts to get a mutable reference to a value from the given JSON + /// value, returning an error if it can't be found. + fn get_mut<'value, S: AsRef, C: AsRef<[S]>>(&'value mut self, ptr: &JsonPointer) -> Result<&'value mut Self, IndexError> { + ptr.ref_toks.as_ref().iter().try_fold(self, |val, tok| { + let tok = tok.as_ref(); + match *val { + Value::Object(ref mut obj) => obj.get_mut(tok).ok_or_else(|| IndexError::NoSuchKey(tok.to_owned())), + Value::Array(ref mut arr) => { + let idx = if tok == "-" { + arr.len() + } else if let Ok(idx) = tok.parse() { + idx + } else { + return Err(IndexError::NoSuchKey(tok.to_owned())); + }; + arr.get_mut(idx).ok_or(IndexError::OutOfBounds(idx)) + }, + _ => Err(IndexError::NotIndexable), + } + }) + } + + /// Attempts to get an owned value from the given JSON value, returning an + /// error if it can't be found. + fn get_owned<'value, S: AsRef, C: AsRef<[S]>>(self, ptr: &JsonPointer) -> Result { + ptr.ref_toks.as_ref().iter().try_fold(self, |val, tok| { + let tok = tok.as_ref(); + match val { + Value::Object(mut obj) => obj.remove(tok).ok_or_else(|| IndexError::NoSuchKey(tok.to_owned())), + Value::Array(mut arr) => { + let idx = if tok == "-" { + arr.len() + } else if let Ok(idx) = tok.parse() { + idx + } else { + return Err(IndexError::NoSuchKey(tok.to_owned())); + }; + if idx >= arr.len() { + Err(IndexError::OutOfBounds(idx)) + } else { + Ok(arr.swap_remove(idx)) + } + }, + _ => Err(IndexError::NotIndexable), + } + }) + } +} + +impl<'a, S: AsRef, C: AsRef<[S]>> Index<&'a JsonPointer> for Value { + type Output = Value; + fn index(&self, ptr: &'a JsonPointer) -> &Value { + self.get(ptr).unwrap() + } +} + +impl<'a, S: AsRef, C: AsRef<[S]>> IndexMut<&'a JsonPointer> for Value { + fn index_mut(&mut self, ptr: &'a JsonPointer) -> &mut Value { + self.get_mut(ptr).unwrap() + } +} diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 0c9c4d5..07d68fe 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1,12 +1,12 @@ mod string_repr; mod uri_fragment; -use JsonPointer; +use crate::JsonPointer; /// A parser for JSON pointers. If the string starts with a `#`, it is parsed /// as a URI fragment. Otherwise, it is parsed in the string representation. pub fn parse(s: &str) -> Result>, ParseError> { - if s.chars().next() == Some('#') { + if s.starts_with('#') { let s = uri_fragment::UnescapeIter::new(s.chars().skip(1)).collect::>()?; string_repr::parse(s.chars()) } else { diff --git a/src/parser/string_repr.rs b/src/parser/string_repr.rs index a3ba145..e0e37f2 100644 --- a/src/parser/string_repr.rs +++ b/src/parser/string_repr.rs @@ -1,7 +1,6 @@ //! A parser for JSON pointers. - -use JsonPointer; -use parser::ParseError; +use crate::parser::ParseError; +use crate::JsonPointer; /// A single token encountered when parsing a JSON pointer. #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] @@ -23,10 +22,9 @@ pub enum Escape { /// The `/` character, which is escaped as `~1`. Slash = 1, } - -impl Into for Escape { - fn into(self) -> char { - match self { +impl From for char { + fn from(val: Escape) -> Self { + match val { Escape::Tilde => '~', Escape::Slash => '/', } @@ -43,7 +41,7 @@ pub fn parse>(ii: II) -> Result 0).unwrap_or(false) { + if iter.next().map(|s| !s.is_empty()).unwrap_or(false) { return Err(ParseError::NoLeadingSlash); } diff --git a/src/parser/uri_fragment.rs b/src/parser/uri_fragment.rs index 6424502..e682a69 100644 --- a/src/parser/uri_fragment.rs +++ b/src/parser/uri_fragment.rs @@ -1,4 +1,4 @@ -use parser::ParseError; +use crate::parser::ParseError; /// An iterator that unescapes URI fragments. pub struct UnescapeIter> { diff --git a/src/ptr.rs b/src/ptr.rs index 57aeeb0..bd8e3d9 100644 --- a/src/ptr.rs +++ b/src/ptr.rs @@ -1,19 +1,19 @@ use parser::{parse, ParseError}; -use serde_json::Value; use std::fmt::{Display, Formatter}; use std::fmt::Result as FmtResult; use std::fmt::Write; use std::marker::PhantomData; -use std::ops::{Index, IndexMut}; use std::str::FromStr; +use crate::parser; + /// A JSON Pointer. /// /// Create a new JSON pointer with [`JsonPointer::new`](#method.new), or parse one from a /// string with [`str::parse()`](https://doc.rust-lang.org/std/primitive.str.html#method.parse). #[derive(Clone, Debug, Eq, PartialEq)] pub struct JsonPointer, C: AsRef<[S]>> { - ref_toks: C, + pub(crate) ref_toks: C, _phantom: PhantomData, } @@ -21,89 +21,16 @@ impl, C: AsRef<[S]>> JsonPointer { /// Creates a new JsonPointer from the given reference tokens. pub fn new(ref_toks: C) -> JsonPointer { JsonPointer { - ref_toks: ref_toks, + ref_toks, _phantom: PhantomData, } } - /// Attempts to get a reference to a value from the given JSON value, - /// returning an error if it can't be found. - pub fn get<'json>(&self, val: &'json Value) -> Result<&'json Value, IndexError> { - self.ref_toks.as_ref().iter().fold(Ok(val), |val, tok| val.and_then(|val| { - let tok = tok.as_ref(); - match *val { - Value::Object(ref obj) => obj.get(tok).ok_or_else(|| IndexError::NoSuchKey(tok.to_owned())), - Value::Array(ref arr) => { - let idx = if tok == "-" { - arr.len() - } else if let Ok(idx) = tok.parse() { - idx - } else { - return Err(IndexError::NoSuchKey(tok.to_owned())); - }; - arr.get(idx).ok_or(IndexError::OutOfBounds(idx)) - }, - _ => Err(IndexError::NotIndexable), - } - })) - } - - /// Attempts to get a mutable reference to a value from the given JSON - /// value, returning an error if it can't be found. - pub fn get_mut<'json>(&self, val: &'json mut Value) -> Result<&'json mut Value, IndexError> { - self.ref_toks.as_ref().iter().fold(Ok(val), |val, tok| val.and_then(|val| { - let tok = tok.as_ref(); - match *val { - Value::Object(ref mut obj) => obj.get_mut(tok).ok_or_else(|| IndexError::NoSuchKey(tok.to_owned())), - Value::Array(ref mut arr) => { - let idx = if tok == "-" { - arr.len() - } else if let Ok(idx) = tok.parse() { - idx - } else { - return Err(IndexError::NoSuchKey(tok.to_owned())); - }; - arr.get_mut(idx).ok_or(IndexError::OutOfBounds(idx)) - }, - _ => Err(IndexError::NotIndexable), - } - })) - } - - /// Attempts to get an owned value from the given JSON value, returning an - /// error if it can't be found. - pub fn get_owned(&self, val: Value) -> Result { - self.ref_toks.as_ref().iter().fold(Ok(val), |val, tok| val.and_then(|val| { - let tok = tok.as_ref(); - match val { - Value::Object(mut obj) => obj.remove(tok).ok_or_else(|| IndexError::NoSuchKey(tok.to_owned())), - Value::Array(mut arr) => { - let idx = if tok == "-" { - arr.len() - } else if let Ok(idx) = tok.parse() { - idx - } else { - return Err(IndexError::NoSuchKey(tok.to_owned())); - }; - if idx >= arr.len() { - Err(IndexError::OutOfBounds(idx)) - } else { - Ok(arr.swap_remove(idx)) - } - }, - _ => Err(IndexError::NotIndexable), - } - })) - } - /// Converts a JSON pointer to a string in URI Fragment Identifier /// Representation, including the leading `#`. pub fn uri_fragment(&self) -> String { fn legal_fragment_byte(b: u8) -> bool { - match b { - 0x21 | 0x24 | 0x26...0x3b | 0x3d | 0x3f...0x5a | 0x5f | 0x61...0x7a => true, - _ => false, - } + matches!(b, 0x21 | 0x24 | 0x26..=0x3b | 0x3d | 0x3f..=0x5a | 0x5f | 0x61..=0x7a) } let mut s = "#".to_string(); @@ -166,16 +93,3 @@ pub enum IndexError { /// The pointer pointed to an out-of-bounds value in an array. OutOfBounds(usize), } - -impl<'a, S: AsRef, C: AsRef<[S]>> Index<&'a JsonPointer> for Value { - type Output = Value; - fn index(&self, ptr: &'a JsonPointer) -> &Value { - ptr.get(self).unwrap() - } -} - -impl<'a, S: AsRef, C: AsRef<[S]>> IndexMut<&'a JsonPointer> for Value { - fn index_mut(&mut self, ptr: &'a JsonPointer) -> &mut Value { - ptr.get_mut(self).unwrap() - } -} diff --git a/src/value.rs b/src/value.rs new file mode 100644 index 0000000..06726dd --- /dev/null +++ b/src/value.rs @@ -0,0 +1,91 @@ +use std::ops::{Index, IndexMut}; +use crate::{JsonPointer, JsonPointerTarget, IndexError}; +use serde_json::Value; + +/// Implement getting for SIMD JSON Owned values +/// +impl JsonPointerTarget for Value { + + /// Attempts to get a reference to a value from the given JSON value, + /// returning an error if it can't be found. + fn get<'value, S: AsRef, C: AsRef<[S]>>(&'value self, ptr: &JsonPointer) -> Result<&'value Self, IndexError> { + ptr.ref_toks.as_ref().iter().try_fold(self, |val, tok| { + let tok = tok.as_ref(); + match *val { + Value::Object(ref obj) => obj.get(tok).ok_or_else(|| IndexError::NoSuchKey(tok.to_owned())), + Value::Array(ref arr) => { + let idx = if tok == "-" { + arr.len() + } else if let Ok(idx) = tok.parse() { + idx + } else { + return Err(IndexError::NoSuchKey(tok.to_owned())); + }; + arr.get(idx).ok_or(IndexError::OutOfBounds(idx)) + }, + _ => Err(IndexError::NotIndexable), + } + }) + } + + /// Attempts to get a mutable reference to a value from the given JSON + /// value, returning an error if it can't be found. + fn get_mut<'value, S: AsRef, C: AsRef<[S]>>(&'value mut self, ptr: &JsonPointer) -> Result<&'value mut Self, IndexError> { + ptr.ref_toks.as_ref().iter().try_fold(self, |val, tok| { + let tok = tok.as_ref(); + match *val { + Value::Object(ref mut obj) => obj.get_mut(tok).ok_or_else(|| IndexError::NoSuchKey(tok.to_owned())), + Value::Array(ref mut arr) => { + let idx = if tok == "-" { + arr.len() + } else if let Ok(idx) = tok.parse() { + idx + } else { + return Err(IndexError::NoSuchKey(tok.to_owned())); + }; + arr.get_mut(idx).ok_or(IndexError::OutOfBounds(idx)) + }, + _ => Err(IndexError::NotIndexable), + } + }) + } + + /// Attempts to get an owned value from the given JSON value, returning an + /// error if it can't be found. + fn get_owned, C: AsRef<[S]>>(self, ptr: &JsonPointer) -> Result { + ptr.ref_toks.as_ref().iter().try_fold(self, |val, tok| { + let tok = tok.as_ref(); + match val { + Value::Object(mut obj) => obj.remove(tok).ok_or_else(|| IndexError::NoSuchKey(tok.to_owned())), + Value::Array(mut arr) => { + let idx = if tok == "-" { + arr.len() + } else if let Ok(idx) = tok.parse() { + idx + } else { + return Err(IndexError::NoSuchKey(tok.to_owned())); + }; + if idx >= arr.len() { + Err(IndexError::OutOfBounds(idx)) + } else { + Ok(arr.swap_remove(idx)) + } + }, + _ => Err(IndexError::NotIndexable), + } + }) + } +} + +impl<'a, S: AsRef, C: AsRef<[S]>> Index<&'a JsonPointer> for Value { + type Output = Value; + fn index(&self, ptr: &'a JsonPointer) -> &Value { + ::get(self,ptr).unwrap() + } +} + +impl<'a, S: AsRef, C: AsRef<[S]>> IndexMut<&'a JsonPointer> for Value { + fn index_mut(&mut self, ptr: &'a JsonPointer) -> &mut Value { + ::get_mut(self, ptr).unwrap() + } +} diff --git a/tests/macros.rs b/tests/macros.rs index ff4b230..a68a3f6 100644 --- a/tests/macros.rs +++ b/tests/macros.rs @@ -1,3 +1,4 @@ +#[allow(unused_macros)] macro_rules! assert_unparse { ($expr:expr) => { let ptr = $expr.parse::>().unwrap(); diff --git a/tests/parsing.rs b/tests/parsing.rs index 9d846f0..3d0cc60 100644 --- a/tests/parsing.rs +++ b/tests/parsing.rs @@ -1,21 +1,14 @@ -extern crate json_pointer; -#[macro_use] -extern crate lazy_static; -#[macro_use] -extern crate quickcheck; -extern crate regex; #[macro_use] mod macros; -use json_pointer::JsonPointer; -use quickcheck::TestResult; +use json_pointer_simd::JsonPointer; +use once_cell::sync::Lazy; +use quickcheck::{quickcheck, TestResult}; use regex::Regex; -lazy_static! { - static ref JSON_POINTER_REGEX: Regex = Regex::new("^(/([^/~]|~[01])*)*$").unwrap(); - static ref URI_FRAGMENT_REGEX: Regex = Regex::new("^#(/([^A-Za-z0-9._!$&'()*+,;=@/?-]|~[01]|%[0-9a-fA-F]{2})*)*$").unwrap(); -} +static JSON_POINTER_REGEX: Lazy = Lazy::new(||Regex::new("^(/([^/~]|~[01])*)*$").unwrap()); +static URI_FRAGMENT_REGEX: Lazy = Lazy::new(||Regex::new("^#(/([^A-Za-z0-9._!$&'()*+,;=@/?-]|~[01]|%[0-9a-fA-F]{2})*)*$").unwrap()); quickcheck! { @@ -26,12 +19,12 @@ fn faithful_parse(s: String) -> TestResult { let ok = match s.parse::>() { Ok(ptr) => if s.chars().next() == Some('#') { if URI_FRAGMENT_REGEX.is_match(&s) { - (s == ptr.uri_fragment()) + s == ptr.uri_fragment() } else { return TestResult::discard(); } } else { - (s == ptr.to_string()) + s == ptr.to_string() }, Err(_) => return TestResult::discard(), }; @@ -49,7 +42,7 @@ fn parses_all_valid(s: String) -> bool { let matches_regex = JSON_POINTER_REGEX.is_match(&s) || URI_FRAGMENT_REGEX.is_match(&s); let parses = s.parse::>().is_ok(); - (matches_regex == parses) + matches_regex == parses } } diff --git a/tests/push_pop.rs b/tests/push_pop.rs index 3334212..719d95a 100644 --- a/tests/push_pop.rs +++ b/tests/push_pop.rs @@ -1,8 +1,5 @@ -extern crate json_pointer; -#[macro_use] -extern crate quickcheck; - -use json_pointer::JsonPointer; +use quickcheck::quickcheck; +use json_pointer_simd::JsonPointer; use quickcheck::TestResult; quickcheck! { diff --git a/tests/rfc.rs b/tests/rfc.rs index 61d17ab..25540be 100644 --- a/tests/rfc.rs +++ b/tests/rfc.rs @@ -1,14 +1,9 @@ -extern crate json_pointer; -#[macro_use] -extern crate lazy_static; -#[macro_use] -extern crate serde_json; +use json_pointer_simd::{JsonPointer,JsonPointerTarget}; +use once_cell::sync::Lazy; +use serde_json::{Value,json}; -use json_pointer::JsonPointer; -use serde_json::Value; - -lazy_static! { - static ref JSON: Value = json!({ +static JSON: Lazy = Lazy::new(|| + json!({ "foo": ["bar", "baz"], "": 0, "a/b": 1, @@ -19,8 +14,8 @@ lazy_static! { "k\"l": 6, " ": 7, "m~n": 8, - }); -} + }) +); macro_rules! rfc_tests { ($($ptr:expr => $json:tt;)*) => { @@ -30,7 +25,8 @@ macro_rules! rfc_tests { fn rfc_tests() { $({ let ptr = $ptr.parse::>().unwrap(); - assert_eq!(ptr.get(&JSON).unwrap(), &json!($json)); + let value = ::get(&JSON,&ptr); + assert_eq!(value.unwrap(), &json!($json)); })* } } diff --git a/tests/rfc_simd.rs b/tests/rfc_simd.rs new file mode 100644 index 0000000..562620c --- /dev/null +++ b/tests/rfc_simd.rs @@ -0,0 +1,85 @@ +use json_pointer_simd::{JsonPointer,JsonPointerTarget}; +use once_cell::sync::Lazy; +use simd_json::{OwnedValue,json}; + +static JSON: Lazy = Lazy::new(|| + json!({ + "foo": ["bar", "baz"], + "": 0, + "a/b": 1, + "c%d": 2, + "e^f": 3, + "g|h": 4, + "i\\j": 5, + "k\"l": 6, + " ": 7, + "m~n": 8, + }) +); + +macro_rules! rfc_tests { + ($($ptr:expr => $json:tt;)*) => { + /// The tests in Sections [5](https://tools.ietf.org/html/rfc6901#section-5) + /// and [6](https://tools.ietf.org/html/rfc6901#section-6) of RFC 6901. + #[test] + fn rfc_tests() { + $({ + let ptr = $ptr.parse::>().unwrap(); + let value = JSON.get(&ptr); + assert_eq!(value.unwrap(), &json!($json)); + })* + } + } +} + +rfc_tests! { + // Section 5 + "" => { + "foo": ["bar", "baz"], + "": 0, + "a/b": 1, + "c%d": 2, + "e^f": 3, + "g|h": 4, + "i\\j": 5, + "k\"l": 6, + " ": 7, + "m~n": 8, + }; + "/foo" => ["bar", "baz"]; + "/foo/0" => "bar"; + "/" => 0; + "/a~1b" => 1; + "/c%d" => 2; + "/e^f" => 3; + "/g|h" => 4; + "/i\\j" => 5; + "/k\"l" => 6; + "/ " => 7; + "/m~0n" => 8; + + // Section 6 + "#" => { + "foo": ["bar", "baz"], + "": 0, + "a/b": 1, + "c%d": 2, + "e^f": 3, + "g|h": 4, + "i\\j": 5, + "k\"l": 6, + " ": 7, + "m~n": 8, + }; + "#/foo" => ["bar", "baz"]; + "#/foo/0" => "bar"; + "#/" => 0; + "#/a~1b" => 1; + "#/c%25d" => 2; + "#/e%5Ef" => 3; + "#/g%7Ch" => 4; + "#/i%5Cj" => 5; + "#/k%22l" => 6; + "#/%20" => 7; + "#/m~0n" => 8; +}