chore: checkpoint before Python removal

This commit is contained in:
2026-03-26 22:33:59 +00:00
parent 683cec9307
commit e568ddf82a
29972 changed files with 11269302 additions and 2 deletions

32
vendor/jsonptr/src/arbitrary.rs vendored Normal file
View File

@@ -0,0 +1,32 @@
use crate::{PointerBuf, Token};
use alloc::{boxed::Box, string::String, vec::Vec};
use quickcheck::Arbitrary;
impl Arbitrary for Token<'static> {
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
Self::new(String::arbitrary(g))
}
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
Box::new(self.decoded().into_owned().shrink().map(Self::new))
}
}
impl Arbitrary for PointerBuf {
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
let size = usize::arbitrary(g) % g.size();
Self::from_tokens((0..size).map(|_| Token::arbitrary(g)).collect::<Vec<_>>())
}
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
let tokens: Vec<_> = self.tokens().map(Token::into_owned).collect();
Box::new((0..self.count()).map(move |i| {
let subset: Vec<_> = tokens
.iter()
.enumerate()
.filter_map(|(j, t)| (i != j).then_some(t.clone()))
.collect();
Self::from_tokens(subset)
}))
}
}

991
vendor/jsonptr/src/assign.rs vendored Normal file
View File

@@ -0,0 +1,991 @@
//! # Assign values based on JSON [`Pointer`]s
//!
//! This module provides the [`Assign`] trait which allows for the assignment of
//! values based on a JSON Pointer.
//!
//! This module is enabled by default with the `"assign"` feature flag.
//!
//! # Expansion
//! The path will automatically be expanded if the [`Pointer`] is not fully
//! exhausted before reaching a non-existent key in the case of objects, index
//! in the case of arrays, or a scalar value (including `null`) based upon a
//! best-guess effort on the meaning of each [`Token`](crate::Token):
//! - If the [`Token`](crate::Token) is equal to `"0"` or `"-"`, the token will
//! be considered an index of an array.
//! - All tokens not equal to `"0"` or `"-"` will be considered keys of an
//! object.
//!
//! ## Usage
//! [`Assign`] can be used directly or through the [`assign`](Pointer::assign)
//! method of [`Pointer`].
//!
//! ```rust
//! use jsonptr::Pointer;
//! use serde_json::json;
//! let mut data = json!({"foo": "bar"});
//! let ptr = Pointer::from_static("/foo");
//! let replaced = ptr.assign(&mut data, "baz").unwrap();
//! assert_eq!(replaced, Some(json!("bar")));
//! assert_eq!(data, json!({"foo": "baz"}));
//! ```
//! ## Provided implementations
//!
//! | Lang | value type | feature flag | Default |
//! | ----- |: ----------------- :|: ---------- :| ------- |
//! | JSON | `serde_json::Value` | `"json"` | ✓ |
//! | TOML | `toml::Value` | `"toml"` | |
//!
use crate::{
diagnostic::{diagnostic_url, Diagnostic, Label},
index::{OutOfBoundsError, ParseIndexError},
Pointer, PointerBuf,
};
use alloc::{boxed::Box, string::ToString};
use core::{
fmt::{self, Debug},
iter::once,
};
/// Implemented by types which can internally assign a
/// ([`Value`](`Assign::Value`)) at a path represented by a JSON [`Pointer`].
///
/// ## Expansion
/// For provided implementations (`"json"`, and `"toml"`) path will
/// automatically be expanded the if the [`Pointer`] is not fully exhausted
/// before reaching a non-existent key in the case of objects, index in the case
/// of arrays, or a scalar value (including `null`) based upon a best-guess
/// effort on the meaning of each [`Token`](crate::Token):
///
/// - If the [`Token`](crate::Token) is equal to `"0"` or `"-"`, the token will
/// be considered an index of an array.
/// - All tokens not equal to `"0"` or `"-"` will be considered keys of an
/// object.
///
/// ## Examples
///
/// ### Successful assignment with replacement
/// This example demonstrates a successful assignment with replacement.
/// ```rust
/// use jsonptr::{Pointer, assign::Assign};
/// use serde_json::{json, Value};
///
/// let mut data = json!({"foo": "bar"});
/// let ptr = Pointer::from_static("/foo");
///
/// let replaced = data.assign(&ptr, "baz").unwrap();
/// assert_eq!(replaced, Some(json!("bar")));
/// assert_eq!(data, json!({"foo": "baz"}));
/// ```
///
/// ### Successful assignment with path expansion
/// This example demonstrates path expansion, including an array index (`"0"`)
/// ```rust
/// # use jsonptr::{Pointer, assign::Assign};
/// # use serde_json::{json, Value};
/// let ptr = Pointer::from_static("/foo/bar/0/baz");
/// let mut data = serde_json::json!({"foo": "bar"});
///
/// let replaced = data.assign(ptr, json!("qux")).unwrap();
///
/// assert_eq!(&data, &json!({"foo": {"bar": [{"baz": "qux"}]}}));
/// assert_eq!(replaced, Some(json!("bar")));
/// ```
///
/// ### Successful assignment with `"-"` token
///
/// This example performs path expansion using the special `"-"` token (per RFC
/// 6901) to represent the next element in an array.
///
/// ```rust
/// # use jsonptr::{Pointer, assign::Assign};
/// # use serde_json::{json, Value};
/// let ptr = Pointer::from_static("/foo/bar/-/baz");
/// let mut data = json!({"foo": "bar"});
///
/// let replaced = data.assign(ptr, json!("qux")).unwrap();
/// assert_eq!(&data, &json!({"foo": {"bar": [{"baz": "qux"}]}}));
/// assert_eq!(replaced, Some(json!("bar")));
/// ```
pub trait Assign {
/// The type of value that this implementation can operate on.
type Value;
/// Error associated with `Assign`
type Error;
/// Assigns a value of based on the path provided by a JSON Pointer,
/// returning the replaced value, if any.
///
/// # Errors
/// Returns [`Self::Error`] if the assignment fails.
fn assign<V>(&mut self, ptr: &Pointer, value: V) -> Result<Option<Self::Value>, Self::Error>
where
V: Into<Self::Value>;
}
/// Alias for [`Error`] - indicates a value assignment failed.
#[deprecated(since = "0.7.0", note = "renamed to `Error`")]
pub type AssignError = Error;
/// Possible error returned from [`Assign`] implementations for
/// [`serde_json::Value`] and
/// [`toml::Value`](https://docs.rs/toml/0.8.14/toml/index.html).
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
/// A [`Token`](crate::Token) within the [`Pointer`] failed to be parsed as
/// an array index.
FailedToParseIndex {
/// Position (index) of the token which failed to parse as an [`Index`](crate::index::Index)
position: usize,
/// Offset of the partial pointer starting with the invalid index.
offset: usize,
/// The source [`ParseIndexError`]
source: ParseIndexError,
},
/// A [`Token`](crate::Token) within the [`Pointer`] contains an
/// [`Index`](crate::index::Index) which is out of bounds.
///
/// The current or resulting array's length is less than the index.
OutOfBounds {
/// Position (index) of the token which failed to parse as an [`Index`](crate::index::Index)
position: usize,
/// Offset of the partial pointer starting with the invalid index.
offset: usize,
/// The source [`OutOfBoundsError`]
source: OutOfBoundsError,
},
}
impl Error {
/// The position (token index) of the [`Token`](crate::Token) which was out of bounds
pub fn position(&self) -> usize {
match self {
Self::OutOfBounds { position, .. } | Self::FailedToParseIndex { position, .. } => {
*position
}
}
}
/// Offset (in bytes) of the partial pointer starting with the invalid token.
pub fn offset(&self) -> usize {
match self {
Self::OutOfBounds { offset, .. } | Self::FailedToParseIndex { offset, .. } => *offset,
}
}
/// Returns `true` if the error is [`OutOfBounds`].
///
/// [`OutOfBounds`]: Error::OutOfBounds
#[must_use]
pub fn is_out_of_bounds(&self) -> bool {
matches!(self, Self::OutOfBounds { .. })
}
/// Returns `true` if the error is [`FailedToParseIndex`].
///
/// [`FailedToParseIndex`]: Error::FailedToParseIndex
#[must_use]
pub fn is_failed_to_parse_index(&self) -> bool {
matches!(self, Self::FailedToParseIndex { .. })
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::FailedToParseIndex { offset, .. } => {
write!(
f,
"assign failed: json pointer token at offset {offset} failed to parse as an array index"
)
}
Self::OutOfBounds { offset, .. } => write!(
f,
"assign failed: json pointer token at offset {offset} is out of bounds",
),
}
}
}
impl Diagnostic for Error {
type Subject = PointerBuf;
fn url() -> &'static str {
diagnostic_url!(enum assign::Error)
}
fn labels(&self, origin: &Self::Subject) -> Option<Box<dyn Iterator<Item = Label>>> {
let position = self.position();
let token = origin.get(position)?;
let offset = if self.offset() + 1 < origin.as_str().len() {
self.offset() + 1
} else {
self.offset()
};
let len = token.encoded().len();
let text = match self {
Error::FailedToParseIndex { .. } => "expected array index or '-'".to_string(),
Error::OutOfBounds { source, .. } => {
format!("{} is out of bounds (len: {})", source.index, source.length)
}
};
Some(Box::new(once(Label::new(text, offset, len))))
}
}
#[cfg(feature = "miette")]
impl miette::Diagnostic for Error {
fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
Some(Box::new(<Self as Diagnostic>::url()))
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::FailedToParseIndex { source, .. } => Some(source),
Self::OutOfBounds { source, .. } => Some(source),
}
}
}
#[cfg(feature = "json")]
mod json {
use super::{Assign, Assigned, Error};
use crate::{Pointer, Token};
use alloc::{
string::{String, ToString},
vec::Vec,
};
use core::mem;
use serde_json::{map::Entry, Map, Value};
fn expand(mut remaining: &Pointer, mut value: Value) -> Value {
while let Some((ptr, tok)) = remaining.split_back() {
remaining = ptr;
match tok.encoded() {
"0" | "-" => {
value = Value::Array(vec![value]);
}
_ => {
let mut obj = Map::new();
obj.insert(tok.to_string(), value);
value = Value::Object(obj);
}
}
}
value
}
impl Assign for Value {
type Value = Value;
type Error = Error;
fn assign<V>(&mut self, ptr: &Pointer, value: V) -> Result<Option<Self::Value>, Self::Error>
where
V: Into<Self::Value>,
{
assign_value(ptr, self, value.into())
}
}
pub(crate) fn assign_value(
mut ptr: &Pointer,
mut dest: &mut Value,
mut value: Value,
) -> Result<Option<Value>, Error> {
let mut offset = 0;
let mut position = 0;
while let Some((token, tail)) = ptr.split_front() {
let tok_len = token.encoded().len();
let assigned = match dest {
Value::Array(array) => assign_array(token, tail, array, value, position, offset)?,
Value::Object(obj) => assign_object(token, tail, obj, value),
_ => assign_scalar(ptr, dest, value),
};
match assigned {
Assigned::Done(assignment) => {
return Ok(assignment);
}
Assigned::Continue {
next_dest: next_value,
same_value: same_src,
} => {
value = same_src;
dest = next_value;
ptr = tail;
}
}
offset += 1 + tok_len;
position += 1;
}
// Pointer is root, we can replace `dest` directly
let replaced = Some(core::mem::replace(dest, value));
Ok(replaced)
}
#[allow(clippy::needless_pass_by_value)]
fn assign_array<'v>(
token: Token<'_>,
remaining: &Pointer,
array: &'v mut Vec<Value>,
src: Value,
position: usize,
offset: usize,
) -> Result<Assigned<'v, Value>, Error> {
// parsing the index
let idx = token
.to_index()
.map_err(|source| Error::FailedToParseIndex {
position,
offset,
source,
})?
.for_len_incl(array.len())
.map_err(|source| Error::OutOfBounds {
position,
offset,
source,
})?;
debug_assert!(idx <= array.len());
if idx < array.len() {
// element exists in the array, we either need to replace it or continue
// depending on whether this is the last token or not
if remaining.is_root() {
// last token, we replace the value and call it a day
Ok(Assigned::Done(Some(mem::replace(&mut array[idx], src))))
} else {
// not the last token, we continue with a mut ref to the element as
// the next value
Ok(Assigned::Continue {
next_dest: &mut array[idx],
same_value: src,
})
}
} else {
// element does not exist in the array.
// we create the path and assign the value
let src = expand(remaining, src);
array.push(src);
Ok(Assigned::Done(None))
}
}
#[allow(clippy::needless_pass_by_value)]
fn assign_object<'v>(
token: Token<'_>,
remaining: &Pointer,
obj: &'v mut Map<String, Value>,
src: Value,
) -> Assigned<'v, Value> {
// grabbing the entry of the token
let entry = obj.entry(token.to_string());
// adding token to the pointer buf
match entry {
Entry::Occupied(entry) => {
// if the entry exists, we either replace it or continue
let entry = entry.into_mut();
if remaining.is_root() {
// if this is the last token, we are done
// grab the old value and replace it with the new one
Assigned::Done(Some(mem::replace(entry, src)))
} else {
// if this is not the last token, we continue with a mutable
// reference to the entry as the next value
Assigned::Continue {
same_value: src,
next_dest: entry,
}
}
}
Entry::Vacant(entry) => {
// if the entry does not exist, we create a value based on the
// remaining path with the src value as a leaf and assign it to the
// entry
entry.insert(expand(remaining, src));
Assigned::Done(None)
}
}
}
fn assign_scalar<'v>(
remaining: &Pointer,
scalar: &'v mut Value,
value: Value,
) -> Assigned<'v, Value> {
// scalar values are always replaced at the current buf (with its token)
// build the new src and we replace the value with it.
let replaced = Some(mem::replace(scalar, expand(remaining, value)));
Assigned::Done(replaced)
}
}
#[cfg(feature = "toml")]
mod toml {
use super::{Assign, Assigned, Error};
use crate::{Pointer, Token};
use alloc::{string::String, vec, vec::Vec};
use core::mem;
use toml::{map::Entry, map::Map, Value};
fn expand(mut remaining: &Pointer, mut value: Value) -> Value {
while let Some((ptr, tok)) = remaining.split_back() {
remaining = ptr;
match tok.encoded() {
"0" | "-" => {
value = Value::Array(vec![value]);
}
_ => {
let mut obj = Map::new();
obj.insert(tok.to_string(), value);
value = Value::Table(obj);
}
}
}
value
}
impl Assign for Value {
type Value = Value;
type Error = Error;
fn assign<V>(&mut self, ptr: &Pointer, value: V) -> Result<Option<Self::Value>, Self::Error>
where
V: Into<Self::Value>,
{
assign_value(ptr, self, value.into())
}
}
pub(crate) fn assign_value(
mut ptr: &Pointer,
mut dest: &mut Value,
mut value: Value,
) -> Result<Option<Value>, Error> {
let mut offset = 0;
let mut position = 0;
while let Some((token, tail)) = ptr.split_front() {
let tok_len = token.encoded().len();
let assigned = match dest {
Value::Array(array) => assign_array(token, tail, array, value, position, offset)?,
Value::Table(tbl) => assign_object(token, tail, tbl, value),
_ => assign_scalar(ptr, dest, value),
};
match assigned {
Assigned::Done(assignment) => {
return Ok(assignment);
}
Assigned::Continue {
next_dest: next_value,
same_value: same_src,
} => {
value = same_src;
dest = next_value;
ptr = tail;
}
}
offset += 1 + tok_len;
position += 1;
}
// Pointer is root, we can replace `dest` directly
let replaced = Some(mem::replace(dest, value));
Ok(replaced)
}
#[allow(clippy::needless_pass_by_value)]
fn assign_array<'v>(
token: Token<'_>,
remaining: &Pointer,
array: &'v mut Vec<Value>,
src: Value,
position: usize,
offset: usize,
) -> Result<Assigned<'v, Value>, Error> {
// parsing the index
let idx = token
.to_index()
.map_err(|source| Error::FailedToParseIndex {
position,
offset,
source,
})?
.for_len_incl(array.len())
.map_err(|source| Error::OutOfBounds {
position,
offset,
source,
})?;
debug_assert!(idx <= array.len());
if idx < array.len() {
// element exists in the array, we either need to replace it or continue
// depending on whether this is the last token or not
if remaining.is_root() {
// last token, we replace the value and call it a day
Ok(Assigned::Done(Some(mem::replace(&mut array[idx], src))))
} else {
// not the last token, we continue with a mut ref to the element as
// the next value
Ok(Assigned::Continue {
next_dest: &mut array[idx],
same_value: src,
})
}
} else {
// element does not exist in the array.
// we create the path and assign the value
let src = expand(remaining, src);
array.push(src);
Ok(Assigned::Done(None))
}
}
#[allow(clippy::needless_pass_by_value)]
fn assign_object<'v>(
token: Token<'_>,
remaining: &Pointer,
obj: &'v mut Map<String, Value>,
src: Value,
) -> Assigned<'v, Value> {
// grabbing the entry of the token
match obj.entry(token.to_string()) {
Entry::Occupied(entry) => {
// if the entry exists, we either replace it or continue
let entry = entry.into_mut();
if remaining.is_root() {
// if this is the last token, we are done
// grab the old value and replace it with the new one
Assigned::Done(Some(mem::replace(entry, src)))
} else {
// if this is not the last token, we continue with a mutable
// reference to the entry as the next value
Assigned::Continue {
same_value: src,
next_dest: entry,
}
}
}
Entry::Vacant(entry) => {
// if the entry does not exist, we create a value based on the
// remaining path with the src value as a leaf and assign it to the
// entry
entry.insert(expand(remaining, src));
Assigned::Done(None)
}
}
}
fn assign_scalar<'v>(
remaining: &Pointer,
scalar: &'v mut Value,
value: Value,
) -> Assigned<'v, Value> {
// scalar values are always replaced at the current buf (with its token)
// build the new src and we replace the value with it.
Assigned::Done(Some(mem::replace(scalar, expand(remaining, value))))
}
}
enum Assigned<'v, V> {
Done(Option<V>),
Continue { next_dest: &'v mut V, same_value: V },
}
#[cfg(test)]
#[allow(clippy::too_many_lines)]
mod tests {
use super::{Assign, Error};
use crate::{
index::{InvalidCharacterError, OutOfBoundsError, ParseIndexError},
Pointer,
};
use alloc::vec;
use core::fmt::{Debug, Display};
#[derive(Debug)]
struct Test<V: Assign> {
data: V,
ptr: &'static str,
assign: V,
expected_data: V,
expected: Result<Option<V>, V::Error>,
}
impl<V> Test<V>
where
V: Assign + Clone + PartialEq + Display + Debug,
V::Value: Debug + PartialEq + From<V>,
V::Error: Debug + PartialEq,
Result<Option<V>, V::Error>: PartialEq<Result<Option<V::Value>, V::Error>>,
{
fn run(self, i: usize) {
let Test {
ptr,
mut data,
assign,
expected_data,
expected,
..
} = self;
let ptr = Pointer::from_static(ptr);
let replaced = ptr.assign(&mut data, assign.clone());
assert_eq!(
&expected_data, &data,
"test #{i}:\n\ndata: \n{data:#?}\n\nexpected_data\n{expected_data:#?}"
);
assert_eq!(&expected, &replaced);
}
}
#[test]
#[cfg(feature = "json")]
fn assign_json() {
use serde_json::json;
[
Test {
ptr: "/foo",
data: json!({}),
assign: json!("bar"),
expected_data: json!({"foo": "bar"}),
expected: Ok(None),
},
Test {
ptr: "",
data: json!({"foo": "bar"}),
assign: json!("baz"),
expected_data: json!("baz"),
expected: Ok(Some(json!({"foo": "bar"}))),
},
Test {
ptr: "/foo",
data: json!({"foo": "bar"}),
assign: json!("baz"),
expected_data: json!({"foo": "baz"}),
expected: Ok(Some(json!("bar"))),
},
Test {
ptr: "/foo/bar",
data: json!({"foo": "bar"}),
assign: json!("baz"),
expected_data: json!({"foo": {"bar": "baz"}}),
expected: Ok(Some(json!("bar"))),
},
Test {
ptr: "/foo/bar",
data: json!({}),
assign: json!("baz"),
expected_data: json!({"foo": {"bar": "baz"}}),
expected: Ok(None),
},
Test {
ptr: "/",
data: json!({}),
assign: json!("foo"),
expected_data: json!({"": "foo"}),
expected: Ok(None),
},
Test {
ptr: "/-",
data: json!({}),
assign: json!("foo"),
expected_data: json!({"-": "foo"}),
expected: Ok(None),
},
Test {
ptr: "/-",
data: json!(null),
assign: json!(34),
expected_data: json!([34]),
expected: Ok(Some(json!(null))),
},
Test {
ptr: "/foo/-",
data: json!({"foo": "bar"}),
assign: json!("baz"),
expected_data: json!({"foo": ["baz"]}),
expected: Ok(Some(json!("bar"))),
},
Test {
ptr: "/foo/-/bar",
assign: "baz".into(),
data: json!({}),
expected: Ok(None),
expected_data: json!({"foo":[{"bar": "baz"}]}),
},
Test {
ptr: "/foo/-/bar",
assign: "qux".into(),
data: json!({"foo":[{"bar":"baz" }]}),
expected: Ok(None),
expected_data: json!({"foo":[{"bar":"baz"},{"bar":"qux"}]}),
},
Test {
ptr: "/foo/-/bar",
data: json!({"foo":[{"bar":"baz"},{"bar":"qux"}]}),
assign: "quux".into(),
expected: Ok(None),
expected_data: json!({"foo":[{"bar":"baz"},{"bar":"qux"},{"bar":"quux"}]}),
},
Test {
ptr: "/foo/0/bar",
data: json!({"foo":[{"bar":"baz"},{"bar":"qux"},{"bar":"quux"}]}),
assign: "grault".into(),
expected: Ok(Some("baz".into())),
expected_data: json!({"foo":[{"bar":"grault"},{"bar":"qux"},{"bar":"quux"}]}),
},
Test {
ptr: "/0",
data: json!({}),
assign: json!("foo"),
expected_data: json!({"0": "foo"}),
expected: Ok(None),
},
Test {
ptr: "/1",
data: json!(null),
assign: json!("foo"),
expected_data: json!({"1": "foo"}),
expected: Ok(Some(json!(null))),
},
Test {
ptr: "/0",
data: json!([]),
expected_data: json!(["foo"]),
assign: json!("foo"),
expected: Ok(None),
},
Test {
ptr: "///bar",
data: json!({"":{"":{"bar": 42}}}),
assign: json!(34),
expected_data: json!({"":{"":{"bar":34}}}),
expected: Ok(Some(json!(42))),
},
Test {
ptr: "/1",
data: json!([]),
assign: json!("foo"),
expected: Err(Error::OutOfBounds {
position: 0,
offset: 0,
source: OutOfBoundsError {
index: 1,
length: 0,
},
}),
expected_data: json!([]),
},
Test {
ptr: "/0",
data: json!(["foo"]),
assign: json!("bar"),
expected: Ok(Some(json!("foo"))),
expected_data: json!(["bar"]),
},
Test {
ptr: "/12a",
data: json!([]),
assign: json!("foo"),
expected: Err(Error::FailedToParseIndex {
position: 0,
offset: 0,
source: ParseIndexError::InvalidCharacter(InvalidCharacterError {
source: "12a".into(),
offset: 2,
}),
}),
expected_data: json!([]),
},
Test {
ptr: "/002",
data: json!([]),
assign: json!("foo"),
expected: Err(Error::FailedToParseIndex {
position: 0,
offset: 0,
source: ParseIndexError::LeadingZeros,
}),
expected_data: json!([]),
},
Test {
ptr: "/+23",
data: json!([]),
assign: json!("foo"),
expected: Err(Error::FailedToParseIndex {
position: 0,
offset: 0,
source: ParseIndexError::InvalidCharacter(InvalidCharacterError {
source: "+23".into(),
offset: 0,
}),
}),
expected_data: json!([]),
},
]
.into_iter()
.enumerate()
.for_each(|(i, t)| t.run(i));
}
#[test]
#[cfg(feature = "toml")]
fn assign_toml() {
use toml::{toml, Table, Value};
[
Test {
data: Value::Table(toml::Table::new()),
ptr: "/foo",
assign: "bar".into(),
expected_data: toml! { "foo" = "bar" }.into(),
expected: Ok(None),
},
Test {
data: toml! {foo = "bar"}.into(),
ptr: "",
assign: "baz".into(),
expected_data: "baz".into(),
expected: Ok(Some(toml! {foo = "bar"}.into())),
},
Test {
data: toml! { foo = "bar"}.into(),
ptr: "/foo",
assign: "baz".into(),
expected_data: toml! {foo = "baz"}.into(),
expected: Ok(Some("bar".into())),
},
Test {
data: toml! { foo = "bar"}.into(),
ptr: "/foo/bar",
assign: "baz".into(),
expected_data: toml! {foo = { bar = "baz"}}.into(),
expected: Ok(Some("bar".into())),
},
Test {
data: Table::new().into(),
ptr: "/",
assign: "foo".into(),
expected_data: toml! {"" = "foo"}.into(),
expected: Ok(None),
},
Test {
data: Table::new().into(),
ptr: "/-",
assign: "foo".into(),
expected_data: toml! {"-" = "foo"}.into(),
expected: Ok(None),
},
Test {
data: "data".into(),
ptr: "/-",
assign: 34.into(),
expected_data: Value::Array(vec![34.into()]),
expected: Ok(Some("data".into())),
},
Test {
data: toml! {foo = "bar"}.into(),
ptr: "/foo/-",
assign: "baz".into(),
expected_data: toml! {foo = ["baz"]}.into(),
expected: Ok(Some("bar".into())),
},
Test {
data: Table::new().into(),
ptr: "/0",
assign: "foo".into(),
expected_data: toml! {"0" = "foo"}.into(),
expected: Ok(None),
},
Test {
data: 21.into(),
ptr: "/1",
assign: "foo".into(),
expected_data: toml! {"1" = "foo"}.into(),
expected: Ok(Some(21.into())),
},
Test {
data: Value::Array(vec![]),
ptr: "/0",
expected_data: vec![Value::from("foo")].into(),
assign: "foo".into(),
expected: Ok(None),
},
Test {
ptr: "/foo/-/bar",
assign: "baz".into(),
data: Table::new().into(),
expected: Ok(None),
expected_data: toml! { "foo" = [{"bar" = "baz"}] }.into(),
},
Test {
ptr: "/foo/-/bar",
assign: "qux".into(),
data: toml! {"foo" = [{"bar" = "baz"}] }.into(),
expected: Ok(None),
expected_data: toml! {"foo" = [{"bar" = "baz"}, {"bar" = "qux"}]}.into(),
},
Test {
ptr: "/foo/-/bar",
data: toml! {"foo" = [{"bar" = "baz"}, {"bar" = "qux"}]}.into(),
assign: "quux".into(),
expected: Ok(None),
expected_data: toml! {"foo" = [{"bar" = "baz"}, {"bar" = "qux"}, {"bar" = "quux"}]}
.into(),
},
Test {
ptr: "/foo/0/bar",
data: toml! {"foo" = [{"bar" = "baz"}, {"bar" = "qux"}, {"bar" = "quux"}]}.into(),
assign: "grault".into(),
expected: Ok(Some("baz".into())),
expected_data:
toml! {"foo" = [{"bar" = "grault"}, {"bar" = "qux"}, {"bar" = "quux"}]}.into(),
},
Test {
data: Value::Array(vec![]),
ptr: "/-",
assign: "foo".into(),
expected: Ok(None),
expected_data: vec!["foo"].into(),
},
Test {
data: Value::Array(vec![]),
ptr: "/1",
assign: "foo".into(),
expected: Err(Error::OutOfBounds {
position: 0,
offset: 0,
source: OutOfBoundsError {
index: 1,
length: 0,
},
}),
expected_data: Value::Array(vec![]),
},
Test {
data: Value::Array(vec![]),
ptr: "/a",
assign: "foo".into(),
expected: Err(Error::FailedToParseIndex {
position: 0,
offset: 0,
source: ParseIndexError::InvalidCharacter(InvalidCharacterError {
source: "a".into(),
offset: 0,
}),
}),
expected_data: Value::Array(vec![]),
},
]
.into_iter()
.enumerate()
.for_each(|(i, t)| t.run(i));
}
}

