use crate::{token::InvalidEncodingError, Components, Token, Tokens};
use alloc::{
borrow::ToOwned,
fmt,
string::{String, ToString},
vec::Vec,
};
use core::{borrow::Borrow, cmp::Ordering, ops::Deref, str::FromStr};
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(not(doc), repr(transparent))]
pub struct Pointer(str);
impl Default for &'static Pointer {
fn default() -> Self {
Pointer::root()
}
}
impl core::fmt::Display for Pointer {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.0.fmt(f)
}
}
impl Pointer {
fn new<S: AsRef<str> + ?Sized>(s: &S) -> &Self {
unsafe { &*(core::ptr::from_ref::<str>(s.as_ref()) as *const Self) }
}
pub const fn root() -> &'static Self {
#[allow(clippy::ref_as_ptr)]
unsafe {
&*("" as *const str as *const Self)
}
}
pub fn parse<S: AsRef<str> + ?Sized>(s: &S) -> Result<&Self, ParseError> {
validate(s.as_ref()).map(Self::new)
}
pub const fn from_static(s: &'static str) -> &'static Self {
assert!(validate(s).is_ok(), "invalid json pointer");
unsafe { &*(core::ptr::from_ref::<str>(s) as *const Self) }
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn to_buf(&self) -> PointerBuf {
PointerBuf(self.0.to_string())
}
pub fn tokens(&self) -> Tokens {
let mut s = self.0.split('/');
s.next();
Tokens::new(s)
}
pub fn count(&self) -> usize {
self.tokens().count()
}
pub fn is_root(&self) -> bool {
self.0.is_empty()
}
#[cfg(feature = "json")]
pub fn to_json_value(&self) -> serde_json::Value {
serde_json::Value::String(self.0.to_string())
}
pub fn back(&self) -> Option<Token> {
self.0
.rsplit_once('/')
.map(|(_, back)| Token::from_encoded_unchecked(back))
}
pub fn last(&self) -> Option<Token> {
self.back()
}
pub fn front(&self) -> Option<Token> {
if self.is_root() {
return None;
}
self.0[1..]
.split_once('/')
.map_or_else(
|| Token::from_encoded_unchecked(&self.0[1..]),
|(front, _)| Token::from_encoded_unchecked(front),
)
.into()
}
pub fn first(&self) -> Option<Token> {
self.front()
}
pub fn split_front(&self) -> Option<(Token, &Self)> {
if self.is_root() {
return None;
}
self.0[1..]
.find('/')
.map_or_else(
|| (Token::from_encoded_unchecked(&self.0[1..]), Self::root()),
|idx| {
let (front, back) = self.0[1..].split_at(idx);
(Token::from_encoded_unchecked(front), Self::new(back))
},
)
.into()
}
pub fn split_at(&self, offset: usize) -> Option<(&Self, &Self)> {
if self.0.as_bytes().get(offset).copied() != Some(b'/') {
return None;
}
let (head, tail) = self.0.split_at(offset);
Some((Self::new(head), Self::new(tail)))
}
pub fn split_back(&self) -> Option<(&Self, Token)> {
self.0
.rsplit_once('/')
.map(|(front, back)| (Self::new(front), Token::from_encoded_unchecked(back)))
}
pub fn parent(&self) -> Option<&Self> {
self.0.rsplit_once('/').map(|(front, _)| Self::new(front))
}
pub fn strip_suffix<'a>(&'a self, suffix: &Self) -> Option<&'a Self> {
self.0.strip_suffix(&suffix.0).map(Self::new)
}
pub fn strip_prefix<'a>(&'a self, prefix: &Self) -> Option<&'a Self> {
self.0.strip_prefix(&prefix.0).map(Self::new)
}
pub fn get(&self, index: usize) -> Option<Token> {
self.tokens().nth(index).clone()
}
#[cfg(feature = "resolve")]
pub fn resolve<'v, R: crate::Resolve>(&self, value: &'v R) -> Result<&'v R::Value, R::Error> {
value.resolve(self)
}
#[cfg(feature = "resolve")]
pub fn resolve_mut<'v, R: crate::ResolveMut>(
&self,
value: &'v mut R,
) -> Result<&'v mut R::Value, R::Error> {
value.resolve_mut(self)
}
pub fn intersection<'a>(&'a self, other: &Self) -> &'a Self {
if self.is_root() || other.is_root() {
return Self::root();
}
let mut idx = 0;
for (a, b) in self.tokens().zip(other.tokens()) {
if a != b {
break;
}
idx += a.encoded().len() + 1;
}
self.split_at(idx).map_or(self, |(head, _)| head)
}
#[cfg(feature = "delete")]
pub fn delete<D: crate::Delete>(&self, value: &mut D) -> Option<D::Value> {
value.delete(self)
}
#[cfg(feature = "assign")]
pub fn assign<D, V>(&self, dest: &mut D, src: V) -> Result<Option<D::Value>, D::Error>
where
D: crate::Assign,
V: Into<D::Value>,
{
dest.assign(self, src)
}
pub fn components(&self) -> Components {
self.into()
}
pub fn with_trailing_token<'t>(&self, token: impl Into<Token<'t>>) -> PointerBuf {
let mut buf = self.to_buf();
buf.push_back(token.into());
buf
}
pub fn with_leading_token<'t>(&self, token: impl Into<Token<'t>>) -> PointerBuf {
let mut buf = self.to_buf();
buf.push_front(token);
buf
}
pub fn concat(&self, other: &Pointer) -> PointerBuf {
let mut buf = self.to_buf();
buf.append(other);
buf
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Pointer {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
<str>::serialize(&self.0, serializer)
}
}
#[cfg(feature = "serde")]
impl<'de: 'p, 'p> serde::Deserialize<'de> for &'p Pointer {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::{Error, Visitor};
struct PointerVisitor;
impl<'a> Visitor<'a> for PointerVisitor {
type Value = &'a Pointer;
fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
formatter.write_str("a borrowed Pointer")
}
fn visit_borrowed_str<E>(self, v: &'a str) -> Result<Self::Value, E>
where
E: Error,
{
Pointer::parse(v).map_err(|err| {
Error::custom(format!("failed to parse json pointer\n\ncaused by:\n{err}"))
})
}
}
deserializer.deserialize_str(PointerVisitor)
}
}
impl ToOwned for Pointer {
type Owned = PointerBuf;
fn to_owned(&self) -> Self::Owned {
self.to_buf()
}
}
impl PartialEq<&str> for Pointer {
fn eq(&self, other: &&str) -> bool {
&&self.0 == other
}
}
impl<'p> PartialEq<String> for &'p Pointer {
fn eq(&self, other: &String) -> bool {
self.0.eq(other)
}
}
impl PartialEq<str> for Pointer {
fn eq(&self, other: &str) -> bool {
&self.0 == other
}
}
impl PartialEq<Pointer> for &str {
fn eq(&self, other: &Pointer) -> bool {
*self == (&other.0)
}
}
impl PartialEq<Pointer> for String {
fn eq(&self, other: &Pointer) -> bool {
self == &other.0
}
}
impl PartialEq<Pointer> for str {
fn eq(&self, other: &Pointer) -> bool {
self == &other.0
}
}
impl PartialEq<String> for Pointer {
fn eq(&self, other: &String) -> bool {
&self.0 == other
}
}
impl PartialEq<PointerBuf> for Pointer {
fn eq(&self, other: &PointerBuf) -> bool {
self.0 == other.0
}
}
impl PartialEq<Pointer> for PointerBuf {
fn eq(&self, other: &Pointer) -> bool {
self.0 == other.0
}
}
impl PartialEq<PointerBuf> for String {
fn eq(&self, other: &PointerBuf) -> bool {
self == &other.0
}
}
impl PartialEq<String> for PointerBuf {
fn eq(&self, other: &String) -> bool {
&self.0 == other
}
}
impl PartialEq<PointerBuf> for str {
fn eq(&self, other: &PointerBuf) -> bool {
self == other.0
}
}
impl PartialEq<PointerBuf> for &str {
fn eq(&self, other: &PointerBuf) -> bool {
*self == other.0
}
}
impl AsRef<Pointer> for Pointer {
fn as_ref(&self) -> &Pointer {
self
}
}
impl AsRef<Pointer> for PointerBuf {
fn as_ref(&self) -> &Pointer {
self
}
}
impl PartialEq<PointerBuf> for &Pointer {
fn eq(&self, other: &PointerBuf) -> bool {
self.0 == other.0
}
}
impl PartialEq<&Pointer> for PointerBuf {
fn eq(&self, other: &&Pointer) -> bool {
self.0 == other.0
}
}
#[cfg(feature = "json")]
impl From<&Pointer> for serde_json::Value {
fn from(ptr: &Pointer) -> Self {
ptr.to_json_value()
}
}
impl AsRef<str> for Pointer {
fn as_ref(&self) -> &str {
&self.0
}
}
impl Borrow<str> for Pointer {
fn borrow(&self) -> &str {
&self.0
}
}
impl AsRef<[u8]> for Pointer {
fn as_ref(&self) -> &[u8] {
self.0.as_bytes()
}
}
impl PartialOrd<PointerBuf> for Pointer {
fn partial_cmp(&self, other: &PointerBuf) -> Option<Ordering> {
self.0.partial_cmp(other.0.as_str())
}
}
impl PartialOrd<Pointer> for PointerBuf {
fn partial_cmp(&self, other: &Pointer) -> Option<Ordering> {
self.0.as_str().partial_cmp(&other.0)
}
}
impl PartialOrd<&Pointer> for PointerBuf {
fn partial_cmp(&self, other: &&Pointer) -> Option<Ordering> {
self.0.as_str().partial_cmp(&other.0)
}
}
impl PartialOrd<Pointer> for String {
fn partial_cmp(&self, other: &Pointer) -> Option<Ordering> {
self.as_str().partial_cmp(&other.0)
}
}
impl PartialOrd<String> for &Pointer {
fn partial_cmp(&self, other: &String) -> Option<Ordering> {
self.0.partial_cmp(other.as_str())
}
}
impl PartialOrd<PointerBuf> for String {
fn partial_cmp(&self, other: &PointerBuf) -> Option<Ordering> {
self.as_str().partial_cmp(other.0.as_str())
}
}
impl PartialOrd<Pointer> for str {
fn partial_cmp(&self, other: &Pointer) -> Option<Ordering> {
self.partial_cmp(&other.0)
}
}
impl PartialOrd<PointerBuf> for str {
fn partial_cmp(&self, other: &PointerBuf) -> Option<Ordering> {
self.partial_cmp(other.0.as_str())
}
}
impl PartialOrd<PointerBuf> for &str {
fn partial_cmp(&self, other: &PointerBuf) -> Option<Ordering> {
(*self).partial_cmp(other.0.as_str())
}
}
impl PartialOrd<Pointer> for &str {
fn partial_cmp(&self, other: &Pointer) -> Option<Ordering> {
(*self).partial_cmp(&other.0)
}
}
impl PartialOrd<&str> for &Pointer {
fn partial_cmp(&self, other: &&str) -> Option<Ordering> {
PartialOrd::partial_cmp(&self.0[..], &other[..])
}
}
impl PartialOrd<String> for Pointer {
fn partial_cmp(&self, other: &String) -> Option<Ordering> {
self.0.partial_cmp(other.as_str())
}
}
impl PartialOrd<&str> for PointerBuf {
fn partial_cmp(&self, other: &&str) -> Option<Ordering> {
PartialOrd::partial_cmp(&self.0[..], &other[..])
}
}
impl<'p> PartialOrd<PointerBuf> for &'p Pointer {
fn partial_cmp(&self, other: &PointerBuf) -> Option<Ordering> {
self.0.partial_cmp(other.0.as_str())
}
}
impl PartialOrd<String> for PointerBuf {
fn partial_cmp(&self, other: &String) -> Option<Ordering> {
self.0.partial_cmp(other)
}
}
impl<'a> IntoIterator for &'a Pointer {
type Item = Token<'a>;
type IntoIter = Tokens<'a>;
fn into_iter(self) -> Self::IntoIter {
self.tokens()
}
}
#[derive(Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PointerBuf(String);
impl PointerBuf {
pub fn new() -> Self {
Self(String::new())
}
pub fn parse<S: AsRef<str> + ?Sized>(s: &S) -> Result<Self, ParseError> {
Pointer::parse(&s).map(Pointer::to_buf)
}
pub fn from_tokens<'a, T>(tokens: impl IntoIterator<Item = T>) -> Self
where
T: Into<Token<'a>>,
{
let mut inner = String::new();
for t in tokens.into_iter().map(Into::into) {
inner.push('/');
inner.push_str(t.encoded());
}
PointerBuf(inner)
}
pub fn as_ptr(&self) -> &Pointer {
self
}
pub fn push_front<'t>(&mut self, token: impl Into<Token<'t>>) {
self.0.insert(0, '/');
self.0.insert_str(1, token.into().encoded());
}
pub fn push_back<'t>(&mut self, token: impl Into<Token<'t>>) {
self.0.push('/');
self.0.push_str(token.into().encoded());
}
pub fn pop_back(&mut self) -> Option<Token<'static>> {
if let Some(idx) = self.0.rfind('/') {
let back = Token::from_encoded_unchecked(self.0.split_off(idx + 1));
self.0.pop(); Some(back)
} else {
None
}
}
pub fn pop_front(&mut self) -> Option<Token<'static>> {
(!self.is_root()).then(|| {
let mut token = if let Some(idx) = self.0[1..].find('/') {
let token = self.0.split_off(idx + 1);
core::mem::replace(&mut self.0, token)
} else {
core::mem::take(&mut self.0)
};
token.remove(0); Token::from_encoded_unchecked(token)
})
}
pub fn append<P: AsRef<Pointer>>(&mut self, other: P) -> &PointerBuf {
let other = other.as_ref();
if self.is_root() {
self.0 = other.0.to_string();
} else if !other.is_root() {
self.0.push_str(&other.0);
}
self
}
pub fn replace<'t>(
&mut self,
index: usize,
token: impl Into<Token<'t>>,
) -> Result<Option<Token>, ReplaceError> {
if self.is_root() {
return Err(ReplaceError {
count: self.count(),
index,
});
}
let mut tokens = self.tokens().collect::<Vec<_>>();
if index >= tokens.len() {
return Err(ReplaceError {
count: tokens.len(),
index,
});
}
let old = tokens.get(index).map(super::token::Token::to_owned);
tokens[index] = token.into();
let mut buf = String::new();
for token in tokens {
buf.push('/');
buf.push_str(token.encoded());
}
self.0 = buf;
Ok(old)
}
pub fn clear(&mut self) {
self.0.clear();
}
}
impl FromStr for PointerBuf {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from(s)
}
}
impl Borrow<Pointer> for PointerBuf {
fn borrow(&self) -> &Pointer {
self.as_ptr()
}
}
impl Deref for PointerBuf {
type Target = Pointer;
fn deref(&self) -> &Self::Target {
Pointer::new(&self.0)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for PointerBuf {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;
let s = String::deserialize(deserializer)?;
PointerBuf::try_from(s).map_err(D::Error::custom)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for PointerBuf {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
String::serialize(&self.0, serializer)
}
}
impl From<Token<'_>> for PointerBuf {
fn from(t: Token) -> Self {
PointerBuf::from_tokens([t])
}
}
impl TryFrom<String> for PointerBuf {
type Error = ParseError;
fn try_from(value: String) -> Result<Self, Self::Error> {
let _ = validate(&value)?;
Ok(Self(value))
}
}
impl From<usize> for PointerBuf {
fn from(value: usize) -> Self {
PointerBuf::from_tokens([value])
}
}
impl<'a> IntoIterator for &'a PointerBuf {
type Item = Token<'a>;
type IntoIter = Tokens<'a>;
fn into_iter(self) -> Self::IntoIter {
self.tokens()
}
}
impl TryFrom<&str> for PointerBuf {
type Error = ParseError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Pointer::parse(value).map(Pointer::to_buf)
}
}
impl PartialEq<&str> for PointerBuf {
fn eq(&self, other: &&str) -> bool {
&self.0 == other
}
}
impl PartialEq<str> for PointerBuf {
fn eq(&self, other: &str) -> bool {
self.0 == other
}
}
impl core::fmt::Display for PointerBuf {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.0.fmt(f)
}
}
const fn validate(value: &str) -> Result<&str, ParseError> {
if value.is_empty() {
return Ok(value);
}
let bytes = value.as_bytes();
if bytes[0] != b'/' {
return Err(ParseError::NoLeadingBackslash);
}
let mut ptr_offset = 0; let mut tok_offset = 0;
let bytes = value.as_bytes();
let mut i = 0;
while i < bytes.len() {
match bytes[i] {
b'/' => {
ptr_offset = i;
tok_offset = 0;
}
b'~' => {
if i + 1 >= bytes.len() || (bytes[i + 1] != b'0' && bytes[i + 1] != b'1') {
return Err(ParseError::InvalidEncoding {
offset: ptr_offset,
source: InvalidEncodingError { offset: tok_offset },
});
}
i += 1;
tok_offset += 1;
}
_ => {}
}
i += 1;
tok_offset += 1;
}
Ok(value)
}
#[derive(Debug, PartialEq)]
pub enum ParseError {
NoLeadingBackslash,
InvalidEncoding {
offset: usize,
source: InvalidEncodingError,
},
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NoLeadingBackslash { .. } => {
write!(
f,
"json pointer is malformed as it does not start with a backslash ('/')"
)
}
Self::InvalidEncoding { source, .. } => write!(f, "{source}"),
}
}
}
impl ParseError {
pub fn is_no_leading_backslash(&self) -> bool {
matches!(self, Self::NoLeadingBackslash { .. })
}
pub fn is_invalid_encoding(&self) -> bool {
matches!(self, Self::InvalidEncoding { .. })
}
pub fn pointer_offset(&self) -> usize {
match *self {
Self::NoLeadingBackslash { .. } => 0,
Self::InvalidEncoding { offset, .. } => offset,
}
}
pub fn source_offset(&self) -> usize {
match self {
Self::NoLeadingBackslash { .. } => 0,
Self::InvalidEncoding { source, .. } => source.offset,
}
}
pub fn complete_offset(&self) -> usize {
self.source_offset() + self.pointer_offset()
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::InvalidEncoding { source, .. } => Some(source),
Self::NoLeadingBackslash => None,
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct ReplaceError {
pub index: usize,
pub count: usize,
}
impl fmt::Display for ReplaceError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "index {} is out of bounds ({})", self.index, self.count)
}
}
#[cfg(feature = "std")]
impl std::error::Error for ReplaceError {}
#[cfg(test)]
mod tests {
use std::error::Error;
use super::*;
use quickcheck::TestResult;
use quickcheck_macros::quickcheck;
#[test]
#[should_panic = "invalid json pointer"]
fn from_const_validates() {
let _ = Pointer::from_static("foo/bar");
}
#[test]
fn strip_suffix() {
let p = Pointer::new("/example/pointer/to/some/value");
let stripped = p.strip_suffix(Pointer::new("/to/some/value")).unwrap();
assert_eq!(stripped, "/example/pointer");
}
#[test]
fn strip_prefix() {
let p = Pointer::new("/example/pointer/to/some/value");
let stripped = p.strip_prefix(Pointer::new("/example/pointer")).unwrap();
assert_eq!(stripped, "/to/some/value");
}
#[test]
fn parse_error_is_no_leading_backslash() {
let err = ParseError::NoLeadingBackslash;
assert!(err.is_no_leading_backslash());
assert!(!err.is_invalid_encoding());
}
#[test]
fn parse_error_is_invalid_encoding() {
let err = ParseError::InvalidEncoding {
offset: 0,
source: InvalidEncodingError { offset: 1 },
};
assert!(!err.is_no_leading_backslash());
assert!(err.is_invalid_encoding());
}
#[test]
fn parse() {
let tests = [
("", Ok("")),
("/", Ok("/")),
("/foo", Ok("/foo")),
("/foo/bar", Ok("/foo/bar")),
("/foo/bar/baz", Ok("/foo/bar/baz")),
("/foo/bar/baz/~0", Ok("/foo/bar/baz/~0")),
("/foo/bar/baz/~1", Ok("/foo/bar/baz/~1")),
("/foo/bar/baz/~01", Ok("/foo/bar/baz/~01")),
("/foo/bar/baz/~10", Ok("/foo/bar/baz/~10")),
("/foo/bar/baz/~11", Ok("/foo/bar/baz/~11")),
("/foo/bar/baz/~1/~0", Ok("/foo/bar/baz/~1/~0")),
("missing-slash", Err(ParseError::NoLeadingBackslash)),
(
"/~",
Err(ParseError::InvalidEncoding {
offset: 0,
source: InvalidEncodingError { offset: 1 },
}),
),
(
"/~2",
Err(ParseError::InvalidEncoding {
offset: 0,
source: InvalidEncodingError { offset: 1 },
}),
),
(
"/~a",
Err(ParseError::InvalidEncoding {
offset: 0,
source: InvalidEncodingError { offset: 1 },
}),
),
];
for (input, expected) in tests {
let actual = Pointer::parse(input).map(Pointer::as_str);
assert_eq!(actual, expected);
}
}
#[test]
fn parse_error_offsets() {
let err = Pointer::parse("/foo/invalid~encoding").unwrap_err();
assert_eq!(err.pointer_offset(), 4);
assert_eq!(err.source_offset(), 8);
assert_eq!(err.complete_offset(), 12);
let err = Pointer::parse("invalid~encoding").unwrap_err();
assert_eq!(err.pointer_offset(), 0);
assert_eq!(err.source_offset(), 0);
let err = Pointer::parse("no-leading/slash").unwrap_err();
assert!(err.source().is_none());
}
#[test]
#[cfg(feature = "std")]
fn parse_error_source() {
use std::error::Error;
let err = Pointer::parse("/foo/invalid~encoding").unwrap_err();
assert!(err.source().is_some());
let source = err.source().unwrap();
assert!(source.is::<InvalidEncodingError>());
let err = Pointer::parse("no-leading/slash").unwrap_err();
assert!(err.source().is_none());
}
#[test]
fn pointerbuf_as_pointer_returns_pointer() {
let ptr = PointerBuf::parse("/foo/bar").unwrap();
assert_eq!(ptr.as_ptr(), ptr);
}
#[test]
fn pointer_buf_clear() {
let mut ptr = PointerBuf::from_tokens(["foo", "bar"]);
ptr.clear();
assert_eq!(ptr, "");
}
#[test]
fn push_pop_back() {
let mut ptr = PointerBuf::default();
assert_eq!(ptr, "", "default, root pointer should equal \"\"");
assert_eq!(ptr.count(), 0, "default pointer should have 0 tokens");
ptr.push_back("foo");
assert_eq!(ptr, "/foo", "pointer should equal \"/foo\" after push_back");
ptr.push_back("bar");
assert_eq!(ptr, "/foo/bar");
ptr.push_back("/baz");
assert_eq!(ptr, "/foo/bar/~1baz");
let mut ptr = PointerBuf::from_tokens(["foo", "bar"]);
assert_eq!(ptr.pop_back(), Some("bar".into()));
assert_eq!(ptr, "/foo", "pointer should equal \"/foo\" after pop_back");
assert_eq!(ptr.pop_back(), Some("foo".into()));
assert_eq!(ptr, "", "pointer should equal \"\" after pop_back");
}
#[test]
fn replace_token() {
let mut ptr = PointerBuf::try_from("/test/token").unwrap();
let res = ptr.replace(0, "new");
assert!(res.is_ok());
assert_eq!(ptr, "/new/token");
let res = ptr.replace(3, "invalid");
assert!(res.is_err());
}
#[test]
fn push_pop_front() {
let mut ptr = PointerBuf::default();
assert_eq!(ptr, "");
assert_eq!(ptr.count(), 0);
ptr.push_front("bar");
assert_eq!(ptr, "/bar");
assert_eq!(ptr.count(), 1);
ptr.push_front("foo");
assert_eq!(ptr, "/foo/bar");
assert_eq!(ptr.count(), 2);
ptr.push_front("too");
assert_eq!(ptr, "/too/foo/bar");
assert_eq!(ptr.count(), 3);
assert_eq!(ptr.pop_front(), Some("too".into()));
assert_eq!(ptr, "/foo/bar");
assert_eq!(ptr.count(), 2);
assert_eq!(ptr.pop_back(), Some("bar".into()));
assert_eq!(ptr, "/foo");
assert_eq!(ptr.count(), 1);
assert_eq!(ptr.pop_front(), Some("foo".into()));
assert_eq!(ptr, "");
}
#[test]
fn display_replace_token_error() {
let err = ReplaceError { index: 3, count: 2 };
assert_eq!(format!("{err}"), "index 3 is out of bounds (2)");
}
#[test]
fn pop_front_works_with_empty_strings() {
{
let mut ptr = PointerBuf::from_tokens(["bar", "", ""]);
assert_eq!(ptr.tokens().count(), 3);
let mut token = ptr.pop_front();
assert_eq!(token, Some(Token::from_encoded_unchecked("bar")));
assert_eq!(ptr.tokens().count(), 2);
token = ptr.pop_front();
assert_eq!(token, Some(Token::from_encoded_unchecked("")));
assert_eq!(ptr.tokens().count(), 1);
token = ptr.pop_front();
assert_eq!(token, Some(Token::from_encoded_unchecked("")));
assert_eq!(ptr.tokens().count(), 0);
assert_eq!(ptr, Pointer::root());
}
{
let mut ptr = PointerBuf::new();
assert_eq!(ptr.tokens().count(), 0);
ptr.push_back("");
assert_eq!(ptr.tokens().count(), 1);
ptr.pop_back();
assert_eq!(ptr.tokens().count(), 0);
}
{
let mut ptr = PointerBuf::new();
let input = ["", "", "", "foo", "", "bar", "baz", ""];
for (idx, &s) in input.iter().enumerate() {
assert_eq!(ptr.tokens().count(), idx);
ptr.push_back(s);
}
assert_eq!(ptr.tokens().count(), input.len());
for (idx, s) in input.iter().enumerate() {
assert_eq!(ptr.tokens().count(), 8 - idx);
assert_eq!(ptr.front().unwrap().decoded(), *s);
assert_eq!(ptr.pop_front().unwrap().decoded(), *s);
}
assert_eq!(ptr.tokens().count(), 0);
assert!(ptr.front().is_none());
assert!(ptr.pop_front().is_none());
}
}
#[test]
fn formatting() {
assert_eq!(PointerBuf::from_tokens(["foo", "bar"]), "/foo/bar");
assert_eq!(
PointerBuf::from_tokens(["~/foo", "~bar", "/baz"]),
"/~0~1foo/~0bar/~1baz"
);
assert_eq!(PointerBuf::from_tokens(["field", "", "baz"]), "/field//baz");
assert_eq!(PointerBuf::default(), "");
let ptr = PointerBuf::from_tokens(["foo", "bar", "baz"]);
assert_eq!(ptr.to_string(), "/foo/bar/baz");
}
#[test]
fn last() {
let ptr = Pointer::from_static("/foo/bar");
assert_eq!(ptr.last(), Some("bar".into()));
let ptr = Pointer::from_static("/foo/bar/-");
assert_eq!(ptr.last(), Some("-".into()));
let ptr = Pointer::from_static("/-");
assert_eq!(ptr.last(), Some("-".into()));
let ptr = Pointer::root();
assert_eq!(ptr.last(), None);
let ptr = Pointer::from_static("/bar");
assert_eq!(ptr.last(), Some("bar".into()));
}
#[test]
fn first() {
let ptr = Pointer::from_static("/foo/bar");
assert_eq!(ptr.first(), Some("foo".into()));
let ptr = Pointer::from_static("/foo/bar/-");
assert_eq!(ptr.first(), Some("foo".into()));
let ptr = Pointer::root();
assert_eq!(ptr.first(), None);
}
#[test]
fn pointerbuf_try_from() {
let ptr = PointerBuf::from_tokens(["foo", "bar", "~/"]);
assert_eq!(PointerBuf::try_from("/foo/bar/~0~1").unwrap(), ptr);
let into: PointerBuf = "/foo/bar/~0~1".try_into().unwrap();
assert_eq!(ptr, into);
}
#[test]
fn default() {
let ptr = PointerBuf::default();
assert_eq!(ptr, "");
assert_eq!(ptr.count(), 0);
let ptr = <&Pointer>::default();
assert_eq!(ptr, "");
}
#[test]
#[cfg(all(feature = "serde", feature = "json"))]
fn to_json_value() {
use serde_json::Value;
let ptr = Pointer::from_static("/foo/bar");
assert_eq!(ptr.to_json_value(), Value::String(String::from("/foo/bar")));
}
#[cfg(all(feature = "resolve", feature = "json"))]
#[test]
fn resolve() {
use serde_json::json;
let value = json!({
"foo": {
"bar": {
"baz": "qux"
}
}
});
let ptr = Pointer::from_static("/foo/bar/baz");
let resolved = ptr.resolve(&value).unwrap();
assert_eq!(resolved, &json!("qux"));
}
#[cfg(all(feature = "delete", feature = "json"))]
#[test]
fn delete() {
use serde_json::json;
let mut value = json!({
"foo": {
"bar": {
"baz": "qux"
}
}
});
let ptr = Pointer::from_static("/foo/bar/baz");
let deleted = ptr.delete(&mut value).unwrap();
assert_eq!(deleted, json!("qux"));
assert_eq!(
value,
json!({
"foo": {
"bar": {}
}
})
);
}
#[cfg(all(feature = "assign", feature = "json"))]
#[test]
fn assign() {
use serde_json::json;
let mut value = json!({});
let ptr = Pointer::from_static("/foo/bar");
let replaced = ptr.assign(&mut value, json!("baz")).unwrap();
assert_eq!(replaced, None);
assert_eq!(
value,
json!({
"foo": {
"bar": "baz"
}
})
);
}
#[test]
fn get() {
let ptr = Pointer::from_static("/0/1/2/3/4/5/6/7/8/9");
for i in 0..10 {
assert_eq!(ptr.get(i).unwrap().decoded(), i.to_string());
}
}
#[test]
fn replace_token_success() {
let mut ptr = PointerBuf::from_tokens(["foo", "bar", "baz"]);
assert!(ptr.replace(1, "qux").is_ok());
assert_eq!(ptr, PointerBuf::from_tokens(["foo", "qux", "baz"]));
assert!(ptr.replace(0, "corge").is_ok());
assert_eq!(ptr, PointerBuf::from_tokens(["corge", "qux", "baz"]));
assert!(ptr.replace(2, "quux").is_ok());
assert_eq!(ptr, PointerBuf::from_tokens(["corge", "qux", "quux"]));
}
#[test]
fn replace_token_out_of_bounds() {
let mut ptr = PointerBuf::from_tokens(["foo", "bar"]);
assert!(ptr.replace(2, "baz").is_err());
assert_eq!(ptr, PointerBuf::from_tokens(["foo", "bar"])); }
#[test]
fn replace_token_with_empty_string() {
let mut ptr = PointerBuf::from_tokens(["foo", "bar", "baz"]);
assert!(ptr.replace(1, "").is_ok());
assert_eq!(ptr, PointerBuf::from_tokens(["foo", "", "baz"]));
}
#[test]
fn replace_token_in_empty_pointer() {
let mut ptr = PointerBuf::default();
assert!(ptr.replace(0, "foo").is_err());
assert_eq!(ptr, PointerBuf::default()); }
#[test]
fn pop_back_works_with_empty_strings() {
{
let mut ptr = PointerBuf::new();
ptr.push_back("");
ptr.push_back("");
ptr.push_back("bar");
assert_eq!(ptr.tokens().count(), 3);
ptr.pop_back();
assert_eq!(ptr.tokens().count(), 2);
ptr.pop_back();
assert_eq!(ptr.tokens().count(), 1);
ptr.pop_back();
assert_eq!(ptr.tokens().count(), 0);
assert_eq!(ptr, PointerBuf::new());
}
{
let mut ptr = PointerBuf::new();
assert_eq!(ptr.tokens().count(), 0);
ptr.push_back("");
assert_eq!(ptr.tokens().count(), 1);
ptr.pop_back();
assert_eq!(ptr.tokens().count(), 0);
}
{
let mut ptr = PointerBuf::new();
let input = ["", "", "", "foo", "", "bar", "baz", ""];
for (idx, &s) in input.iter().enumerate() {
assert_eq!(ptr.tokens().count(), idx);
ptr.push_back(s);
}
assert_eq!(ptr.tokens().count(), input.len());
for (idx, s) in input.iter().enumerate().rev() {
assert_eq!(ptr.tokens().count(), idx + 1);
assert_eq!(ptr.back().unwrap().decoded(), *s);
assert_eq!(ptr.pop_back().unwrap().decoded(), *s);
}
assert_eq!(ptr.tokens().count(), 0);
assert!(ptr.back().is_none());
assert!(ptr.pop_back().is_none());
}
}
#[test]
#[allow(clippy::useless_asref)]
fn pointerbuf_as_ref_returns_pointer() {
let ptr_str = "/foo/bar";
let ptr = Pointer::from_static(ptr_str);
let ptr_buf = ptr.to_buf();
assert_eq!(ptr_buf.as_ref(), ptr);
let r: &Pointer = ptr.as_ref();
assert_eq!(ptr, r);
let s: &str = ptr.as_ref();
assert_eq!(s, ptr_str);
let b: &[u8] = ptr.as_ref();
assert_eq!(b, ptr_str.as_bytes());
}
#[test]
fn from_tokens() {
let ptr = PointerBuf::from_tokens(["foo", "bar", "baz"]);
assert_eq!(ptr, "/foo/bar/baz");
}
#[test]
fn pointer_borrow() {
let ptr = Pointer::from_static("/foo/bar");
let borrowed: &str = ptr.borrow();
assert_eq!(borrowed, "/foo/bar");
}
#[test]
#[cfg(feature = "json")]
fn into_value() {
use serde_json::Value;
let ptr = Pointer::from_static("/foo/bar");
let value: Value = ptr.into();
assert_eq!(value, Value::String("/foo/bar".to_string()));
}
#[test]
fn intersect() {
let base = Pointer::from_static("/foo/bar");
let a = Pointer::from_static("/foo/bar/qux");
let b = Pointer::from_static("/foo/bar");
assert_eq!(a.intersection(b), base);
let base = Pointer::from_static("");
let a = Pointer::from_static("/foo");
let b = Pointer::from_static("/");
assert_eq!(a.intersection(b), base);
let base = Pointer::from_static("");
let a = Pointer::from_static("/fooqux");
let b = Pointer::from_static("/foobar");
assert_eq!(a.intersection(b), base);
}
#[quickcheck]
fn qc_pop_and_push(mut ptr: PointerBuf) -> bool {
let original_ptr = ptr.clone();
let mut tokens = Vec::with_capacity(ptr.count());
while let Some(token) = ptr.pop_back() {
tokens.push(token);
}
if ptr.count() != 0 || !ptr.is_root() || ptr.last().is_some() || ptr.first().is_some() {
return false;
}
for token in tokens.drain(..) {
ptr.push_front(token);
}
if ptr != original_ptr {
return false;
}
while let Some(token) = ptr.pop_front() {
tokens.push(token);
}
if ptr.count() != 0 || !ptr.is_root() || ptr.last().is_some() || ptr.first().is_some() {
return false;
}
for token in tokens {
ptr.push_back(token);
}
ptr == original_ptr
}
#[quickcheck]
fn qc_split(ptr: PointerBuf) -> bool {
if let Some((head, tail)) = ptr.split_front() {
{
let Some(first) = ptr.first() else {
return false;
};
if first != head {
return false;
}
}
{
let mut copy = ptr.clone();
copy.pop_front();
if copy != tail {
return false;
}
}
{
let mut buf = tail.to_buf();
buf.push_front(head.clone());
if buf != ptr {
return false;
}
}
{
let fmt = alloc::format!("/{}{tail}", head.encoded());
if Pointer::parse(&fmt).unwrap() != ptr {
return false;
}
}
} else {
return ptr.is_root()
&& ptr.count() == 0
&& ptr.last().is_none()
&& ptr.first().is_none();
}
if let Some((head, tail)) = ptr.split_back() {
{
let Some(last) = ptr.last() else {
return false;
};
if last != tail {
return false;
}
}
{
let mut copy = ptr.clone();
copy.pop_back();
if copy != head {
return false;
}
}
{
let mut buf = head.to_buf();
buf.push_back(tail.clone());
if buf != ptr {
return false;
}
}
{
let fmt = alloc::format!("{head}/{}", tail.encoded());
if Pointer::parse(&fmt).unwrap() != ptr {
return false;
}
}
if Some(head) != ptr.parent() {
return false;
}
} else {
return ptr.is_root()
&& ptr.count() == 0
&& ptr.last().is_none()
&& ptr.first().is_none();
}
true
}
#[quickcheck]
fn qc_from_tokens(tokens: Vec<String>) -> bool {
let buf = PointerBuf::from_tokens(&tokens);
let reconstructed: Vec<_> = buf.tokens().collect();
reconstructed
.into_iter()
.zip(tokens)
.all(|(a, b)| a.decoded() == b)
}
#[quickcheck]
fn qc_intersection(base: PointerBuf, suffix_0: PointerBuf, suffix_1: PointerBuf) -> TestResult {
if suffix_0.first() == suffix_1.first() {
return TestResult::discard();
}
let mut a = base.clone();
a.append(&suffix_0);
let mut b = base.clone();
b.append(&suffix_1);
let isect = a.intersection(&b);
TestResult::from_bool(isect == base)
}
#[cfg(all(feature = "json", feature = "std", feature = "serde"))]
#[test]
fn serde() {
use serde::Deserialize;
let ptr = PointerBuf::from_tokens(["foo", "bar"]);
let json = serde_json::to_string(&ptr).unwrap();
assert_eq!(json, "\"/foo/bar\"");
let deserialized: PointerBuf = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, ptr);
let ptr = Pointer::from_static("/foo/bar");
let json = serde_json::to_string(&ptr).unwrap();
assert_eq!(json, "\"/foo/bar\"");
let mut de = serde_json::Deserializer::from_str("\"/foo/bar\"");
let p = <&Pointer>::deserialize(&mut de).unwrap();
assert_eq!(p, ptr);
let s = serde_json::to_string(p).unwrap();
assert_eq!(json, s);
let invalid = serde_json::from_str::<&Pointer>("\"foo/bar\"");
assert!(invalid.is_err());
assert_eq!(
invalid.unwrap_err().to_string(),
"failed to parse json pointer\n\ncaused by:\njson pointer is malformed as it does not start with a backslash ('/') at line 1 column 9"
);
}
#[test]
fn to_owned() {
let ptr = Pointer::from_static("/bread/crumbs");
let buf = ptr.to_owned();
assert_eq!(buf, "/bread/crumbs");
}
#[test]
fn concat() {
let ptr = Pointer::from_static("/foo");
let barbaz = Pointer::from_static("/bar/baz");
assert_eq!(ptr.concat(barbaz), "/foo/bar/baz");
}
#[test]
fn with_leading_token() {
let ptr = Pointer::from_static("/bar");
let foobar = ptr.with_leading_token("foo");
assert_eq!(foobar, "/foo/bar");
}
#[test]
fn with_trailing_token() {
let ptr = Pointer::from_static("/foo");
let foobar = ptr.with_trailing_token("bar");
assert_eq!(foobar, "/foo/bar");
}
#[test]
fn len() {
let ptr = Pointer::from_static("/foo/bar");
assert_eq!(ptr.len(), 8);
let mut ptr = ptr.to_buf();
ptr.push_back("~");
assert_eq!(ptr.len(), 11);
}
#[test]
fn is_empty() {
assert!(Pointer::from_static("").is_empty());
assert!(!Pointer::from_static("/").is_empty());
}
#[test]
#[allow(clippy::cmp_owned, unused_must_use)]
fn partial_eq() {
let ptr_string = String::from("/bread/crumbs");
let ptr_str = "/bread/crumbs";
let ptr = Pointer::from_static(ptr_str);
let ptr_buf = ptr.to_buf();
<&Pointer as PartialEq<&Pointer>>::eq(&ptr, &ptr);
<Pointer as PartialEq<&str>>::eq(ptr, &ptr_str);
<&Pointer as PartialEq<String>>::eq(&ptr, &ptr_string);
<Pointer as PartialEq<String>>::eq(ptr, &ptr_string);
<Pointer as PartialEq<PointerBuf>>::eq(ptr, &ptr_buf);
<&str as PartialEq<Pointer>>::eq(&ptr_str, ptr);
<String as PartialEq<Pointer>>::eq(&ptr_string, ptr);
<str as PartialEq<Pointer>>::eq(ptr_str, ptr);
<PointerBuf as PartialEq<str>>::eq(&ptr_buf, ptr_str);
<PointerBuf as PartialEq<PointerBuf>>::eq(&ptr_buf, &ptr_buf);
<PointerBuf as PartialEq<Pointer>>::eq(&ptr_buf, ptr);
<Pointer as PartialEq<PointerBuf>>::eq(ptr, &ptr_buf);
<PointerBuf as PartialEq<&Pointer>>::eq(&ptr_buf, &ptr);
<PointerBuf as PartialEq<&str>>::eq(&ptr_buf, &ptr_str);
<PointerBuf as PartialEq<String>>::eq(&ptr_buf, &ptr_string);
<&Pointer as PartialEq<PointerBuf>>::eq(&ptr, &ptr_buf);
<str as PartialEq<PointerBuf>>::eq(ptr_str, &ptr_buf);
<&str as PartialEq<PointerBuf>>::eq(&ptr_str, &ptr_buf);
<String as PartialEq<PointerBuf>>::eq(&ptr_string, &ptr_buf);
}
#[test]
fn partial_ord() {
let a_str = "/foo/bar";
let a_string = a_str.to_string();
let a_ptr = Pointer::from_static(a_str);
let a_buf = a_ptr.to_buf();
let b_str = "/foo/bar";
let b_string = b_str.to_string();
let b_ptr = Pointer::from_static(b_str);
let b_buf = b_ptr.to_buf();
let c_str = "/foo/bar/baz";
let c_string = c_str.to_string();
let c_ptr = Pointer::from_static(c_str);
let c_buf = c_ptr.to_buf();
assert!(<Pointer as PartialOrd<PointerBuf>>::lt(a_ptr, &c_buf));
assert!(<PointerBuf as PartialOrd<Pointer>>::lt(&a_buf, c_ptr));
assert!(<String as PartialOrd<Pointer>>::lt(&a_string, c_ptr));
assert!(<str as PartialOrd<Pointer>>::lt(a_str, c_ptr));
assert!(<str as PartialOrd<PointerBuf>>::lt(a_str, &c_buf));
assert!(<&str as PartialOrd<Pointer>>::lt(&a_str, c_ptr));
assert!(<&str as PartialOrd<PointerBuf>>::lt(&a_str, &c_buf));
assert!(<&Pointer as PartialOrd<PointerBuf>>::lt(&a_ptr, &c_buf));
assert!(<&Pointer as PartialOrd<&str>>::lt(&b_ptr, &c_str));
assert!(<Pointer as PartialOrd<String>>::lt(a_ptr, &c_string));
assert!(<PointerBuf as PartialOrd<&str>>::lt(&a_buf, &c_str));
assert!(<PointerBuf as PartialOrd<String>>::lt(&a_buf, &c_string));
assert!(a_ptr < c_buf);
assert!(c_buf > a_ptr);
assert!(a_buf < c_ptr);
assert!(a_ptr < c_buf);
assert!(a_ptr < c_ptr);
assert!(a_ptr <= c_ptr);
assert!(c_ptr > a_ptr);
assert!(c_ptr >= a_ptr);
assert!(a_ptr == b_ptr);
assert!(a_ptr <= b_ptr);
assert!(a_ptr >= b_ptr);
assert!(a_string < c_buf);
assert!(a_string <= c_buf);
assert!(c_string > a_buf);
assert!(c_string >= a_buf);
assert!(a_string == b_buf);
assert!(a_ptr < c_buf);
assert!(a_ptr <= c_buf);
assert!(c_ptr > a_buf);
assert!(c_ptr >= a_buf);
assert!(a_ptr == b_buf);
assert!(a_ptr <= b_buf);
assert!(a_ptr >= b_buf);
assert!(a_ptr < c_buf);
assert!(c_ptr > b_string);
#[allow(clippy::nonminimal_bool)]
let not = !(a_ptr > c_buf);
assert!(not);
}
#[test]
fn intersection() {
struct Test {
base: &'static str,
a_suffix: &'static str,
b_suffix: &'static str,
}
let tests = [
Test {
base: "",
a_suffix: "/",
b_suffix: "/a/b/c",
},
Test {
base: "",
a_suffix: "",
b_suffix: "",
},
Test {
base: "/a",
a_suffix: "/",
b_suffix: "/suffix",
},
Test {
base: "/a",
a_suffix: "/suffix",
b_suffix: "",
},
Test {
base: "/¦\\>‶“lv\u{eedd}\u{8a}Y\n\u{99}𘐷vT\n\u{4}Hª\\ 嗱\\Yl6Y`\"1\u{6dd}\u{17}\0\u{10}ዄ8\"Z닍6i)V;\u{6be4c}\u{b}\u{59836}`\u{1e}㑍§~05\u{1d}\u{8a}[뵔\u{437c3}j\u{f326}\";*\u{c}*U\u{1b}\u{8a}I\u{4}묁",
a_suffix: "/Y\u{2064}",
b_suffix: "",
}
];
for Test {
base,
a_suffix,
b_suffix,
} in tests
{
let base = PointerBuf::parse(base).expect(&format!("failed to parse ${base}"));
let mut a = base.clone();
let mut b = base.clone();
a.append(PointerBuf::parse(a_suffix).unwrap());
b.append(PointerBuf::parse(b_suffix).unwrap());
let intersection = a.intersection(&b);
assert_eq!(intersection, base);
}
}
#[test]
fn parse_error_display() {
assert_eq!(
ParseError::NoLeadingBackslash.to_string(),
"json pointer is malformed as it does not start with a backslash ('/')"
);
}
#[test]
fn into_iter() {
use core::iter::IntoIterator;
let ptr = PointerBuf::from_tokens(["foo", "bar", "baz"]);
let tokens: Vec<Token> = ptr.into_iter().collect();
let from_tokens = PointerBuf::from_tokens(tokens);
assert_eq!(ptr, from_tokens);
let ptr = Pointer::from_static("/foo/bar/baz");
let tokens: Vec<_> = ptr.into_iter().collect();
assert_eq!(ptr, PointerBuf::from_tokens(tokens));
}
#[test]
fn from_str() {
let p = PointerBuf::from_str("/foo/bar").unwrap();
assert_eq!(p, "/foo/bar");
}
#[test]
fn from_token() {
let p = PointerBuf::from(Token::new("foo"));
assert_eq!(p, "/foo");
}
#[test]
fn from_usize() {
let p = PointerBuf::from(0);
assert_eq!(p, "/0");
}
#[test]
fn borrow() {
let ptr = PointerBuf::from_tokens(["foo", "bar"]);
let borrowed: &Pointer = ptr.borrow();
assert_eq!(borrowed, "/foo/bar");
}
}