diff --git a/src/ptr.rs b/src/ptr.rs index 8525da0298fde69e5027c02757d24a8a48489178..57aeeb0284411d1178a9410c91832884ff7eb818 100644 --- a/src/ptr.rs +++ b/src/ptr.rs @@ -2,6 +2,7 @@ use parser::{parse, ParseError}; use serde_json::Value; use std::fmt::{Display, Formatter}; use std::fmt::Result as FmtResult; +use std::fmt::Write; use std::marker::PhantomData; use std::ops::{Index, IndexMut}; use std::str::FromStr; @@ -94,6 +95,30 @@ impl<S: AsRef<str>, C: AsRef<[S]>> JsonPointer<S, C> { } })) } + + /// Converts a JSON pointer to a string in URI Fragment Identifier + /// Representation, including the leading `#`. + pub fn uri_fragment(&self) -> String { + fn legal_fragment_byte(b: u8) -> bool { + match b { + 0x21 | 0x24 | 0x26...0x3b | 0x3d | 0x3f...0x5a | 0x5f | 0x61...0x7a => true, + _ => false, + } + } + + let mut s = "#".to_string(); + for part in self.ref_toks.as_ref().iter() { + s += "/"; + for b in part.as_ref().bytes() { + if legal_fragment_byte(b) { + s.push(b as char) + } else { + write!(s, "%{:02x}", b).unwrap() + } + } + } + s + } } impl<S: AsRef<str>> JsonPointer<S, Vec<S>> { diff --git a/tests/parsing.rs b/tests/parsing.rs index 4a16154ada6e234c5d05b5065062844adb1089ec..48b45398847fb45b210c412362a501233e458342 100644 --- a/tests/parsing.rs +++ b/tests/parsing.rs @@ -11,20 +11,20 @@ use regex::Regex; quickcheck! { -/// Essentially, `unparse(parse("..."))` should be a no-op when not in URI -/// Fragment Identifier Representation. +/// Essentially, `unparse(parse("..."))` should be a no-op. fn faithful_parse(s: String) -> TestResult { - if s.chars().next() == Some('#') { - TestResult::discard() + let ok = match s.parse::<JsonPointer<_, _>>() { + Ok(ptr) => if s.chars().next() == Some('#') { + (s == ptr.uri_fragment()) + } else { + (s == ptr.to_string()) + }, + Err(_) => return TestResult::discard(), + }; + if ok { + TestResult::passed() } else { - match s.parse::<JsonPointer<_, _>>() { - Ok(ptr) => if s == ptr.to_string() { - TestResult::passed() - } else { - TestResult::failed() - }, - Err(_) => TestResult::discard(), - } + TestResult::failed() } } @@ -33,7 +33,7 @@ fn faithful_parse(s: String) -> TestResult { fn parses_all_valid(s: String) -> bool { lazy_static! { static ref JSON_POINTER_REGEX: Regex = Regex::new("^(/([^/~]|~[01])*)*$").unwrap(); - static ref URI_FRAGMENT_REGEX: Regex = Regex::new("^#(/([^/~%]|~[01]|%[0-9a-fA-F]{2})*)*$").unwrap(); + static ref URI_FRAGMENT_REGEX: Regex = Regex::new("^#(/([^A-Za-z0-9._!$&'()*+,;=@/?-]|~[01]|%[0-9a-fA-F]{2})*)*$").unwrap(); } let matches_regex = JSON_POINTER_REGEX.is_match(&s) || URI_FRAGMENT_REGEX.is_match(&s); diff --git a/tests/uri_fragment.rs b/tests/uri_fragment.rs new file mode 100644 index 0000000000000000000000000000000000000000..1ec8bf7f48afcd5b38dffae099b65f1569c61832 --- /dev/null +++ b/tests/uri_fragment.rs @@ -0,0 +1,16 @@ +extern crate json_pointer; + +use json_pointer::JsonPointer; + +macro_rules! assert_unparse { + ($expr:expr) => { + let ptr = $expr.parse::<JsonPointer<_, _>>().unwrap(); + assert_eq!(ptr.uri_fragment(), $expr); + }; +} + +#[test] +fn uri_fragment_unparses() { + assert_unparse!("#/"); + assert_unparse!("#/per%25/%25cent"); +}