75
vendor/jsonptr/src/component.rs vendored Normal file
View File

@@ -0,0 +1,75 @@
use crate::{Pointer, Token, Tokens};
/// A single [`Token`] or the root of a JSON Pointer
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Component<'t> {
/// The document root
Root,
/// A segment of a JSON Pointer
Token(Token<'t>),
}
impl<'t> From<Token<'t>> for Component<'t> {
fn from(token: Token<'t>) -> Self {
Self::Token(token)
}
}
/// An iterator over the [`Component`]s of a JSON Pointer
#[derive(Debug)]
pub struct Components<'t> {
tokens: Tokens<'t>,
sent_root: bool,
}
impl<'t> Iterator for Components<'t> {
type Item = Component<'t>;
fn next(&mut self) -> Option<Self::Item> {
if !self.sent_root {
self.sent_root = true;
return Some(Component::Root);
}
self.tokens.next().map(Component::Token)
}
}
impl<'t> From<&'t Pointer> for Components<'t> {
fn from(pointer: &'t Pointer) -> Self {
Self {
sent_root: false,
tokens: pointer.tokens(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn components() {
let ptr = Pointer::from_static("");
let components: Vec<_> = Components::from(ptr).collect();
assert_eq!(components, vec![Component::Root]);
let ptr = Pointer::from_static("/foo");
let components = ptr.components().collect::<Vec<_>>();
assert_eq!(
components,
vec![Component::Root, Component::Token("foo".into())]
);
let ptr = Pointer::from_static("/foo/bar/-/0/baz");
let components = ptr.components().collect::<Vec<_>>();
assert_eq!(
components,
vec![
Component::Root,
Component::from(Token::from("foo")),
Component::Token("bar".into()),
Component::Token("-".into()),
Component::Token("0".into()),
Component::Token("baz".into())
]
);
}
}

344
vendor/jsonptr/src/delete.rs vendored Normal file
View File

@@ -0,0 +1,344 @@
//! # Delete values based on JSON Pointers
//!
//! This module provides the [`Delete`] trait which is implemented by types that
//! can internally remove a value based on a JSON Pointer.
//!
//! The rules of deletion are determined by the implementation, with the
//! provided implementations (`"json"` & `"toml"`) operating as follows:
//! - If the [`Pointer`] can be resolved, then the [`Value`](`Delete::Value`) is
//! deleted and returned as `Some(value)`.
//! - If the [`Pointer`] fails to resolve for any reason, `Ok(None)` is
//! returned.
//! - If the [`Pointer`] is root, `value` is replaced:
//! - `"json"` - `serde_json::Value::Null`
//! - `"toml"` - `toml::Value::Table::Default`
//!
//! This module is enabled by default with the `"delete"` feature flag.
//!
//! ## Usage
//! Deleting a resolved pointer:
//! ```rust
//! use jsonptr::{Pointer, delete::Delete};
//! use serde_json::json;
//!
//! let mut data = json!({ "foo": { "bar": { "baz": "qux" } } });
//! let ptr = Pointer::from_static("/foo/bar/baz");
//! assert_eq!(data.delete(&ptr), Some("qux".into()));
//! assert_eq!(data, json!({ "foo": { "bar": {} } }));
//! ```
//! Deleting a non-existent Pointer returns `None`:
//! ```rust
//! use jsonptr::{ Pointer, delete::Delete };
//! use serde_json::json;
//!
//! let mut data = json!({});
//! let ptr = Pointer::from_static("/foo/bar/baz");
//! assert_eq!(ptr.delete(&mut data), None);
//! assert_eq!(data, json!({}));
//! ```
//! Deleting a root pointer replaces the value with `Value::Null`:
//! ```rust
//! use jsonptr::{Pointer, delete::Delete};
//! use serde_json::json;
//!
//! let mut data = json!({ "foo": { "bar": "baz" } });
//! let ptr = Pointer::root();
//! assert_eq!(data.delete(&ptr), Some(json!({ "foo": { "bar": "baz" } })));
//! assert!(data.is_null());
//! ```
//!
//! ## Provided implementations
//!
//! | Lang | value type | feature flag | Default |
//! | ----- |: ----------------- :|: ---------- :| ------- |
//! | JSON | `serde_json::Value` | `"json"` | ✓ |
//! | TOML | `toml::Value` | `"toml"` | |
use crate::Pointer;
/*
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
╔══════════════════════════════════════════════════════════════════════════════╗
║ ║
║ Delete ║
║ ¯¯¯¯¯¯¯¯ ║
╚══════════════════════════════════════════════════════════════════════════════╝
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
*/
/// Delete is implemented by types which can internally remove a value based on
/// a JSON Pointer
pub trait Delete {
/// The type of value that this implementation can operate on.
type Value;
/// Attempts to internally delete a value based upon a [Pointer].
fn delete(&mut self, ptr: &Pointer) -> Option<Self::Value>;
}
/*
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
╔══════════════════════════════════════════════════════════════════════════════╗
║ ║
║ json impl ║
║ ¯¯¯¯¯¯¯¯¯¯¯ ║
╚══════════════════════════════════════════════════════════════════════════════╝
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
*/
#[cfg(feature = "json")]
mod json {
use super::Delete;
use crate::Pointer;
use core::mem;
use serde_json::Value;
impl Delete for Value {
type Value = Value;
fn delete(&mut self, ptr: &Pointer) -> Option<Self::Value> {
let Some((parent_ptr, last)) = ptr.split_back() else {
// deleting at root
return Some(mem::replace(self, Value::Null));
};
parent_ptr
.resolve_mut(self)
.ok()
.and_then(|parent| match parent {
Value::Array(children) => {
let idx = last.to_index().ok()?.for_len_incl(children.len()).ok()?;
children.remove(idx).into()
}
Value::Object(children) => children.remove(last.decoded().as_ref()),
_ => None,
})
}
}
}
/*
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
╔══════════════════════════════════════════════════════════════════════════════╗
║ ║
║ toml impl ║
║ ¯¯¯¯¯¯¯¯¯¯¯ ║
╚══════════════════════════════════════════════════════════════════════════════╝
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
*/
#[cfg(feature = "toml")]
mod toml {
use super::Delete;
use crate::Pointer;
use core::mem;
use toml::{Table, Value};
impl Delete for Value {
type Value = Value;
fn delete(&mut self, ptr: &Pointer) -> Option<Self::Value> {
let Some((parent_ptr, last)) = ptr.split_back() else {
// deleting at root
return Some(mem::replace(self, Table::default().into()));
};
parent_ptr
.resolve_mut(self)
.ok()
.and_then(|parent| match parent {
Value::Array(children) => {
let idx = last.to_index().ok()?.for_len_incl(children.len()).ok()?;
children.remove(idx).into()
}
Value::Table(children) => children.remove(last.decoded().as_ref()),
_ => None,
})
}
}
}
/*
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
╔══════════════════════════════════════════════════════════════════════════════╗
║ ║
║ Tests ║
║ ¯¯¯¯¯¯¯ ║
╚══════════════════════════════════════════════════════════════════════════════╝
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
*/
#[cfg(test)]
mod tests {
use super::Delete;
use crate::Pointer;
use core::fmt;
use serde_json::json;
struct Test<V> {
data: V,
ptr: &'static str,
expected_data: V,
expected_deleted: Option<V>,
}
impl<V> Test<V>
where
V: Delete<Value = V> + Clone + PartialEq + fmt::Display + fmt::Debug,
{
fn all(tests: impl IntoIterator<Item = Test<V>>) {
tests.into_iter().enumerate().for_each(|(i, t)| t.run(i));
}
fn run(self, _i: usize) {
let Test {
mut data,
ptr,
expected_data,
expected_deleted,
} = self;
let ptr = Pointer::from_static(ptr);
let deleted = ptr.delete(&mut data);
assert_eq!(expected_data, data);
assert_eq!(expected_deleted, deleted);
}
}
/*
╔═══════════════════════════════════════════════════╗
║ json ║
╚═══════════════════════════════════════════════════╝
*/
#[test]
#[cfg(feature = "json")]
fn delete_json() {
Test::all([
// 0
Test {
ptr: "/foo",
data: json!({"foo": "bar"}),
expected_data: json!({}),
expected_deleted: Some(json!("bar")),
},
// 1
Test {
ptr: "/foo/bar",
data: json!({"foo": {"bar": "baz"}}),
expected_data: json!({"foo": {}}),
expected_deleted: Some(json!("baz")),
},
// 2
Test {
ptr: "/foo/bar",
data: json!({"foo": "bar"}),
expected_data: json!({"foo": "bar"}),
expected_deleted: None,
},
// 3
Test {
ptr: "/foo/bar",
data: json!({"foo": {"bar": "baz"}}),
expected_data: json!({"foo": {}}),
expected_deleted: Some(json!("baz")),
},
// 4
Test {
ptr: "/foo/bar/0",
data: json!({"foo": {"bar": ["baz", "qux"]}}),
expected_data: json!({"foo": {"bar": ["qux"]}}),
expected_deleted: Some(json!("baz")),
},
// 5
Test {
ptr: "/foo/0",
data: json!({"foo": "bar"}),
expected_data: json!({"foo": "bar"}),
expected_deleted: None,
},
// 6
Test {
ptr: "/foo/bar/0/baz",
data: json!({"foo": { "bar": [{"baz": "qux", "remaining": "field"}]}}),
expected_data: json!({"foo": { "bar": [{"remaining": "field"}]} }),
expected_deleted: Some(json!("qux")),
},
// 7
// issue #18 - unable to delete root token https://github.com/chanced/jsonptr/issues/18
Test {
ptr: "/Example",
data: json!({"Example": 21, "test": "test"}),
expected_data: json!({"test": "test"}),
expected_deleted: Some(json!(21)),
},
Test {
ptr: "",
data: json!({"Example": 21, "test": "test"}),
expected_data: json!(null),
expected_deleted: Some(json!({"Example": 21, "test": "test"})),
},
]);
}
/*
╔═══════════════════════════════════════════════════╗
║ toml ║
╚═══════════════════════════════════════════════════╝
*/
#[test]
#[cfg(feature = "toml")]
fn delete_toml() {
use toml::{toml, Table, Value};
Test::all([
// 0
Test {
data: toml! {"foo" = "bar"}.into(),
ptr: "/foo",
expected_data: Value::Table(Table::new()),
expected_deleted: Some("bar".into()),
},
// 1
Test {
data: toml! {"foo" = {"bar" = "baz"}}.into(),
ptr: "/foo/bar",
expected_data: toml! {"foo" = {}}.into(),
expected_deleted: Some("baz".into()),
},
// 2
Test {
data: toml! {"foo" = "bar"}.into(),
ptr: "/foo/bar",
expected_data: toml! {"foo" = "bar"}.into(),
expected_deleted: None,
},
// 3
Test {
data: toml! {"foo" = {"bar" = "baz"}}.into(),
ptr: "/foo/bar",
expected_data: toml! {"foo" = {}}.into(),
expected_deleted: Some("baz".into()),
},
// 4
Test {
data: toml! {"foo" = {"bar" = ["baz", "qux"]}}.into(),
ptr: "/foo/bar/0",
expected_data: toml! {"foo" = {"bar" = ["qux"]}}.into(),
expected_deleted: Some("baz".into()),
},
// 5
Test {
data: toml! {"foo" = "bar"}.into(),
ptr: "/foo/0",
expected_data: toml! {"foo" = "bar"}.into(),
expected_deleted: None,
},
// 6
Test {
data: toml! {"foo" = { "bar" = [{"baz" = "qux", "remaining" = "field"}]}}.into(),
ptr: "/foo/bar/0/baz",
expected_data: toml! {"foo" = { "bar" = [{"remaining" = "field"}]} }.into(),
expected_deleted: Some("qux".into()),
},
// 7
// issue #18 - unable to delete root token https://github.com/chanced/jsonptr/issues/18
Test {
data: toml! {"Example" = 21 "test" = "test"}.into(),
ptr: "/Example",
expected_data: toml! {"test" = "test"}.into(),
expected_deleted: Some(21.into()),
},
]);
}
}

280
vendor/jsonptr/src/diagnostic.rs vendored Normal file
View File

@@ -0,0 +1,280 @@
//! Error reporting data structures and miette integration.
//!
use alloc::{boxed::Box, string::String};
use core::{fmt, ops::Deref};
/// Implemented by errors which can be converted into a [`Report`].
pub trait Diagnostic: Sized {
/// The value which caused the error.
type Subject: Deref;
/// Combine the error with its subject to generate a [`Report`].
fn into_report(self, subject: impl Into<Self::Subject>) -> Report<Self> {
Report::new(self, subject.into())
}
/// The docs.rs URL for this error
fn url() -> &'static str;
/// Returns the label for the given [`Subject`] if applicable.
fn labels(&self, subject: &Self::Subject) -> Option<Box<dyn Iterator<Item = Label>>>;
}
/// A label for a span within a json pointer or malformed string.
///
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Label {
text: String,
offset: usize,
len: usize,
}
impl Label {
/// Creates a new instance of a [`Label`] from its parts
pub fn new(text: String, offset: usize, len: usize) -> Self {
Self { text, offset, len }
}
}
#[cfg(feature = "miette")]
impl From<Label> for miette::LabeledSpan {
fn from(value: Label) -> Self {
miette::LabeledSpan::new(Some(value.text), value.offset, value.len)
}
}
/// An enriched error wrapper which captures the original error and the subject
/// (`String` or `PointerBuf`) which caused it, for reporting purposes.
///
/// This type serves two roles:
///
/// 1. **[`PointerBuf::parse`]**: Captures the [`ParseError`] along with the
/// input `String`.
///
/// 2. **Reporting:** Provides enriched reporting capabilities, including
/// (optional) `miette` integration, for `ParseError` and associated errors
/// of `assign::Assign` and `resolve::Resolve` implementations
#[derive(Debug, Clone)]
pub struct Report<T: Diagnostic> {
source: T,
subject: T::Subject,
}
impl<T: Diagnostic> Report<T> {
fn new(source: T, subject: T::Subject) -> Self {
Self { source, subject }
}
/// The value which caused the error.
pub fn subject(&self) -> &<T::Subject as Deref>::Target {
&self.subject
}
/// The error which occurred.
pub fn original(&self) -> &T {
&self.source
}
/// The original parts of the [`Report`].
pub fn decompose(self) -> (T, T::Subject) {
(self.source, self.subject)
}
/// Consumes the [`Report`] and returns the original error `T`.
pub fn into_original(self) -> T {
self.source
}
}
impl<T: Diagnostic> core::ops::Deref for Report<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.source
}
}
impl<T: Diagnostic + fmt::Display> fmt::Display for Report<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
fmt::Display::fmt(&self.source, f)
}
}
#[cfg(feature = "std")]
impl<T> std::error::Error for Report<T>
where
T: Diagnostic + fmt::Debug + std::error::Error + 'static,
T::Subject: fmt::Debug,
{
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source.source()
}
}
#[cfg(feature = "miette")]
impl<T> miette::Diagnostic for Report<T>
where
T: Diagnostic + fmt::Debug + std::error::Error + 'static,
T::Subject: fmt::Debug + miette::SourceCode,
{
fn url<'a>(&'a self) -> Option<Box<dyn core::fmt::Display + 'a>> {
Some(Box::new(T::url()))
}
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
Some(&self.subject)
}
fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
Some(Box::new(T::labels(self, &self.subject)?.map(Into::into)))
}
}
macro_rules! diagnostic_url {
(enum $type:ident) => {
$crate::diagnostic::diagnostic_url!("enum", "", $type)
};
(struct $type:ident) => {
$crate::diagnostic::diagnostic_url!("struct", "", $type)
};
(enum $mod:ident::$type:ident) => {
$crate::diagnostic::diagnostic_url!("enum", concat!("/", stringify!($mod)), $type)
};
(struct $mod:ident::$type:ident) => {
$crate::diagnostic::diagnostic_url!("struct", concat!("/", stringify!($mod)), $type)
};
($kind:literal, $mod:expr, $type:ident) => {
concat!(
"https://docs.rs/jsonptr/",
env!("CARGO_PKG_VERSION"),
"/jsonptr",
$mod,
"/",
$kind,
".",
stringify!($type),
".html",
)
};
}
pub(crate) use diagnostic_url;
/// An extension trait for `Result<_, E>`, where `E` is an implementation of
/// [`Diagnostic`], that converts `E` into [`Report<E>`](`Report`), yielding
/// `Result<_, Report<E>>`.
pub trait Diagnose<'s, T> {
/// The error type returned from `diagnose` and `diagnose_with`.
type Error: Diagnostic;
/// If the `Result` is an `Err`, converts the error into a [`Report`] with
/// the supplied `subject`.
///
/// ## Example
/// ```
/// use core::any::{Any, TypeId};
/// use jsonptr::{Pointer, ParseError, Diagnose, Report};
/// let subj = "invalid/pointer";
/// let err = Pointer::parse(subj).diagnose(subj).unwrap_err();
/// assert_eq!(err.type_id(),TypeId::of::<Report<ParseError>>());
/// ```
#[allow(clippy::missing_errors_doc)]
fn diagnose(
self,
subject: impl Into<<Self::Error as Diagnostic>::Subject>,
) -> Result<T, Report<Self::Error>>;
/// If the `Result` is an `Err`, converts the error into a [`Report`] with
/// the subject returned from `f`
///
/// ## Example
/// ```
/// use core::any::{Any, TypeId};
/// use jsonptr::{Pointer, ParseError, Diagnose, Report};
/// let subj = "invalid/pointer";
/// let err = Pointer::parse(subj).diagnose_with(|| subj).unwrap_err();
///
/// assert_eq!(err.type_id(),TypeId::of::<Report<ParseError>>());
#[allow(clippy::missing_errors_doc)]
fn diagnose_with<F, S>(self, f: F) -> Result<T, Report<Self::Error>>
where
F: FnOnce() -> S,
S: Into<<Self::Error as Diagnostic>::Subject>;
}
impl<T, E> Diagnose<'_, T> for Result<T, E>
where
E: Diagnostic,
{
type Error = E;
fn diagnose(
self,
subject: impl Into<<Self::Error as Diagnostic>::Subject>,
) -> Result<T, Report<Self::Error>> {
self.map_err(|error| error.into_report(subject.into()))
}
fn diagnose_with<F, S>(self, f: F) -> Result<T, Report<Self::Error>>
where
F: FnOnce() -> S,
S: Into<<Self::Error as Diagnostic>::Subject>,
{
self.diagnose(f())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Pointer, PointerBuf};
#[test]
#[cfg(all(
feature = "assign",
feature = "miette",
feature = "serde",
feature = "json"
))]
fn assign_error() {
let mut v = serde_json::json!({"foo": {"bar": ["0"]}});
let ptr = PointerBuf::parse("/foo/bar/invalid/cannot/reach").unwrap();
let report = ptr.assign(&mut v, "qux").diagnose(ptr).unwrap_err();
println!("{:?}", miette::Report::from(report));
let ptr = PointerBuf::parse("/foo/bar/3/cannot/reach").unwrap();
let report = ptr.assign(&mut v, "qux").diagnose(ptr).unwrap_err();
println!("{:?}", miette::Report::from(report));
}
#[test]
#[cfg(all(
feature = "resolve",
feature = "miette",
feature = "serde",
feature = "json"
))]
fn resolve_error() {
let v = serde_json::json!({"foo": {"bar": ["0"]}});
let ptr = PointerBuf::parse("/foo/bar/invalid/cannot/reach").unwrap();
let report = ptr.resolve(&v).diagnose(ptr).unwrap_err();
println!("{:?}", miette::Report::from(report));
let ptr = PointerBuf::parse("/foo/bar/3/cannot/reach").unwrap();
let report = ptr.resolve(&v).diagnose(ptr).unwrap_err();
println!("{:?}", miette::Report::from(report));
}
#[test]
#[cfg(feature = "miette")]
fn parse_error() {
let invalid = "/foo/bar/invalid~3~encoding/cannot/reach";
let report = Pointer::parse(invalid).diagnose(invalid).unwrap_err();
println!("{:?}", miette::Report::from(report));
let report = PointerBuf::parse("/foo/bar/invalid~3~encoding/cannot/reach").unwrap_err();
let report = miette::Report::from(report);
println!("{report:?}");
}
}

