Skip to content

Commit

Permalink
Adds TOML support, refactors tests, adds Deserialize to Pointer (#48
Browse files Browse the repository at this point in the history
)

* adds TOML support
* implements `serde::Deserialize` for `Pointer`
* refactors tests
* docs, comments and minor cleanup
* removes `Pointer::must_parse`, `PointerBuf::must_parse`
* re-exports ops traits at root
  • Loading branch information
chanced authored Jun 30, 2024
1 parent acd4503 commit 1f9853e
Show file tree
Hide file tree
Showing 12 changed files with 1,871 additions and 603 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ This is a breaking release including:

### Changed

- JSON Pointers with leading `"#"` are no longer accepted. Previously, the erroneous leading hashtag was allowed during parsing but discarded.
- `Assign`, `Resolve`, `ResolveMut`, `Delete` all now use associated types `Value` and `Error`, allowing for more impls other than JSON
- Debug implementation now preserves type information (e.g. prints `PathBuf("/foo/bar")` instead of `"/foo/bar"`) - `Display` remains the same
- Original `Pointer` type renamed to `PointerBuf`
Expand Down
12 changes: 9 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ quickcheck = "1.0.3"
quickcheck_macros = "1.0.0"

[features]
default = ["std", "json"]
json = ["dep:serde_json"]
std = ["serde/std", "serde_json?/std"]
assign = []
default = ["std", "serde", "json", "toml", "resolve", "assign", "delete"]
delete = []
impl = []
json = ["dep:serde_json", "serde"]
resolve = []
serde = []
std = ["serde/std", "serde_json/std"]
toml = ["dep:toml", "serde", "std"]
2 changes: 1 addition & 1 deletion src/arbitrary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ impl Arbitrary for PointerBuf {
}

fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
let tokens: Vec<_> = self.tokens().map(|t| t.into_owned()).collect();
let tokens: Vec<_> = self.tokens().map(Token::into_owned).collect();
Box::new((0..self.count()).map(move |i| {
let subset: Vec<_> = tokens
.iter()
Expand Down
775 changes: 617 additions & 158 deletions src/assign.rs

Large diffs are not rendered by default.

390 changes: 300 additions & 90 deletions src/delete.rs

Large diffs are not rendered by default.

22 changes: 14 additions & 8 deletions src/index.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
//! 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 access an array is an error, the token
//! can be useful when paired with [RFC 6902](https://datatracker.ietf.org/doc/
//! html/rfc6902) as a way to express where to put the new element when extending
//! an array.
//! 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 access an array is an error,
//! the token can be useful when paired with [RFC
//! 6902](https://datatracker.ietf.org/∑doc/html/rfc6902) as 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.
//! 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:
Expand Down Expand Up @@ -70,6 +70,8 @@ impl Index {
/// 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),
Expand Down Expand Up @@ -100,6 +102,9 @@ impl Index {
/// 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),
Expand All @@ -122,6 +127,7 @@ impl Index {
/// assert_eq!(Index::Num(42).for_len_unchecked(30), 42);
/// assert_eq!(Index::Next.for_len_unchecked(30), 30);
/// ````
#[must_use]
pub fn for_len_unchecked(&self, length: usize) -> usize {
match *self {
Self::Num(idx) => idx,
Expand Down
89 changes: 85 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,32 @@
#![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
)]
extern crate alloc;

use core::{fmt, num::ParseIntError};

pub mod prelude;

#[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};

mod tokens;
pub use tokens::*;
Expand All @@ -29,6 +44,16 @@ pub use index::Index;
#[cfg(test)]
mod arbitrary;

/*
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
╔══════════════════════════════════════════════════════════════════════════════╗
║ ║
║ ParseError ║
║ ¯¯¯¯¯¯¯¯¯¯¯¯ ║
╚══════════════════════════════════════════════════════════════════════════════╝
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
*/

/// Indicates that a `Pointer` was malformed and unable to be parsed.
#[derive(Debug, PartialEq)]
pub enum ParseError {
Expand Down Expand Up @@ -63,12 +88,14 @@ impl fmt::Display for ParseError {
impl ParseError {
/// Returns `true` if this error is `NoLeadingBackslash`; otherwise returns
/// `false`.
#[must_use]
pub fn is_no_leading_backslash(&self) -> bool {
matches!(self, Self::NoLeadingBackslash { .. })
}

/// Returns `true` if this error is `InvalidEncoding`; otherwise returns
/// `false`.
#[must_use]
pub fn is_invalid_encoding(&self) -> bool {
matches!(self, Self::InvalidEncoding { .. })
}
Expand All @@ -85,6 +112,7 @@ impl ParseError {
/// let err = PointerBuf::parse("/foo/invalid~tilde/invalid").unwrap_err();
/// assert_eq!(err.pointer_offset(), 4)
/// ```
#[must_use]
pub fn pointer_offset(&self) -> usize {
match *self {
Self::NoLeadingBackslash { .. } => 0,
Expand All @@ -93,7 +121,7 @@ impl ParseError {
}

/// Offset of the character index from within the first token of
/// [Self::pointer_offset])
/// [`Self::pointer_offset`])
/// ```text
/// "/foo/invalid~tilde/invalid"
/// ↑
Expand All @@ -104,6 +132,7 @@ impl ParseError {
/// let err = PointerBuf::parse("/foo/invalid~tilde/invalid").unwrap_err();
/// assert_eq!(err.source_offset(), 8)
/// ```
#[must_use]
pub fn source_offset(&self) -> usize {
match self {
Self::NoLeadingBackslash { .. } => 0,
Expand All @@ -122,6 +151,7 @@ impl ParseError {
/// let err = PointerBuf::parse("/foo/invalid~tilde/invalid").unwrap_err();
/// assert_eq!(err.pointer_offset(), 4)
/// ```
#[must_use]
pub fn complete_offset(&self) -> usize {
self.source_offset() + self.pointer_offset()
}
Expand All @@ -132,11 +162,21 @@ impl std::error::Error for ParseError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::InvalidEncoding { source, .. } => Some(source),
_ => None,
Self::NoLeadingBackslash => None,
}
}
}

/*
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
╔══════════════════════════════════════════════════════════════════════════════╗
║ ║
║ ParseIndexError ║
║ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ║
╚══════════════════════════════════════════════════════════════════════════════╝
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
*/

/// Indicates that the `Token` could not be parsed as valid RFC 6901 index.
#[derive(Debug, PartialEq, Eq)]
pub struct ParseIndexError {
Expand All @@ -163,6 +203,16 @@ impl std::error::Error for ParseIndexError {
}
}

/*
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
╔══════════════════════════════════════════════════════════════════════════════╗
║ ║
║ InvalidEncodingError ║
║ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ║
╚══════════════════════════════════════════════════════════════════════════════╝
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
*/

/// A token within a json pointer contained invalid encoding (`~` not followed
/// by `0` or `1`).
///
Expand All @@ -174,6 +224,7 @@ pub struct InvalidEncodingError {

impl InvalidEncodingError {
/// The byte offset of the first invalid `~`.
#[must_use]
pub fn offset(&self) -> usize {
self.offset
}
Expand Down Expand Up @@ -210,6 +261,16 @@ impl std::error::Error for IndexError {
}
}

/*
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
╔══════════════════════════════════════════════════════════════════════════════╗
║ ║
║ OutOfBoundsError ║
║ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ║
╚══════════════════════════════════════════════════════════════════════════════╝
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
*/

/// Indicates that an `Index` is not within the given bounds.
#[derive(Debug, PartialEq, Eq)]
pub struct OutOfBoundsError {
Expand Down Expand Up @@ -239,10 +300,20 @@ impl fmt::Display for OutOfBoundsError {
#[cfg(feature = "std")]
impl std::error::Error for OutOfBoundsError {}

/// NotFoundError indicates that a Pointer's path was not found in the data.
/*
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
╔══════════════════════════════════════════════════════════════════════════════╗
║ ║
║ NotFoundError ║
║ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ║
╚══════════════════════════════════════════════════════════════════════════════╝
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
*/

/// An error that indicates a [`Pointer`]'s path was not found in the data.
#[derive(Debug, PartialEq, Eq)]
pub struct NotFoundError {
/// The starting offset of the `Token` within the `Pointer` which could not
/// The starting offset of the [`Token`] within the [`Pointer`] which could not
/// be resolved.
pub offset: usize,
}
Expand All @@ -256,6 +327,16 @@ impl fmt::Display for NotFoundError {
#[cfg(feature = "std")]
impl std::error::Error for NotFoundError {}

/*
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
╔══════════════════════════════════════════════════════════════════════════════╗
║ ║
║ ReplaceTokenError ║
║ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ║
╚══════════════════════════════════════════════════════════════════════════════╝
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
*/

/// Returned from `Pointer::replace_token` when the provided index is out of
/// bounds.
#[derive(Debug, PartialEq, Eq)]
Expand Down
Loading

0 comments on commit 1f9853e

Please sign in to comment.