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';
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Token<'a> {
inner: Cow<'a, str>,
}
impl<'a> Token<'a> {
pub(crate) fn from_encoded_unchecked(inner: impl Into<Cow<'a, str>>) -> Self {
Self {
inner: inner.into(),
}
}
pub fn from_encoded(s: &'a str) -> Result<Self, InvalidEncodingError> {
let mut escaped = false;
for (offset, b) in s.bytes().enumerate() {
match b {
b'/' => return Err(InvalidEncodingError { offset }),
ENC_PREFIX => {
escaped = true;
}
TILDE_ENC | SLASH_ENC if escaped => {
escaped = false;
}
_ => {
if escaped {
return Err(InvalidEncodingError { offset });
}
}
}
}
if escaped {
return Err(InvalidEncodingError { offset: s.len() });
}
Ok(Self { inner: s.into() })
}
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();
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 {
inner: Cow::Owned(unsafe { String::from_utf8_unchecked(bytes) }),
}
} else {
Self { inner: s }
}
}
pub fn into_owned(self) -> Token<'static> {
Token {
inner: Cow::Owned(self.inner.into_owned()),
}
}
pub fn to_owned(&self) -> Token<'static> {
Token {
inner: Cow::Owned(self.inner.clone().into_owned()),
}
}
pub fn encoded(&self) -> &str {
&self.inner
}
pub fn decoded(&self) -> Cow<'_, str> {
if let Some(i) = self.inner.bytes().position(|b| b == ENC_PREFIX) {
let input = self.inner.as_bytes();
let mut bytes = Vec::with_capacity(input.len() + 1);
bytes.extend_from_slice(&input[..i]);
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);
}
}
}
Cow::Owned(unsafe { String::from_utf8_unchecked(bytes) })
} else {
self.inner.clone()
}
}
pub fn to_index(&self) -> Result<Index, ParseIndexError> {
self.try_into()
}
}
macro_rules! impl_from_num {
($($ty:ty),*) => {
$(
impl From<$ty> for Token<'static> {
fn from(v: $ty) -> Self {
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())
}
}
#[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().map(Token::from_encoded_unchecked)
}
}
impl<'t> Tokens<'t> {
pub(crate) fn new(inner: Split<'t, char>) -> Self {
Self { inner }
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct InvalidEncodingError {
pub offset: usize,
}
impl fmt::Display for InvalidEncodingError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"json pointer is malformed due to invalid encoding ('~' not followed by '0' or '1')"
)
}
}
#[cfg(feature = "std")]
impl std::error::Error for InvalidEncodingError {}
#[cfg(test)]
mod tests {
use crate::{assign::AssignError, index::OutOfBoundsError, 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 assign_error_display() {
let err = AssignError::FailedToParseIndex {
offset: 3,
source: ParseIndexError::InvalidInteger("a".parse::<usize>().unwrap_err()),
};
assert_eq!(
err.to_string(),
"assignment failed due to an invalid index at offset 3"
);
let err = AssignError::OutOfBounds {
offset: 3,
source: OutOfBoundsError {
index: 3,
length: 2,
},
};
assert_eq!(
err.to_string(),
"assignment failed due to index at offset 3 being out of bounds"
);
}
#[test]
#[cfg(feature = "std")]
fn assign_error_source() {
use std::error::Error;
let err = AssignError::FailedToParseIndex {
offset: 3,
source: ParseIndexError::InvalidInteger("a".parse::<usize>().unwrap_err()),
};
assert!(err.source().is_some());
assert!(err.source().unwrap().is::<ParseIndexError>());
let err = AssignError::OutOfBounds {
offset: 3,
source: OutOfBoundsError {
index: 3,
length: 2,
},
};
assert!(err.source().unwrap().is::<OutOfBoundsError>());
}
#[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 invalid_encoding_error_display() {
assert_eq!(
Token::from_encoded("~").unwrap_err().to_string(),
"json pointer is malformed due to invalid encoding ('~' not followed by '0' or '1')"
);
}
#[test]
fn tokens() {
let pointer = Pointer::from_static("/a/b/c");
let tokens: Vec<Token> = pointer.tokens().collect();
assert_eq!(
tokens,
vec![
Token::from_encoded_unchecked("a"),
Token::from_encoded_unchecked("b"),
Token::from_encoded_unchecked("c")
]
);
}
}