470
vendor/jsonptr/src/index.rs vendored Normal file
View File

@@ -0,0 +1,470 @@
//! Abstract index representation for RFC 6901.
//!
//! [RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901) defines two valid
//! ways to represent array indices as Pointer tokens: non-negative integers,
//! and the character `-`, which stands for the index after the last existing
//! array member. While attempting to use `-` to resolve an array value will
//! always be out of bounds, the token can be useful when paired with utilities
//! which can mutate a value, such as this crate's [`assign`](crate::assign)
//! functionality or JSON Patch [RFC
//! 6902](https://datatracker.ietf.org/doc/html/rfc6902), as it provides a way
//! to express where to put the new element when extending an array.
//!
//! While this crate doesn't implement RFC 6902, it still must consider
//! non-numerical indices as valid, and provide a mechanism for manipulating
//! them. This is what this module provides.
//!
//! The main use of the `Index` type is when resolving a [`Token`] instance as a
//! concrete index for a given array length:
//!
//! ```
//! # use jsonptr::{index::Index, Token};
//! assert_eq!(Token::new("1").to_index(), Ok(Index::Num(1)));
//! assert_eq!(Token::new("-").to_index(), Ok(Index::Next));
//! assert!(Token::new("a").to_index().is_err());
//!
//! assert_eq!(Index::Num(0).for_len(1), Ok(0));
//! assert!(Index::Num(1).for_len(1).is_err());
//! assert!(Index::Next.for_len(1).is_err());
//!
//! assert_eq!(Index::Num(1).for_len_incl(1), Ok(1));
//! assert_eq!(Index::Next.for_len_incl(1), Ok(1));
//! assert!(Index::Num(2).for_len_incl(1).is_err());
//!
//! assert_eq!(Index::Num(42).for_len_unchecked(30), 42);
//! assert_eq!(Index::Next.for_len_unchecked(30), 30);
//! ```
use crate::Token;
use alloc::string::String;
use core::{fmt, num::ParseIntError, str::FromStr};
/// Represents an abstract index into an array.
///
/// If provided an upper bound with [`Self::for_len`] or [`Self::for_len_incl`],
/// will produce a concrete numerical index.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Index {
/// A non-negative integer value
Num(usize),
/// The `-` token, the position of the next would-be item in the array
Next,
}
impl Index {
/// Bounds the index for a given array length (exclusive).
///
/// The upper range is exclusive, so only indices that are less than
/// the given length will be accepted as valid. This ensures that
/// the resolved numerical index can be used to access an existing array
/// element.
///
/// [`Self::Next`], by consequence, is always considered *invalid*, since
/// it resolves to the array length itself.
///
/// See also [`Self::for_len_incl`] for an alternative if you wish to accept
/// [`Self::Next`] (or its numerical equivalent) as valid.
///
/// # Examples
///
/// ```
/// # use jsonptr::index::Index;
/// assert_eq!(Index::Num(0).for_len(1), Ok(0));
/// assert!(Index::Num(1).for_len(1).is_err());
/// assert!(Index::Next.for_len(1).is_err());
/// ```
/// # Errors
/// Returns [`OutOfBoundsError`] if the index is out of bounds.
pub fn for_len(&self, length: usize) -> Result<usize, OutOfBoundsError> {
match *self {
Self::Num(index) if index < length => Ok(index),
Self::Num(index) => Err(OutOfBoundsError { length, index }),
Self::Next => Err(OutOfBoundsError {
length,
index: length,
}),
}
}
/// Bounds the index for a given array length (inclusive).
///
/// The upper range is inclusive, so an index pointing to the position
/// _after_ the last element will be considered valid. Be careful when using
/// the resulting numerical index for accessing an array.
///
/// [`Self::Next`] is always considered valid.
///
/// See also [`Self::for_len`] for an alternative if you wish to ensure that
/// the resolved index can be used to access an existing array element.
///
/// # Examples
///
/// ```
/// # use jsonptr::index::Index;
/// assert_eq!(Index::Num(1).for_len_incl(1), Ok(1));
/// assert_eq!(Index::Next.for_len_incl(1), Ok(1));
/// assert!(Index::Num(2).for_len_incl(1).is_err());
/// ```
///
/// # Errors
/// Returns [`OutOfBoundsError`] if the index is out of bounds.
pub fn for_len_incl(&self, length: usize) -> Result<usize, OutOfBoundsError> {
match *self {
Self::Num(index) if index <= length => Ok(index),
Self::Num(index) => Err(OutOfBoundsError { length, index }),
Self::Next => Ok(length),
}
}
/// Resolves the index for a given array length.
///
/// No bound checking will take place. If you wish to ensure the
/// index can be used to access an existing element in the array, use
/// [`Self::for_len`] - or use [`Self::for_len_incl`] if you wish to accept
/// [`Self::Next`] as valid as well.
///
/// # Examples
///
/// ```
/// # use jsonptr::index::Index;
/// assert_eq!(Index::Num(42).for_len_unchecked(30), 42);
/// assert_eq!(Index::Next.for_len_unchecked(30), 30);
///
/// // no bounds checks
/// assert_eq!(Index::Num(34).for_len_unchecked(40), 34);
/// assert_eq!(Index::Next.for_len_unchecked(34), 34);
/// ```
pub fn for_len_unchecked(&self, length: usize) -> usize {
match *self {
Self::Num(idx) => idx,
Self::Next => length,
}
}
}
impl fmt::Display for Index {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match *self {
Self::Num(index) => write!(f, "{index}"),
Self::Next => f.write_str("-"),
}
}
}
impl From<usize> for Index {
fn from(value: usize) -> Self {
Self::Num(value)
}
}
impl FromStr for Index {
type Err = ParseIndexError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == "-" {
Ok(Index::Next)
} else if s.starts_with('0') && s != "0" {
Err(ParseIndexError::LeadingZeros)
} else {
s.chars().position(|c| !c.is_ascii_digit()).map_or_else(
|| {
s.parse::<usize>()
.map(Index::Num)
.map_err(ParseIndexError::from)
},
|offset| {
// this comes up with the `+` sign which is valid for
// representing a `usize` but not allowed in RFC 6901 array
// indices
Err(ParseIndexError::InvalidCharacter(InvalidCharacterError {
source: String::from(s),
offset,
}))
},
)
}
}
}
impl TryFrom<&Token<'_>> for Index {
type Error = ParseIndexError;
fn try_from(value: &Token) -> Result<Self, Self::Error> {
Index::from_str(value.encoded())
}
}
impl TryFrom<&str> for Index {
type Error = ParseIndexError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Index::from_str(value)
}
}
impl TryFrom<Token<'_>> for Index {
type Error = ParseIndexError;
fn try_from(value: Token) -> Result<Self, Self::Error> {
Index::from_str(value.encoded())
}
}
macro_rules! derive_try_from {
($($t:ty),+ $(,)?) => {
$(
impl TryFrom<$t> for Index {
type Error = ParseIndexError;
fn try_from(value: $t) -> Result<Self, Self::Error> {
Index::from_str(&value)
}
}
)*
}
}
derive_try_from!(String, &String);
/*
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
╔══════════════════════════════════════════════════════════════════════════════╗
║ ║
║ OutOfBoundsError ║
║ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ║
╚══════════════════════════════════════════════════════════════════════════════╝
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
*/
/// Indicates that an `Index` is not within the given bounds.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OutOfBoundsError {
/// The provided array length.
///
/// If the range is inclusive, the resolved numerical index will be strictly
/// less than this value, otherwise it could be equal to it.
pub length: usize,
/// The resolved numerical index.
///
/// Note that [`Index::Next`] always resolves to the given array length,
/// so it is only valid when the range is inclusive.
pub index: usize,
}
impl fmt::Display for OutOfBoundsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"index {} out of bounds (len: {})",
self.index, self.length
)
}
}
#[cfg(feature = "std")]
impl std::error::Error for OutOfBoundsError {}
/*
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
╔══════════════════════════════════════════════════════════════════════════════╗
║ ║
║ ParseIndexError ║
║ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ║
╚══════════════════════════════════════════════════════════════════════════════╝
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
*/
/// Indicates that the `Token` could not be parsed as valid RFC 6901 array index.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseIndexError {
/// The Token does not represent a valid integer.
InvalidInteger(ParseIntError),
/// The Token contains leading zeros.
LeadingZeros,
/// The Token contains a non-digit character.
InvalidCharacter(InvalidCharacterError),
}
impl From<ParseIntError> for ParseIndexError {
fn from(source: ParseIntError) -> Self {
Self::InvalidInteger(source)
}
}
impl fmt::Display for ParseIndexError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParseIndexError::InvalidInteger(_) => {
write!(f, "failed to parse token as an integer")
}
ParseIndexError::LeadingZeros => write!(
f,
"token contained leading zeros, which are disallowed by RFC 6901"
),
ParseIndexError::InvalidCharacter(_) => {
write!(f, "failed to parse token as an index")
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseIndexError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
ParseIndexError::InvalidInteger(source) => Some(source),
ParseIndexError::InvalidCharacter(source) => Some(source),
ParseIndexError::LeadingZeros => None,
}
}
}
/// Indicates that a non-digit character was found when parsing the RFC 6901 array index.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InvalidCharacterError {
pub(crate) source: String,
pub(crate) offset: usize,
}
impl InvalidCharacterError {
/// Returns the offset of the character in the string.
///
/// This offset is given in characters, not in bytes.
pub fn offset(&self) -> usize {
self.offset
}
/// Returns the source string.
pub fn source(&self) -> &str {
&self.source
}
/// Returns the offending character.
#[allow(clippy::missing_panics_doc)]
pub fn char(&self) -> char {
self.source
.chars()
.nth(self.offset)
.expect("char was found at offset")
}
}
impl fmt::Display for InvalidCharacterError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"token contains the non-digit character '{}', \
which is disallowed by RFC 6901",
self.char()
)
}
}
#[cfg(feature = "std")]
impl std::error::Error for InvalidCharacterError {}
/*
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
╔══════════════════════════════════════════════════════════════════════════════╗
║ ║
║ Tests ║
║ ¯¯¯¯¯¯¯ ║
╚══════════════════════════════════════════════════════════════════════════════╝
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
*/
#[cfg(test)]
mod tests {
use super::*;
use crate::Token;
#[test]
fn index_from_usize() {
let index = Index::from(5usize);
assert_eq!(index, Index::Num(5));
}
#[test]
fn index_try_from_token_num() {
let token = Token::new("3");
let index = Index::try_from(&token).unwrap();
assert_eq!(index, Index::Num(3));
}
#[test]
fn index_try_from_token_next() {
let token = Token::new("-");
let index = Index::try_from(&token).unwrap();
assert_eq!(index, Index::Next);
}
#[test]
fn index_try_from_str_num() {
let index = Index::try_from("42").unwrap();
assert_eq!(index, Index::Num(42));
}
#[test]
fn index_try_from_str_next() {
let index = Index::try_from("-").unwrap();
assert_eq!(index, Index::Next);
}
#[test]
fn index_try_from_string_num() {
let index = Index::try_from(String::from("7")).unwrap();
assert_eq!(index, Index::Num(7));
}
#[test]
fn index_try_from_string_next() {
let index = Index::try_from(String::from("-")).unwrap();
assert_eq!(index, Index::Next);
}
#[test]
fn index_for_len_incl_valid() {
assert_eq!(Index::Num(0).for_len_incl(1), Ok(0));
assert_eq!(Index::Next.for_len_incl(2), Ok(2));
}
#[test]
fn index_for_len_incl_out_of_bounds() {
Index::Num(2).for_len_incl(1).unwrap_err();
}
#[test]
fn index_for_len_unchecked() {
assert_eq!(Index::Num(10).for_len_unchecked(5), 10);
assert_eq!(Index::Next.for_len_unchecked(3), 3);
}
#[test]
fn display_index_num() {
let index = Index::Num(5);
assert_eq!(index.to_string(), "5");
}
#[test]
fn display_index_next() {
assert_eq!(Index::Next.to_string(), "-");
}
#[test]
fn for_len() {
assert_eq!(Index::Num(0).for_len(1), Ok(0));
assert!(Index::Num(1).for_len(1).is_err());
assert!(Index::Next.for_len(1).is_err());
}
#[test]
fn try_from_token() {
let token = Token::new("3");
let index = <Index as TryFrom<Token>>::try_from(token).unwrap();
assert_eq!(index, Index::Num(3));
let token = Token::new("-");
let index = Index::try_from(&token).unwrap();
assert_eq!(index, Index::Next);
}
}

85
vendor/jsonptr/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,85 @@
// rustdoc + README hack: https://linebender.org/blog/doc-include
//! <style>.rustdoc-hidden { display: none; }</style>
//! [`Pointer`]: https://docs.rs/jsonptr/latest/jsonptr/struct.Pointer.html
//! [`Pointer::tokens`]: crate::Pointer::tokens
//! [`Pointer::components`]: crate::Pointer::components
//! [`Pointer::parse`]: crate::Pointer::parse
//! [`Pointer::resolve`]: crate::Pointer::resolve
//! [`Pointer::resolve_mut`]: crate::Pointer::resolve_mut
//! [`Pointer::assign`]: crate::Pointer::assign
//! [`Pointer::delete`]: crate::Pointer::delete
//! [`PointerBuf::parse`]: crate::PointerBuf::parse
//! [`PointerBuf`]: crate::PointerBuf
//! [`from_tokens`]: crate::PointerBuf::from_tokens
//! [`Token`]: crate::Token
//! [`Tokens`]: crate::Tokens
//! [`Components`]: crate::Components
//! [`Component`]: crate::Component
//! [`index`]: crate::index
//! [`tokens`]: crate::Pointer::tokens
//! [`components`]: crate::Pointer::components
//! [`resolve`]: crate::resolve
//! [`assign`]: crate::asign
//! [`delete`]: crate::delete
//! [`Resolve`]: crate::resolve::Resolve
//! [`ResolveMut`]: crate::resolve::ResolveMut
//! [`Assign`]: crate::assign::Assign
//! [`Delete`]: crate::delete::Delete
//! [`serde`]: https://docs.rs/serde/1.0/serde/index
//! [`serde_json`]: https://docs.rs/serde_json/1.0/serde_json/enum.Value.html
//! [`serde_json::Value`]: https://docs.rs/serde_json/1.0/serde_json/enum.Value.html
//! [`toml`]: https://docs.rs/toml/0.8/toml/enum.Value.html
//! [`toml::Value`]: https://docs.rs/toml/0.8/toml/enum.Value.html
//! [`Path`]: https://doc.rust-lang.org/std/path/struct.Path.html
//! [`PathBuf`]: https://doc.rust-lang.org/std/path/struct.PathBuf.html
#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
#![deny(clippy::all, clippy::pedantic)]
#![cfg_attr(not(feature = "std"), no_std)]
#![allow(
clippy::module_name_repetitions,
clippy::into_iter_without_iter,
clippy::needless_pass_by_value,
clippy::expect_fun_call,
clippy::must_use_candidate,
clippy::similar_names
)]
#[cfg_attr(not(feature = "std"), macro_use)]
extern crate alloc;
#[cfg(feature = "assign")]
pub mod assign;
#[cfg(feature = "assign")]
pub use assign::Assign;
#[cfg(feature = "delete")]
pub mod delete;
#[cfg(feature = "delete")]
pub use delete::Delete;
#[cfg(feature = "resolve")]
pub mod resolve;
#[cfg(feature = "resolve")]
pub use resolve::{Resolve, ResolveMut};
pub mod diagnostic;
pub use diagnostic::{Diagnose, Report};
mod pointer;
pub use pointer::{ParseError, Pointer, PointerBuf, RichParseError};
mod token;
pub use token::{EncodingError, InvalidEncoding, Token, Tokens};
#[allow(deprecated)]
pub use token::InvalidEncodingError;
pub mod index;
mod component;
pub use component::{Component, Components};
#[cfg(test)]
mod arbitrary;

2353
vendor/jsonptr/src/pointer.rs vendored Normal file

File diff suppressed because it is too large Load Diff

500
vendor/jsonptr/src/pointer/slice.rs vendored Normal file
View File

@@ -0,0 +1,500 @@
use super::Pointer;
use crate::Token;
use core::ops::Bound;
pub trait PointerIndex<'p>: private::Sealed {
type Output: 'p;
fn get(self, pointer: &'p Pointer) -> Option<Self::Output>;
}
impl<'p> PointerIndex<'p> for usize {
type Output = Token<'p>;
fn get(self, pointer: &'p Pointer) -> Option<Self::Output> {
pointer.tokens().nth(self)
}
}
impl<'p> PointerIndex<'p> for core::ops::Range<usize> {
type Output = &'p Pointer;
fn get(self, pointer: &'p Pointer) -> Option<Self::Output> {
if self.end < self.start {
// never valid
return None;
}
let mut idx = 0;
let mut offset = 0;
let mut start_offset = None;
let mut end_offset = None;
for token in pointer.tokens() {
if idx == self.start {
start_offset = Some(offset);
}
if idx == self.end {
end_offset = Some(offset);
break;
}
idx += 1;
// also include the `/` separator
offset += token.encoded().len() + 1;
}
// edge case where end is last token index + 1
// this is valid because range is exclusive
if idx == self.end {
end_offset = Some(offset);
}
let slice = &pointer.0.as_bytes()[start_offset?..end_offset?];
// SAFETY: start and end offsets are token boundaries, so the slice is
// valid utf-8 (and also a valid json pointer!)
Some(unsafe { Pointer::new_unchecked(core::str::from_utf8_unchecked(slice)) })
}
}
impl<'p> PointerIndex<'p> for core::ops::RangeFrom<usize> {
type Output = &'p Pointer;
fn get(self, pointer: &'p Pointer) -> Option<Self::Output> {
{
let mut offset = 0;
let mut start_offset = None;
for (idx, token) in pointer.tokens().enumerate() {
if idx == self.start {
start_offset = Some(offset);
break;
}
// also include the `/` separator
offset += token.encoded().len() + 1;
}
let slice = &pointer.0.as_bytes()[start_offset?..];
// SAFETY: start offset is token boundary, so the slice is valid
// utf-8 (and also a valid json pointer!)
Some(unsafe { Pointer::new_unchecked(core::str::from_utf8_unchecked(slice)) })
}
}
}
impl<'p> PointerIndex<'p> for core::ops::RangeTo<usize> {
type Output = &'p Pointer;
fn get(self, pointer: &'p Pointer) -> Option<Self::Output> {
{
let mut idx = 0;
let mut offset = 0;
let mut end_offset = None;
for token in pointer.tokens() {
if idx == self.end {
end_offset = Some(offset);
break;
}
idx += 1;
// also include the `/` separator
offset += token.encoded().len() + 1;
}
// edge case where end is last token index + 1
// this is valid because range is exclusive
if idx == self.end {
end_offset = Some(offset);
}
let slice = &pointer.0.as_bytes()[..end_offset?];
// SAFETY: start and end offsets are token boundaries, so the slice is
// valid utf-8 (and also a valid json pointer!)
Some(unsafe { Pointer::new_unchecked(core::str::from_utf8_unchecked(slice)) })
}
}
}
impl<'p> PointerIndex<'p> for core::ops::RangeFull {
type Output = &'p Pointer;
fn get(self, pointer: &'p Pointer) -> Option<Self::Output> {
Some(pointer)
}
}
impl<'p> PointerIndex<'p> for core::ops::RangeInclusive<usize> {
type Output = &'p Pointer;
fn get(self, pointer: &'p Pointer) -> Option<Self::Output> {
let (start, end) = self.into_inner();
if end < start {
// never valid
return None;
}
let mut offset = 0;
let mut start_offset = None;
let mut end_offset = None;
for (idx, token) in pointer.tokens().enumerate() {
if idx == start {
start_offset = Some(offset);
}
// also include the `/` separator
offset += token.encoded().len() + 1;
// since the range is inclusive, we wish to slice up until the end
// of the token whose index is `end`, so we increment offset first
// before checking for a match
if idx == end {
end_offset = Some(offset);
break;
}
}
// notice that we don't use an inclusive range here, because we already
// acounted for the included end token when computing `end_offset` above
let slice = &pointer.0.as_bytes()[start_offset?..end_offset?];
// SAFETY: start and end offsets are token boundaries, so the slice is
// valid utf-8 (and also a valid json pointer!)
Some(unsafe { Pointer::new_unchecked(core::str::from_utf8_unchecked(slice)) })
}
}
impl<'p> PointerIndex<'p> for core::ops::RangeToInclusive<usize> {
type Output = &'p Pointer;
fn get(self, pointer: &'p Pointer) -> Option<Self::Output> {
{
let mut offset = 0;
let mut end_offset = None;
for (idx, token) in pointer.tokens().enumerate() {
// also include the `/` separator
offset += token.encoded().len() + 1;
// since the range is inclusive, we wish to slice up until the end
// of the token whose index is `end`, so we increment offset first
// before checking for a match
if idx == self.end {
end_offset = Some(offset);
break;
}
}
// notice that we don't use an inclusive range here, because we already
// acounted for the included end token when computing `end_offset` above
let slice = &pointer.0.as_bytes()[..end_offset?];
// SAFETY: start and end offsets are token boundaries, so the slice is
// valid utf-8 (and also a valid json pointer!)
Some(unsafe { Pointer::new_unchecked(core::str::from_utf8_unchecked(slice)) })
}
}
}
impl<'p> PointerIndex<'p> for (Bound<usize>, Bound<usize>) {
type Output = &'p Pointer;
fn get(self, pointer: &'p Pointer) -> Option<Self::Output> {
match self {
(Bound::Included(start), Bound::Included(end)) => pointer.get(start..=end),
(Bound::Included(start), Bound::Excluded(end)) => pointer.get(start..end),
(Bound::Included(start), Bound::Unbounded) => pointer.get(start..),
(Bound::Excluded(start), Bound::Included(end)) => pointer.get(start + 1..=end),
(Bound::Excluded(start), Bound::Excluded(end)) => pointer.get(start + 1..end),
(Bound::Excluded(start), Bound::Unbounded) => pointer.get(start + 1..),
(Bound::Unbounded, Bound::Included(end)) => pointer.get(..=end),
(Bound::Unbounded, Bound::Excluded(end)) => pointer.get(..end),
(Bound::Unbounded, Bound::Unbounded) => pointer.get(..),
}
}
}
mod private {
use core::ops;
pub trait Sealed {}
impl Sealed for usize {}
impl Sealed for ops::Range<usize> {}
impl Sealed for ops::RangeTo<usize> {}
impl Sealed for ops::RangeFrom<usize> {}
impl Sealed for ops::RangeFull {}
impl Sealed for ops::RangeInclusive<usize> {}
impl Sealed for ops::RangeToInclusive<usize> {}
impl Sealed for (ops::Bound<usize>, ops::Bound<usize>) {}
}
#[cfg(test)]
mod tests {
use core::ops::Bound;
use crate::{Pointer, Token};
#[test]
fn get_single() {
let ptr = Pointer::from_static("/foo/bar/qux");
let s = ptr.get(0);
assert_eq!(s, Some(Token::new("foo")));
let s = ptr.get(1);
assert_eq!(s, Some(Token::new("bar")));
let s = ptr.get(2);
assert_eq!(s, Some(Token::new("qux")));
let s = ptr.get(3);
assert_eq!(s, None);
let ptr = Pointer::from_static("/");
let s = ptr.get(0);
assert_eq!(s, Some(Token::new("")));
let s = ptr.get(1);
assert_eq!(s, None);
let ptr = Pointer::from_static("");
let s = ptr.get(0);
assert_eq!(s, None);
let s = ptr.get(1);
assert_eq!(s, None);
}
#[allow(clippy::reversed_empty_ranges)]
#[test]
fn get_range() {
let ptr = Pointer::from_static("/foo/bar/qux");
let s = ptr.get(0..3);
assert_eq!(s, Some(ptr));
let s = ptr.get(0..2);
assert_eq!(s, Some(Pointer::from_static("/foo/bar")));
let s = ptr.get(0..1);
assert_eq!(s, Some(Pointer::from_static("/foo")));
let s = ptr.get(0..0);
assert_eq!(s, Some(Pointer::from_static("")));
let s = ptr.get(1..3);
assert_eq!(s, Some(Pointer::from_static("/bar/qux")));
let s = ptr.get(1..2);
assert_eq!(s, Some(Pointer::from_static("/bar")));
let s = ptr.get(1..1);
assert_eq!(s, Some(Pointer::from_static("")));
let s = ptr.get(1..0);
assert_eq!(s, None);
let s = ptr.get(0..4);
assert_eq!(s, None);
let s = ptr.get(2..4);
assert_eq!(s, None);
let ptr = Pointer::from_static("/");
let s = ptr.get(0..1);
assert_eq!(s, Some(ptr));
let s = ptr.get(0..0);
assert_eq!(s, Some(Pointer::root()));
let s = ptr.get(1..0);
assert_eq!(s, None);
let s = ptr.get(0..2);
assert_eq!(s, None);
let s = ptr.get(1..2);
assert_eq!(s, None);
let s = ptr.get(1..1);
assert_eq!(s, None);
let ptr = Pointer::root();
let s = ptr.get(0..1);
assert_eq!(s, None);
let s = ptr.get(0..0);
assert_eq!(s, None);
let s = ptr.get(1..0);
assert_eq!(s, None);
let s = ptr.get(1..1);
assert_eq!(s, None);
}
#[test]
fn get_from_range() {
let ptr = Pointer::from_static("/foo/bar/qux");
let s = ptr.get(0..);
assert_eq!(s, Some(ptr));
let s = ptr.get(1..);
assert_eq!(s, Some(Pointer::from_static("/bar/qux")));
let s = ptr.get(2..);
assert_eq!(s, Some(Pointer::from_static("/qux")));
let s = ptr.get(3..);
assert_eq!(s, None);
let ptr = Pointer::from_static("/");
let s = ptr.get(0..);
assert_eq!(s, Some(ptr));
let s = ptr.get(1..);
assert_eq!(s, None);
let ptr = Pointer::from_static("");
let s = ptr.get(0..);
assert_eq!(s, None);
}
#[test]
fn get_to_range() {
let ptr = Pointer::from_static("/foo/bar/qux");
let s = ptr.get(..4);
assert_eq!(s, None);
let s = ptr.get(..3);
assert_eq!(s, Some(ptr));
let s = ptr.get(..2);
assert_eq!(s, Some(Pointer::from_static("/foo/bar")));
let s = ptr.get(..1);
assert_eq!(s, Some(Pointer::from_static("/foo")));
let s = ptr.get(..0);
assert_eq!(s, Some(Pointer::from_static("")));
let ptr = Pointer::from_static("/");
let s = ptr.get(..0);
assert_eq!(s, Some(Pointer::from_static("")));
let s = ptr.get(..1);
assert_eq!(s, Some(ptr));
let s = ptr.get(..2);
assert_eq!(s, None);
let ptr = Pointer::from_static("");
let s = ptr.get(..0);
assert_eq!(s, Some(ptr));
let s = ptr.get(..1);
assert_eq!(s, None);
}
#[test]
fn get_full_range() {
let ptr = Pointer::from_static("/foo/bar");
let s = ptr.get(..);
assert_eq!(s, Some(ptr));
let ptr = Pointer::from_static("/");
let s = ptr.get(..);
assert_eq!(s, Some(ptr));
let ptr = Pointer::from_static("");
let s = ptr.get(..);
assert_eq!(s, Some(ptr));
}
#[allow(clippy::reversed_empty_ranges)]
#[test]
fn get_range_inclusive() {
let ptr = Pointer::from_static("/foo/bar/qux");
let s = ptr.get(0..=3);
assert_eq!(s, None);
let s = ptr.get(0..=2);
assert_eq!(s, Some(ptr));
let s = ptr.get(0..=1);
assert_eq!(s, Some(Pointer::from_static("/foo/bar")));
let s = ptr.get(0..=0);
assert_eq!(s, Some(Pointer::from_static("/foo")));
let s = ptr.get(1..=3);
assert_eq!(s, None);
let s = ptr.get(1..=2);
assert_eq!(s, Some(Pointer::from_static("/bar/qux")));
let s = ptr.get(1..=1);
assert_eq!(s, Some(Pointer::from_static("/bar")));
let s = ptr.get(1..=0);
assert_eq!(s, None);
let ptr = Pointer::from_static("/");
let s = ptr.get(0..=0);
assert_eq!(s, Some(ptr));
let s = ptr.get(1..=0);
assert_eq!(s, None);
let s = ptr.get(0..=1);
assert_eq!(s, None);
let s = ptr.get(1..=1);
assert_eq!(s, None);
let ptr = Pointer::root();
let s = ptr.get(0..=1);
assert_eq!(s, None);
let s = ptr.get(0..=0);
assert_eq!(s, None);
let s = ptr.get(1..=0);
assert_eq!(s, None);
let s = ptr.get(1..=1);
assert_eq!(s, None);
}
#[test]
fn get_to_range_inclusive() {
let ptr = Pointer::from_static("/foo/bar/qux");
let s = ptr.get(..=3);
assert_eq!(s, None);
let s = ptr.get(..=2);
assert_eq!(s, Some(ptr));
let s = ptr.get(..=1);
assert_eq!(s, Some(Pointer::from_static("/foo/bar")));
let s = ptr.get(..=0);
assert_eq!(s, Some(Pointer::from_static("/foo")));
let ptr = Pointer::from_static("/");
let s = ptr.get(..=0);
assert_eq!(s, Some(ptr));
let s = ptr.get(..=1);
assert_eq!(s, None);
let ptr = Pointer::from_static("");
let s = ptr.get(..=0);
assert_eq!(s, None);
let s = ptr.get(..=1);
assert_eq!(s, None);
}
#[test]
fn get_by_explicit_bounds() {
let ptr = Pointer::from_static("/foo/bar/qux");
let s = ptr.get((Bound::Excluded(0), Bound::Included(2)));
assert_eq!(s, Some(Pointer::from_static("/bar/qux")));
let s = ptr.get((Bound::Excluded(0), Bound::Excluded(2)));
assert_eq!(s, Some(Pointer::from_static("/bar")));
let s = ptr.get((Bound::Excluded(0), Bound::Unbounded));
assert_eq!(s, Some(Pointer::from_static("/bar/qux")));
let s = ptr.get((Bound::Included(0), Bound::Included(2)));
assert_eq!(s, Some(Pointer::from_static("/foo/bar/qux")));
let s = ptr.get((Bound::Included(0), Bound::Excluded(2)));
assert_eq!(s, Some(Pointer::from_static("/foo/bar")));
let s = ptr.get((Bound::Included(0), Bound::Unbounded));
assert_eq!(s, Some(Pointer::from_static("/foo/bar/qux")));
let s = ptr.get((Bound::Unbounded, Bound::Included(2)));
assert_eq!(s, Some(Pointer::from_static("/foo/bar/qux")));
let s = ptr.get((Bound::Unbounded, Bound::Excluded(2)));
assert_eq!(s, Some(Pointer::from_static("/foo/bar")));
let s = ptr.get((Bound::Unbounded, Bound::Unbounded));
assert_eq!(s, Some(Pointer::from_static("/foo/bar/qux")));
let ptr = Pointer::from_static("/foo/bar");
let s = ptr.get((Bound::Excluded(0), Bound::Included(2)));
assert_eq!(s, None);
let s = ptr.get((Bound::Excluded(0), Bound::Excluded(2)));
assert_eq!(s, Some(Pointer::from_static("/bar")));
let s = ptr.get((Bound::Excluded(0), Bound::Unbounded));
assert_eq!(s, Some(Pointer::from_static("/bar")));
let s = ptr.get((Bound::Included(0), Bound::Included(2)));
assert_eq!(s, None);
let s = ptr.get((Bound::Included(0), Bound::Excluded(2)));
assert_eq!(s, Some(ptr));
let s = ptr.get((Bound::Included(0), Bound::Unbounded));
assert_eq!(s, Some(ptr));
let s = ptr.get((Bound::Unbounded, Bound::Included(2)));
assert_eq!(s, None);
let s = ptr.get((Bound::Unbounded, Bound::Excluded(2)));
assert_eq!(s, Some(ptr));
let s = ptr.get((Bound::Unbounded, Bound::Unbounded));
assert_eq!(s, Some(ptr));
// testing only the start excluded case a bit more exhaustively since
// other cases just delegate directly (so are covered by other tests)
let ptr = Pointer::from_static("/");
let s = ptr.get((Bound::Excluded(0), Bound::Included(0)));
assert_eq!(s, None);
let s = ptr.get((Bound::Excluded(0), Bound::Excluded(0)));
assert_eq!(s, None);
let s = ptr.get((Bound::Excluded(0), Bound::Unbounded));
assert_eq!(s, None);
let ptr = Pointer::from_static("");
let s = ptr.get((Bound::Excluded(0), Bound::Included(0)));
assert_eq!(s, None);
let s = ptr.get((Bound::Excluded(0), Bound::Excluded(0)));
assert_eq!(s, None);
let s = ptr.get((Bound::Excluded(0), Bound::Unbounded));
assert_eq!(s, None);
}
}

861
vendor/jsonptr/src/resolve.rs vendored Normal file
View File

@@ -0,0 +1,861 @@
//! # Resolve values based on JSON [`Pointer`]s
//!
//! This module provides the [`Resolve`] and [`ResolveMut`] traits which are
//! implemented by types that can internally resolve a value based on a JSON
//! Pointer.
//!
//! This module is enabled by default with the `"resolve"` feature flag.
//!
//! ## Usage
//! [`Resolve`] and [`ResolveMut`] can be used directly or through the
//! [`resolve`](Pointer::resolve) and [`resolve_mut`](Pointer::resolve_mut)
//! methods on [`Pointer`] and [`PointerBuf`](crate::PointerBuf).
//!
//! ```rust
//! use jsonptr::{Pointer, Resolve, ResolveMut};
//! use serde_json::json;
//!
//! let ptr = Pointer::from_static("/foo/1");
//! let mut data = json!({"foo": ["bar", "baz"]});
//!
//! let value = ptr.resolve(&data).unwrap();
//! assert_eq!(value, &json!("baz"));
//!
//! let value = data.resolve_mut(ptr).unwrap();
//! assert_eq!(value, &json!("baz"));
//! ```
//!
//! ## Provided implementations
//!
//! | Lang | value type | feature flag | Default |
//! | ----- |: ----------------- :|: ---------- :| ------- |
//! | JSON | `serde_json::Value` | `"json"` | ✓ |
//! | TOML | `toml::Value` | `"toml"` | |
//!
//!
use crate::{
diagnostic::{diagnostic_url, Diagnostic, Label},
index::{OutOfBoundsError, ParseIndexError},
Pointer, PointerBuf, Token,
};
use alloc::{boxed::Box, string::ToString};
use core::iter::once;
/// A trait implemented by types which can resolve a reference to a value type
/// from a path represented by a JSON [`Pointer`].
pub trait Resolve {
/// The type of value that this implementation can operate on.
type Value;
/// Error associated with `Resolve`
type Error;
/// Resolve a reference to `Self::Value` based on the path in a [Pointer].
///
/// ## Errors
/// Returns a [`Self::Error`](Resolve::Error) if the [`Pointer`] can not
/// be resolved.
fn resolve(&self, ptr: &Pointer) -> Result<&Self::Value, Self::Error>;
}
/// A trait implemented by types which can resolve a mutable reference to a
/// value type from a path represented by a JSON [`Pointer`].
pub trait ResolveMut {
/// The type of value that is being resolved.
type Value;
/// Error associated with `ResolveMut`
type Error;
/// Resolve a mutable reference to a `serde_json::Value` based on the path
/// in a JSON Pointer.
///
/// ## Errors
/// Returns a [`Self::Error`](ResolveMut::Error) if the [`Pointer`] can not
/// be resolved.
fn resolve_mut(&mut self, ptr: &Pointer) -> Result<&mut Self::Value, Self::Error>;
}
// TODO: should ResolveError be deprecated?
/// Alias for [`Error`].
pub type ResolveError = Error;
/// Indicates that the `Pointer` could not be resolved.
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
/// `Pointer` could not be resolved because a `Token` for an array index is
/// not a valid integer or dash (`"-"`).
///
/// ## Example
/// ```rust
/// # use serde_json::json;
/// # use jsonptr::Pointer;
/// let data = json!({ "foo": ["bar"] });
/// let ptr = Pointer::from_static("/foo/invalid");
/// assert!(ptr.resolve(&data).unwrap_err().is_failed_to_parse_index());
/// ```
FailedToParseIndex {
/// Position (index) of the token which failed to parse as an [`Index`](crate::index::Index)
position: usize,
/// Offset of the partial pointer starting with the invalid index.
offset: usize,
/// The source `ParseIndexError`
source: ParseIndexError,
},
/// A [`Token`] within the [`Pointer`] contains an [`Index`] which is out of
/// bounds.
///
/// ## Example
/// ```rust
/// # use serde_json::json;
/// # use jsonptr::Pointer;
/// let data = json!({ "foo": ["bar"] });
/// let ptr = Pointer::from_static("/foo/1");
/// assert!(ptr.resolve(&data).unwrap_err().is_out_of_bounds());
OutOfBounds {
/// Position (index) of the token which failed to parse as an [`Index`](crate::index::Index)
position: usize,
/// Offset of the partial pointer starting with the invalid index.
offset: usize,
/// The source `OutOfBoundsError`
source: OutOfBoundsError,
},
/// `Pointer` could not be resolved as a segment of the path was not found.
///
/// ## Example
/// ```rust
/// # use serde_json::json;
/// # use jsonptr::{Pointer};
/// let mut data = json!({ "foo": "bar" });
/// let ptr = Pointer::from_static("/bar");
/// assert!(ptr.resolve(&data).unwrap_err().is_not_found());
/// ```
NotFound {
/// Position (index) of the token which was not found.
position: usize,
/// Offset of the pointer starting with the `Token` which was not found.
offset: usize,
},
/// `Pointer` could not be resolved as the path contains a scalar value
/// before fully exhausting the path.
///
/// ## Example
/// ```rust
/// # use serde_json::json;
/// # use jsonptr::Pointer;
/// let mut data = json!({ "foo": "bar" });
/// let ptr = Pointer::from_static("/foo/unreachable");
/// let err = ptr.resolve(&data).unwrap_err();
/// assert!(err.is_unreachable());
/// ```
Unreachable {
/// Position (index) of the token which was unreachable.
position: usize,
/// Offset of the pointer which was unreachable.
offset: usize,
},
}
impl Error {
/// Offset of the partial pointer starting with the token which caused the
/// error.
pub fn offset(&self) -> usize {
match self {
Self::FailedToParseIndex { offset, .. }
| Self::OutOfBounds { offset, .. }
| Self::NotFound { offset, .. }
| Self::Unreachable { offset, .. } => *offset,
}
}
/// Position (index) of the token which caused the error.
pub fn position(&self) -> usize {
match self {
Self::FailedToParseIndex { position, .. }
| Self::OutOfBounds { position, .. }
| Self::NotFound { position, .. }
| Self::Unreachable { position, .. } => *position,
}
}
/// Returns `true` if this error is `FailedToParseIndex`; otherwise returns
/// `false`.
pub fn is_unreachable(&self) -> bool {
matches!(self, Self::Unreachable { .. })
}
/// Returns `true` if this error is `FailedToParseIndex`; otherwise returns
/// `false`.
pub fn is_not_found(&self) -> bool {
matches!(self, Self::NotFound { .. })
}
/// Returns `true` if this error is `FailedToParseIndex`; otherwise returns
/// `false`.
pub fn is_out_of_bounds(&self) -> bool {
matches!(self, Self::OutOfBounds { .. })
}
/// Returns `true` if this error is `FailedToParseIndex`; otherwise returns
/// `false`.
pub fn is_failed_to_parse_index(&self) -> bool {
matches!(self, Self::FailedToParseIndex { .. })
}
}
impl Diagnostic for Error {
type Subject = PointerBuf;
fn url() -> &'static str {
diagnostic_url!(enum assign::Error)
}
fn labels(&self, origin: &Self::Subject) -> Option<Box<dyn Iterator<Item = Label>>> {
let position = self.position();
let token = origin.get(position)?;
let offset = if self.offset() + 1 < origin.as_str().len() {
self.offset() + 1
} else {
self.offset()
};
let len = token.encoded().len();
let text = match self {
Error::FailedToParseIndex { .. } => "not an array index".to_string(),
Error::OutOfBounds { source, .. } => source.to_string(),
Error::NotFound { .. } => "not found in value".to_string(),
Error::Unreachable { .. } => "unreachable".to_string(),
};
Some(Box::new(once(Label::new(text, offset, len))))
}
}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::FailedToParseIndex { offset, .. } => {
write!(f, "resolve failed: json pointer token at offset {offset} failed to parse as an index")
}
Self::OutOfBounds { offset, .. } => {
write!(
f,
"resolve failed: json pointer token at offset {offset} is out of bounds"
)
}
Self::NotFound { offset, .. } => {
write!(
f,
"resolve failed: json pointer token at {offset} was not found in value"
)
}
Self::Unreachable { offset, .. } => {
write!(f, "resolve failed: json pointer token at {offset} is unreachable (previous token resolved to a scalar or null value)")
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::FailedToParseIndex { source, .. } => Some(source),
Self::OutOfBounds { source, .. } => Some(source),
_ => None,
}
}
}
#[cfg(feature = "json")]
mod json {
use super::{parse_index, Error, Pointer, Resolve, ResolveMut};
use serde_json::Value;
impl Resolve for Value {
type Value = Value;
type Error = Error;
fn resolve(&self, mut ptr: &Pointer) -> Result<&Value, Self::Error> {
let mut offset = 0;
let mut position = 0;
let mut value = self;
while let Some((token, rem)) = ptr.split_front() {
let tok_len = token.encoded().len();
ptr = rem;
value = match value {
Value::Array(v) => {
let idx = token
.to_index()
.map_err(|source| Error::FailedToParseIndex {
position,
offset,
source,
})?
.for_len(v.len())
.map_err(|source| Error::OutOfBounds {
position,
offset,
source,
})?;
Ok(&v[idx])
}
Value::Object(v) => v
.get(token.decoded().as_ref())
.ok_or(Error::NotFound { position, offset }),
// found a leaf node but the pointer hasn't been exhausted
_ => Err(Error::Unreachable { position, offset }),
}?;
offset += 1 + tok_len;
position += 1;
}
Ok(value)
}
}
impl ResolveMut for Value {
type Value = Value;
type Error = Error;
fn resolve_mut(&mut self, mut ptr: &Pointer) -> Result<&mut Value, Error> {
let mut offset = 0;
let mut position = 0;
let mut value = self;
while let Some((token, rem)) = ptr.split_front() {
let tok_len = token.encoded().len();
ptr = rem;
value = match value {
Value::Array(array) => {
let idx = parse_index(token, array.len(), position, offset)?;
Ok(&mut array[idx])
}
Value::Object(v) => v
.get_mut(token.decoded().as_ref())
.ok_or(Error::NotFound { position, offset }),
// found a leaf node but the pointer hasn't been exhausted
_ => Err(Error::Unreachable { position, offset }),
}?;
offset += 1 + tok_len;
position += 1;
}
Ok(value)
}
}
}
fn parse_index(
token: Token,
array_len: usize,
position: usize,
offset: usize,
) -> Result<usize, Error> {
token
.to_index()
.map_err(|source| Error::FailedToParseIndex {
position,
offset,
source,
})?
.for_len(array_len)
.map_err(|source| Error::OutOfBounds {
position,
offset,
source,
})
}
#[cfg(feature = "toml")]
mod toml {
use super::{Error, Resolve, ResolveMut};
use crate::Pointer;
use toml::Value;
impl Resolve for Value {
type Value = Value;
type Error = Error;
fn resolve(&self, mut ptr: &Pointer) -> Result<&Value, Self::Error> {
let mut offset = 0;
let mut position = 0;
let mut value = self;
while let Some((token, rem)) = ptr.split_front() {
let tok_len = token.encoded().len();
ptr = rem;
value = match value {
Value::Array(v) => {
let idx = token
.to_index()
.map_err(|source| Error::FailedToParseIndex {
position,
offset,
source,
})?
.for_len(v.len())
.map_err(|source| Error::OutOfBounds {
position,
offset,
source,
})?;
Ok(&v[idx])
}
Value::Table(v) => v
.get(token.decoded().as_ref())
.ok_or(Error::NotFound { position, offset }),
// found a leaf node but the pointer hasn't been exhausted
_ => Err(Error::Unreachable { position, offset }),
}?;
offset += 1 + tok_len;
position += 1;
}
Ok(value)
}
}
impl ResolveMut for Value {
type Value = Value;
type Error = Error;
fn resolve_mut(&mut self, mut ptr: &Pointer) -> Result<&mut Value, Error> {
let mut offset = 0;
let mut position = 0;
let mut value = self;
while let Some((token, rem)) = ptr.split_front() {
let tok_len = token.encoded().len();
ptr = rem;
value = match value {
Value::Array(array) => {
let idx = token
.to_index()
.map_err(|source| Error::FailedToParseIndex {
position,
offset,
source,
})?
.for_len(array.len())
.map_err(|source| Error::OutOfBounds {
position,
offset,
source,
})?;
Ok(&mut array[idx])
}
Value::Table(v) => v
.get_mut(token.decoded().as_ref())
.ok_or(Error::NotFound { position, offset }),
// found a leaf node but the pointer hasn't been exhausted
_ => Err(Error::Unreachable { position, offset }),
}?;
offset += 1 + tok_len;
position += 1;
}
Ok(value)
}
}
}
#[cfg(test)]
mod tests {
use super::{Error, Resolve, ResolveMut};
use crate::{
index::{OutOfBoundsError, ParseIndexError},
Pointer,
};
use core::fmt;
#[test]
fn resolve_error_is_unreachable() {
let err = Error::FailedToParseIndex {
position: 0,
offset: 0,
source: ParseIndexError::InvalidInteger("invalid".parse::<usize>().unwrap_err()),
};
assert!(!err.is_unreachable());
let err = Error::OutOfBounds {
position: 0,
offset: 0,
source: OutOfBoundsError {
index: 1,
length: 0,
},
};
assert!(!err.is_unreachable());
let err = Error::NotFound {
position: 0,
offset: 0,
};
assert!(!err.is_unreachable());
let err = Error::Unreachable {
position: 0,
offset: 0,
};
assert!(err.is_unreachable());
}
#[test]
fn resolve_error_is_not_found() {
let err = Error::FailedToParseIndex {
position: 0,
offset: 0,
source: ParseIndexError::InvalidInteger("invalid".parse::<usize>().unwrap_err()),
};
assert!(!err.is_not_found());
let err = Error::OutOfBounds {
position: 0,
offset: 0,
source: OutOfBoundsError {
index: 1,
length: 0,
},
};
assert!(!err.is_not_found());
let err = Error::NotFound {
position: 0,
offset: 0,
};
assert!(err.is_not_found());
let err = Error::Unreachable {
position: 0,
offset: 0,
};
assert!(!err.is_not_found());
}
#[test]
fn resolve_error_is_out_of_bounds() {
let err = Error::FailedToParseIndex {
position: 0,
offset: 0,
source: ParseIndexError::InvalidInteger("invalid".parse::<usize>().unwrap_err()),
};
assert!(!err.is_out_of_bounds());
let err = Error::OutOfBounds {
position: 0,
offset: 0,
source: OutOfBoundsError {
index: 1,
length: 0,
},
};
assert!(err.is_out_of_bounds());
let err = Error::NotFound {
position: 0,
offset: 0,
};
assert!(!err.is_out_of_bounds());
let err = Error::Unreachable {
position: 0,
offset: 0,
};
assert!(!err.is_out_of_bounds());
}
#[test]
fn resolve_error_is_failed_to_parse_index() {
let err = Error::FailedToParseIndex {
position: 0,
offset: 0,
source: ParseIndexError::InvalidInteger("invalid".parse::<usize>().unwrap_err()),
};
assert!(err.is_failed_to_parse_index());
let err = Error::OutOfBounds {
position: 0,
offset: 0,
source: OutOfBoundsError {
index: 1,
length: 0,
},
};
assert!(!err.is_failed_to_parse_index());
let err = Error::NotFound {
position: 0,
offset: 0,
};
assert!(!err.is_failed_to_parse_index());
let err = Error::Unreachable {
position: 0,
offset: 0,
};
assert!(!err.is_failed_to_parse_index());
}
/*
╔═══════════════════════════════════════════════════╗
║ json ║
╚═══════════════════════════════════════════════════╝
*/
#[test]
#[cfg(feature = "json")]
fn resolve_json() {
use serde_json::json;
let data = &json!({
"array": ["bar", "baz"],
"object": {
"object": {"baz": {"qux": "quux"}},
"strings": ["zero", "one", "two"],
"nothing": null,
"bool": true,
"objects": [{"field": "zero"}, {"field": "one"}, {"field": "two"}]
},
"": 0,
"a/b": 1,
"c%d": 2,
"e^f": 3,
"g|h": 4,
"i\\j": 5,
"k\"l": 6,
" ": 7,
"m~n": 8
});
// let data = &data;
Test::all([
// 0
Test {
ptr: "",
data,
expected: Ok(data),
},
// 1
Test {
ptr: "/array",
data,
expected: Ok(data.get("array").unwrap()), // ["bar", "baz"]
},
// 2
Test {
ptr: "/array/0",
data,
expected: Ok(data.get("array").unwrap().get(0).unwrap()), // "bar"
},
// 3
Test {
ptr: "/a~1b",
data,
expected: Ok(data.get("a/b").unwrap()), // 1
},
// 4
Test {
ptr: "/c%d",
data,
expected: Ok(data.get("c%d").unwrap()), // 2
},
// 5
Test {
ptr: "/e^f",
data,
expected: Ok(data.get("e^f").unwrap()), // 3
},
// 6
Test {
ptr: "/g|h",
data,
expected: Ok(data.get("g|h").unwrap()), // 4
},
// 7
Test {
ptr: "/i\\j",
data,
expected: Ok(data.get("i\\j").unwrap()), // 5
},
// 8
Test {
ptr: "/k\"l",
data,
expected: Ok(data.get("k\"l").unwrap()), // 6
},
// 9
Test {
ptr: "/ ",
data,
expected: Ok(data.get(" ").unwrap()), // 7
},
// 10
Test {
ptr: "/m~0n",
data,
expected: Ok(data.get("m~n").unwrap()), // 8
},
// 11
Test {
ptr: "/object/bool/unresolvable",
data,
expected: Err(Error::Unreachable {
position: 2,
offset: 12,
}),
},
// 12
Test {
ptr: "/object/not_found",
data,
expected: Err(Error::NotFound {
position: 1,
offset: 7,
}),
},
]);
}
/*
╔═══════════════════════════════════════════════════╗
║ toml ║
╚═══════════════════════════════════════════════════╝
*/
#[test]
#[cfg(feature = "toml")]
fn resolve_toml() {
use toml::{toml, Value};
let data = &Value::Table(toml! {
"array" = ["bar", "baz"]
"object" = {
"object" = {"baz" = {"qux" = "quux"}},
"strings" = ["zero", "one", "two"],
"bool" = true,
"objects" = [{"field" = "zero"}, {"field" = "one"}, {"field" = "two"}]
}
"" = 0
"a/b" = 1
"c%d" = 2
"e^f" = 3
"g|h" = 4
"i\\j" = 5
"k\"l" = 6
" " = 7
"m~n" = 8
});
// let data = &data;
Test::all([
Test {
ptr: "",
data,
expected: Ok(data),
},
Test {
ptr: "/array",
data,
expected: Ok(data.get("array").unwrap()), // ["bar", "baz"]
},
Test {
ptr: "/array/0",
data,
expected: Ok(data.get("array").unwrap().get(0).unwrap()), // "bar"
},
Test {
ptr: "/a~1b",
data,
expected: Ok(data.get("a/b").unwrap()), // 1
},
Test {
ptr: "/c%d",
data,
expected: Ok(data.get("c%d").unwrap()), // 2
},
Test {
ptr: "/e^f",
data,
expected: Ok(data.get("e^f").unwrap()), // 3
},
Test {
ptr: "/g|h",
data,
expected: Ok(data.get("g|h").unwrap()), // 4
},
Test {
ptr: "/i\\j",
data,
expected: Ok(data.get("i\\j").unwrap()), // 5
},
Test {
ptr: "/k\"l",
data,
expected: Ok(data.get("k\"l").unwrap()), // 6
},
Test {
ptr: "/ ",
data,
expected: Ok(data.get(" ").unwrap()), // 7
},
Test {
ptr: "/m~0n",
data,
expected: Ok(data.get("m~n").unwrap()), // 8
},
Test {
ptr: "/object/bool/unresolvable",
data,
expected: Err(Error::Unreachable {
position: 2,
offset: 12,
}),
},
Test {
ptr: "/object/not_found",
data,
expected: Err(Error::NotFound {
position: 1,
offset: 7,
}),
},
]);
}
struct Test<'v, V> {
ptr: &'static str,
expected: Result<&'v V, Error>,
data: &'v V,
}
impl<'v, V> Test<'v, V>
where
V: Resolve<Value = V, Error = Error>
+ ResolveMut<Value = V, Error = Error>
+ Clone
+ PartialEq
+ fmt::Display
+ fmt::Debug,
{
fn all(tests: impl IntoIterator<Item = Test<'v, V>>) {
tests.into_iter().enumerate().for_each(|(i, t)| t.run(i));
}
fn run(self, _i: usize) {
_ = self;
let Test {
ptr,
data,
expected,
} = self;
let ptr = Pointer::from_static(ptr);
// cloning the data & expected to make comparison easier
let mut data = data.clone();
let expected = expected.cloned();
// testing Resolve
let res = data.resolve(ptr).cloned();
assert_eq!(&res, &expected);
// testing ResolveMut
let res = data.resolve_mut(ptr).cloned();
assert_eq!(&res, &expected);
}
}
}

520
vendor/jsonptr/src/token.rs vendored Normal file
View File

@@ -0,0 +1,520 @@
use core::str::Split;
use crate::index::{Index, ParseIndexError};
use alloc::{
borrow::Cow,
fmt,
string::{String, ToString},
vec::Vec,
};
const ENCODED_TILDE: &[u8] = b"~0";
const ENCODED_SLASH: &[u8] = b"~1";
const ENC_PREFIX: u8 = b'~';
const TILDE_ENC: u8 = b'0';
const SLASH_ENC: u8 = b'1';
/*
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
╔══════════════════════════════════════════════════════════════════════════════╗
║ ║
║ Token ║
║ ¯¯¯¯¯¯¯ ║
╚══════════════════════════════════════════════════════════════════════════════╝
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
*/
/// A `Token` is a segment of a JSON [`Pointer`](crate::Token), preceded by `'/'` (`%x2F`).
///
/// `Token`s can represent a key in a JSON object or an index in an array.
///
/// - Indexes should not contain leading zeros.
/// - When dealing with arrays or path expansion for assignment, `"-"` represent
/// the next, non-existent index in a JSON array.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Token<'a> {
inner: Cow<'a, str>,
}
impl<'a> Token<'a> {
/// Constructs a `Token` from an RFC 6901 encoded string.
///
/// This is like [`Self::from_encoded`], except that no validation is
/// performed on the input string.
///
/// ## Safety
/// Input string must be RFC 6901 encoded.
pub(crate) unsafe fn from_encoded_unchecked(inner: impl Into<Cow<'a, str>>) -> Self {
Self {
inner: inner.into(),
}
}
/// Constructs a `Token` from an RFC 6901 encoded string.
///
/// To be valid, the string must not contain any `/` characters, and any `~`
/// characters must be followed by either `0` or `1`.
///
/// This function does not allocate.
///
/// # Examples
///
/// ```
/// # use jsonptr::Token;
/// assert_eq!(Token::from_encoded("~1foo~1~0bar").unwrap().decoded(), "/foo/~bar");
/// let err = Token::from_encoded("foo/oops~bar").unwrap_err();
/// assert_eq!(err.offset, 3);
/// ```
///
/// ## Errors
/// Returns `InvalidEncodingError` if the input string is not a valid RFC
/// 6901 (`~` must be followed by `0` or `1`)
pub fn from_encoded(s: &'a str) -> Result<Self, EncodingError> {
let mut escaped = false;
for (offset, b) in s.bytes().enumerate() {
match b {
b'/' => {
return Err(EncodingError {
offset,
source: InvalidEncoding::Slash,
})
}
ENC_PREFIX => {
escaped = true;
}
TILDE_ENC | SLASH_ENC if escaped => {
escaped = false;
}
_ => {
if escaped {
return Err(EncodingError {
offset,
source: InvalidEncoding::Tilde,
});
}
}
}
}
if escaped {
return Err(EncodingError {
offset: s.len(),
source: InvalidEncoding::Slash,
});
}
Ok(Self { inner: s.into() })
}
/// Constructs a `Token` from an arbitrary string.
///
/// If the string contains a `/` or a `~`, then it will be assumed not
/// encoded, in which case this function will encode it, allocating a new
/// string.
///
/// If the string is already encoded per RFC 6901, use
/// [`Self::from_encoded`] instead, otherwise it will end up double-encoded.
///
/// # Examples
///
/// ```
/// # use jsonptr::Token;
/// assert_eq!(Token::new("/foo/~bar").encoded(), "~1foo~1~0bar");
/// ```
pub fn new(s: impl Into<Cow<'a, str>>) -> Self {
let s = s.into();
if let Some(i) = s.bytes().position(|b| b == b'/' || b == b'~') {
let input = s.as_bytes();
// we could take advantage of [`Cow::into_owned`] here, but it would
// mean copying over the entire string, only to overwrite a portion
// of it... so instead we explicitly allocate a new buffer and copy
// only the prefix until the first encoded character
// NOTE: the output is at least as large as the input + 1, so we
// allocate that much capacity ahead of time
let mut bytes = Vec::with_capacity(input.len() + 1);
bytes.extend_from_slice(&input[..i]);
for &b in &input[i..] {
match b {
b'/' => {
bytes.extend_from_slice(ENCODED_SLASH);
}
b'~' => {
bytes.extend_from_slice(ENCODED_TILDE);
}
other => {
bytes.push(other);
}
}
}
Self {
// SAFETY: we started from a valid UTF-8 sequence of bytes,
// and only replaced some ASCII characters with other two ASCII
// characters, so the output is guaranteed valid UTF-8.
inner: Cow::Owned(unsafe { String::from_utf8_unchecked(bytes) }),
}
} else {
Self { inner: s }
}
}
/// Converts into an owned copy of this token.
///
/// If the token is not already owned, this will clone the referenced string
/// slice.
pub fn into_owned(self) -> Token<'static> {
Token {
inner: Cow::Owned(self.inner.into_owned()),
}
}
/// Extracts an owned copy of this token.
///
/// If the token is not already owned, this will clone the referenced string
/// slice.
///
/// This method is like [`Self::into_owned`], except it doesn't take
/// ownership of the original `Token`.
pub fn to_owned(&self) -> Token<'static> {
Token {
inner: Cow::Owned(self.inner.clone().into_owned()),
}
}
/// Returns the encoded string representation of the `Token`.
///
/// # Examples
///
/// ```
/// # use jsonptr::Token;
/// assert_eq!(Token::new("~bar").encoded(), "~0bar");
/// ```
pub fn encoded(&self) -> &str {
&self.inner
}
/// Returns the decoded string representation of the `Token`.
///
/// # Examples
///
/// ```
/// # use jsonptr::Token;
/// assert_eq!(Token::new("~bar").decoded(), "~bar");
/// ```
pub fn decoded(&self) -> Cow<'_, str> {
if let Some(i) = self.inner.bytes().position(|b| b == ENC_PREFIX) {
let input = self.inner.as_bytes();
// we could take advantage of [`Cow::into_owned`] here, but it would
// mean copying over the entire string, only to overwrite a portion
// of it... so instead we explicitly allocate a new buffer and copy
// only the prefix until the first encoded character
// NOTE: the output is at least as large as the input + 1, so we
// allocate that much capacity ahead of time
let mut bytes = Vec::with_capacity(input.len() + 1);
bytes.extend_from_slice(&input[..i]);
// we start from the first escaped character
let mut escaped = true;
for &b in &input[i + 1..] {
match b {
ENC_PREFIX => {
escaped = true;
}
TILDE_ENC if escaped => {
bytes.push(b'~');
escaped = false;
}
SLASH_ENC if escaped => {
bytes.push(b'/');
escaped = false;
}
other => {
bytes.push(other);
}
}
}
// SAFETY: we start from a valid String, and only write valid UTF-8
// byte sequences into it.
Cow::Owned(unsafe { String::from_utf8_unchecked(bytes) })
} else {
// if there are no encoded characters, we don't need to allocate!
self.inner.clone()
}
}
/// Attempts to parse the given `Token` as an array index.
///
/// Per [RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901#section-4),
/// the acceptable values are non-negative integers and the `-` character,
/// which stands for the next, non-existent member after the last array
/// element.
///
/// ## Examples
///
/// ```
/// # use jsonptr::{index::Index, Token};
/// assert_eq!(Token::new("-").to_index(), Ok(Index::Next));
/// assert_eq!(Token::new("0").to_index(), Ok(Index::Num(0)));
/// assert_eq!(Token::new("2").to_index(), Ok(Index::Num(2)));
/// assert!(Token::new("a").to_index().is_err());
/// assert!(Token::new("-1").to_index().is_err());
/// ```
/// ## Errors
/// Returns [`ParseIndexError`] if the token is not a valid array index.
pub fn to_index(&self) -> Result<Index, ParseIndexError> {
self.try_into()
}
/// Returns if the `Token` is `-`, which stands for the next array index.
///
/// See also [`Self::to_index`].
pub fn is_next(&self) -> bool {
matches!(self.to_index(), Ok(Index::Next))
}
}
macro_rules! impl_from_num {
($($ty:ty),*) => {
$(
impl From<$ty> for Token<'static> {
fn from(v: $ty) -> Self {
// SAFETY: only used for integer types, which are always valid
unsafe { Token::from_encoded_unchecked(v.to_string()) }
}
}
)*
};
}
impl_from_num!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize);
impl<'a> From<&'a str> for Token<'a> {
fn from(value: &'a str) -> Self {
Token::new(value)
}
}
impl<'a> From<&'a String> for Token<'a> {
fn from(value: &'a String) -> Self {
Token::new(value)
}
}
impl From<String> for Token<'static> {
fn from(value: String) -> Self {
Token::new(value)
}
}
impl<'a> From<&Token<'a>> for Token<'a> {
fn from(value: &Token<'a>) -> Self {
value.clone()
}
}
impl alloc::fmt::Display for Token<'_> {
fn fmt(&self, f: &mut alloc::fmt::Formatter<'_>) -> alloc::fmt::Result {
write!(f, "{}", self.decoded())
}
}
/*
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
╔══════════════════════════════════════════════════════════════════════════════╗
║ ║
║ Tokens ║
║ ¯¯¯¯¯¯¯¯ ║
╚══════════════════════════════════════════════════════════════════════════════╝
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
*/
/// An iterator over the [`Token`]s of a [`Pointer`](crate::Pointer).
#[derive(Debug)]
pub struct Tokens<'a> {
inner: Split<'a, char>,
}
impl<'a> Iterator for Tokens<'a> {
type Item = Token<'a>;
fn next(&mut self) -> Option<Self::Item> {
self.inner
.next()
// SAFETY: source pointer is encoded
.map(|s| unsafe { Token::from_encoded_unchecked(s) })
}
}
impl<'t> Tokens<'t> {
pub(crate) fn new(inner: Split<'t, char>) -> Self {
Self { inner }
}
}
/*
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
╔══════════════════════════════════════════════════════════════════════════════╗
║ ║
║ InvalidEncodingError ║
║ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ║
╚══════════════════════════════════════════════════════════════════════════════╝
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
*/
#[deprecated(since = "0.7.0", note = "renamed to `EncodingError`")]
/// Deprecated alias for [`EncodingError`].
pub type InvalidEncodingError = EncodingError;
/*
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
╔══════════════════════════════════════════════════════════════════════════════╗
║ ║
║ EncodingError ║
║ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ║
╚══════════════════════════════════════════════════════════════════════════════╝
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
*/
/// A token within a json pointer contained invalid encoding (`~` not followed
/// by `0` or `1`).
///
#[derive(Debug, PartialEq, Eq)]
pub struct EncodingError {
/// offset of the erroneous `~` from within the `Token`
pub offset: usize,
/// the specific encoding error
pub source: InvalidEncoding,
}
#[cfg(feature = "std")]
impl std::error::Error for EncodingError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.source)
}
}
impl fmt::Display for EncodingError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"token contains invalid encoding at offset {}",
self.offset
)
}
}
/*
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
╔══════════════════════════════════════════════════════════════════════════════╗
║ ║
║ InvalidEncoding ║
║ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ║
╚══════════════════════════════════════════════════════════════════════════════╝
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
*/
/// Represents the specific type of invalid encoding error.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum InvalidEncoding {
/// `~` not followed by `0` or `1`
Tilde,
/// non-encoded `/` found in token
Slash,
}
impl fmt::Display for InvalidEncoding {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
InvalidEncoding::Tilde => write!(f, "tilde (~) not followed by 0 or 1"),
InvalidEncoding::Slash => write!(f, "slash (/) found in token"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for InvalidEncoding {}
/*
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
╔══════════════════════════════════════════════════════════════════════════════╗
║ ║
║ Tests ║
║ ¯¯¯¯¯¯¯ ║
╚══════════════════════════════════════════════════════════════════════════════╝
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
*/
#[cfg(test)]
mod tests {
use crate::Pointer;
use super::*;
use quickcheck_macros::quickcheck;
#[test]
fn from() {
assert_eq!(Token::from("/").encoded(), "~1");
assert_eq!(Token::from("~/").encoded(), "~0~1");
assert_eq!(Token::from(34u32).encoded(), "34");
assert_eq!(Token::from(34u64).encoded(), "34");
assert_eq!(Token::from(String::from("foo")).encoded(), "foo");
assert_eq!(Token::from(&Token::new("foo")).encoded(), "foo");
}
#[test]
fn to_index() {
assert_eq!(Token::new("-").to_index(), Ok(Index::Next));
assert_eq!(Token::new("0").to_index(), Ok(Index::Num(0)));
assert_eq!(Token::new("2").to_index(), Ok(Index::Num(2)));
assert!(Token::new("a").to_index().is_err());
assert!(Token::new("-1").to_index().is_err());
}
#[test]
fn new() {
assert_eq!(Token::new("~1").encoded(), "~01");
assert_eq!(Token::new("a/b").encoded(), "a~1b");
}
#[test]
fn from_encoded() {
assert_eq!(Token::from_encoded("~1").unwrap().encoded(), "~1");
assert_eq!(Token::from_encoded("~0~1").unwrap().encoded(), "~0~1");
let t = Token::from_encoded("a~1b").unwrap();
assert_eq!(t.decoded(), "a/b");
assert!(Token::from_encoded("a/b").is_err());
assert!(Token::from_encoded("a~a").is_err());
}
#[test]
fn into_owned() {
let token = Token::from_encoded("foo~0").unwrap().into_owned();
assert_eq!(token.encoded(), "foo~0");
}
#[quickcheck]
fn encode_decode(s: String) -> bool {
let token = Token::new(s);
let decoded = Token::from_encoded(token.encoded()).unwrap();
token == decoded
}
#[test]
fn tokens() {
let pointer = Pointer::from_static("/a/b/c");
let tokens: Vec<Token> = pointer.tokens().collect();
assert_eq!(tokens, unsafe {
vec![
Token::from_encoded_unchecked("a"),
Token::from_encoded_unchecked("b"),
Token::from_encoded_unchecked("c"),
]
});
}
#[test]
fn is_next() {
let token = Token::new("-");
assert!(token.is_next());
let token = Token::new("0");
assert!(!token.is_next());
let token = Token::new("a");
assert!(!token.is_next());
let token = Token::new("");
assert!(!token.is_next());
}
}