#![forbid(unsafe_code)]
#![doc(html_logo_url = "https://raw.githubusercontent.com/V0ldek/rsonpath/main/img/rsonquery-logo.svg")]
#![cfg_attr(
not(debug_assertions),
warn(
missing_docs,
clippy::cargo_common_metadata,
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::too_long_first_doc_paragraph
)
)]
#![cfg_attr(not(debug_assertions), warn(rustdoc::missing_crate_level_docs))]
#![cfg_attr(not(test), warn(clippy::unwrap_used))]
#![cfg_attr(
not(debug_assertions),
warn(clippy::print_stderr, clippy::print_stdout, clippy::todo)
)]
#![cfg_attr(docsrs, feature(doc_cfg))]
pub mod builder;
pub mod error;
pub mod num;
mod parser;
pub mod prelude;
pub mod str;
pub(crate) const JSONPATH_WHITESPACE: [char; 4] = [' ', '\t', '\n', '\r'];
pub(crate) const JSONPATH_WHITESPACE_BYTES: [u8; 4] = [0x20, 0x09, 0x0A, 0x0D];
use crate::builder::{
EmptyComparisonExprBuilder, EmptyLogicalExprBuilder, JsonPathQueryBuilder, SingularJsonPathQueryBuilder,
SliceBuilder,
};
use std::{
fmt::{self, Display},
ops::Deref,
};
#[derive(Debug, Clone, Default)]
pub struct Parser {
options: ParserOptions,
}
#[derive(Debug, Clone, Default)]
pub struct ParserBuilder {
options: ParserOptions,
}
#[derive(Debug, Clone)]
struct ParserOptions {
recursion_limit: Option<usize>,
relaxed_whitespace: bool,
}
impl ParserBuilder {
#[inline]
#[must_use]
pub fn new() -> Self {
Self {
options: ParserOptions::default(),
}
}
#[inline]
pub fn set_recursion_limit(&mut self, value: Option<usize>) -> &mut Self {
self.options.recursion_limit = value;
self
}
#[inline]
pub fn allow_surrounding_whitespace(&mut self, value: bool) -> &mut Self {
self.options.relaxed_whitespace = value;
self
}
#[inline]
#[must_use]
pub fn build(&self) -> Parser {
Parser {
options: self.options.clone(),
}
}
}
impl ParserOptions {
fn is_leading_whitespace_allowed(&self) -> bool {
self.relaxed_whitespace
}
fn is_trailing_whitespace_allowed(&self) -> bool {
self.relaxed_whitespace
}
}
impl Default for ParserOptions {
#[inline(always)]
fn default() -> Self {
Self {
recursion_limit: Some(Parser::RECURSION_LIMIT_DEFAULT),
relaxed_whitespace: false,
}
}
}
impl From<ParserBuilder> for Parser {
#[inline(always)]
fn from(value: ParserBuilder) -> Self {
Self { options: value.options }
}
}
pub type Result<T> = std::result::Result<T, error::ParseError>;
#[inline]
pub fn parse(str: &str) -> Result<JsonPathQuery> {
Parser::default().parse(str)
}
impl Parser {
pub const RECURSION_LIMIT_DEFAULT: usize = 128;
#[inline]
pub fn parse(&self, str: &str) -> Result<JsonPathQuery> {
crate::parser::parse_with_options(str, &self.options)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Segment {
Child(Selectors),
Descendant(Selectors),
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Selectors {
inner: Vec<Selector>,
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Selector {
Name(str::JsonString),
Wildcard,
Index(Index),
Slice(Slice),
Filter(LogicalExpr),
}
impl From<str::JsonString> for Selector {
#[inline]
fn from(value: str::JsonString) -> Self {
Self::Name(value)
}
}
impl From<Index> for Selector {
#[inline]
fn from(value: Index) -> Self {
Self::Index(value)
}
}
impl From<Slice> for Selector {
#[inline]
fn from(value: Slice) -> Self {
Self::Slice(value)
}
}
impl From<LogicalExpr> for Selector {
#[inline]
fn from(value: LogicalExpr) -> Self {
Self::Filter(value)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Index {
FromStart(num::JsonUInt),
FromEnd(num::JsonNonZeroUInt),
}
impl<N: Into<num::JsonInt>> From<N> for Index {
#[inline]
fn from(value: N) -> Self {
let value = value.into();
if value.as_i64() >= 0 {
Self::FromStart(value.abs())
} else {
Self::FromEnd(value.abs().try_into().expect("checked for zero already"))
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Step {
Forward(num::JsonUInt),
Backward(num::JsonNonZeroUInt),
}
impl From<num::JsonInt> for Step {
#[inline]
fn from(value: num::JsonInt) -> Self {
if value.as_i64() >= 0 {
Self::Forward(value.abs())
} else {
Self::Backward(value.abs().try_into().expect("checked for zero already"))
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Slice {
start: Index,
end: Option<Index>,
step: Step,
}
impl Slice {
pub(crate) const DEFAULT_START_FORWARDS: Index = Index::FromStart(num::JsonUInt::ZERO);
#[inline(always)]
pub(crate) fn default_start_backwards() -> Index {
Index::FromEnd(1.try_into().expect("const 1 is nonzero"))
}
pub(crate) const DEFAULT_STEP: Step = Step::Forward(num::JsonUInt::ONE);
#[inline(always)]
#[must_use]
pub fn build() -> SliceBuilder {
SliceBuilder::new()
}
#[inline(always)]
#[must_use]
pub fn new(start: Index, end: Option<Index>, step: Step) -> Self {
Self { start, end, step }
}
#[inline(always)]
#[must_use]
pub fn start(&self) -> Index {
self.start
}
#[inline(always)]
#[must_use]
pub fn end(&self) -> Option<Index> {
self.end
}
#[inline(always)]
#[must_use]
pub fn step(&self) -> Step {
self.step
}
}
impl Default for Slice {
#[inline]
fn default() -> Self {
Self {
start: Index::FromStart(0.into()),
end: None,
step: Step::Forward(1.into()),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Literal {
String(str::JsonString),
Number(num::JsonNumber),
Bool(bool),
Null,
}
impl<S> From<S> for Literal
where
S: Into<str::JsonString>,
{
#[inline(always)]
fn from(value: S) -> Self {
Self::String(value.into())
}
}
impl From<num::JsonInt> for Literal {
#[inline(always)]
fn from(value: num::JsonInt) -> Self {
Self::Number(num::JsonNumber::Int(value))
}
}
impl From<num::JsonFloat> for Literal {
#[inline(always)]
fn from(value: num::JsonFloat) -> Self {
Self::Number(num::JsonNumber::Float(value))
}
}
impl From<num::JsonNumber> for Literal {
#[inline(always)]
fn from(value: num::JsonNumber) -> Self {
Self::Number(value)
}
}
impl From<bool> for Literal {
#[inline(always)]
fn from(value: bool) -> Self {
Self::Bool(value)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum LogicalExpr {
Or(LogicalExprNode, LogicalExprNode),
And(LogicalExprNode, LogicalExprNode),
Not(LogicalExprNode),
Comparison(ComparisonExpr),
Test(TestExpr),
}
impl LogicalExpr {
#[inline(always)]
#[must_use]
pub fn build() -> EmptyLogicalExprBuilder {
EmptyLogicalExprBuilder
}
fn precedence(&self) -> usize {
match self {
Self::Or(_, _) => 2,
Self::And(_, _) => 3,
Self::Comparison(_) => 4,
Self::Not(_) => 5,
Self::Test(_) => 10,
}
}
}
type LogicalExprNode = Box<LogicalExpr>;
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TestExpr {
Relative(JsonPathQuery),
Absolute(JsonPathQuery),
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ComparisonExpr {
lhs: Comparable,
op: ComparisonOp,
rhs: Comparable,
}
impl ComparisonExpr {
#[inline(always)]
#[must_use]
pub fn build() -> EmptyComparisonExprBuilder {
EmptyComparisonExprBuilder
}
#[inline]
#[must_use]
pub fn lhs(&self) -> &Comparable {
&self.lhs
}
#[inline]
#[must_use]
pub fn op(&self) -> ComparisonOp {
self.op
}
#[inline]
#[must_use]
pub fn rhs(&self) -> &Comparable {
&self.rhs
}
#[inline]
#[must_use]
pub fn from_parts(lhs: Comparable, op: ComparisonOp, rhs: Comparable) -> Self {
Self { lhs, op, rhs }
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ComparisonOp {
EqualTo,
NotEqualTo,
LesserOrEqualTo,
GreaterOrEqualTo,
LessThan,
GreaterThan,
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Comparable {
Literal(Literal),
RelativeSingularQuery(SingularJsonPathQuery),
AbsoluteSingularQuery(SingularJsonPathQuery),
}
impl From<Literal> for Comparable {
#[inline(always)]
fn from(value: Literal) -> Self {
Self::Literal(value)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SingularJsonPathQuery {
segments: Vec<SingularSegment>,
}
impl SingularJsonPathQuery {
#[inline(always)]
#[must_use]
pub fn build() -> SingularJsonPathQueryBuilder {
SingularJsonPathQueryBuilder::new()
}
#[inline]
pub fn segments(&self) -> impl Iterator<Item = &'_ SingularSegment> {
self.segments.iter()
}
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SingularSegment {
Name(str::JsonString),
Index(Index),
}
impl FromIterator<SingularSegment> for SingularJsonPathQuery {
#[inline]
fn from_iter<T: IntoIterator<Item = SingularSegment>>(iter: T) -> Self {
Self {
segments: iter.into_iter().collect(),
}
}
}
impl From<SingularSegment> for Segment {
#[inline]
fn from(value: SingularSegment) -> Self {
match value {
SingularSegment::Name(n) => Self::Child(Selectors::one(Selector::Name(n))),
SingularSegment::Index(i) => Self::Child(Selectors::one(Selector::Index(i))),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct JsonPathQuery {
segments: Vec<Segment>,
}
impl FromIterator<Segment> for JsonPathQuery {
#[inline]
fn from_iter<T: IntoIterator<Item = Segment>>(iter: T) -> Self {
Self {
segments: iter.into_iter().collect(),
}
}
}
impl JsonPathQuery {
#[inline(always)]
#[must_use]
pub fn build() -> JsonPathQueryBuilder {
JsonPathQueryBuilder::new()
}
fn try_to_singular(self) -> std::result::Result<SingularJsonPathQuery, Self> {
if self.segments.iter().all(Segment::is_singular) {
let mut singular_segments = Vec::with_capacity(self.segments.len());
for segment in self.segments {
singular_segments.push(segment.into_singular());
}
Ok(SingularJsonPathQuery {
segments: singular_segments,
})
} else {
Err(self)
}
}
}
impl JsonPathQuery {
#[inline(always)]
#[must_use]
pub fn segments(&self) -> &[Segment] {
&self.segments
}
}
impl Segment {
#[inline(always)]
#[must_use]
pub fn selectors(&self) -> &Selectors {
match self {
Self::Child(s) | Self::Descendant(s) => s,
}
}
#[inline(always)]
#[must_use]
pub fn is_child(&self) -> bool {
matches!(self, Self::Child(_))
}
#[inline(always)]
#[must_use]
pub fn is_descendant(&self) -> bool {
matches!(self, Self::Descendant(_))
}
fn is_singular(&self) -> bool {
match self {
Self::Child(s) => s.len() == 1 && s.first().is_singular(),
Self::Descendant(_) => false,
}
}
fn into_singular(self) -> SingularSegment {
assert!(self.is_singular(), "forcing a non-singular segment, this is a bug");
match self {
Self::Child(mut s) => match s.inner.drain(..).next().expect("is_singular") {
Selector::Name(n) => SingularSegment::Name(n),
Selector::Index(i) => SingularSegment::Index(i),
_ => unreachable!(),
},
Self::Descendant(_) => unreachable!(),
}
}
}
impl Selectors {
#[inline(always)]
#[must_use]
pub fn one(selector: Selector) -> Self {
Self { inner: vec![selector] }
}
#[inline]
#[must_use]
pub fn many(vec: Vec<Selector>) -> Self {
assert!(!vec.is_empty(), "cannot create an empty Selectors collection");
Self { inner: vec }
}
#[inline]
#[must_use]
pub fn first(&self) -> &Selector {
&self.inner[0]
}
#[inline]
#[must_use]
pub fn as_slice(&self) -> &[Selector] {
self
}
}
impl Selector {
#[inline(always)]
#[must_use]
pub const fn is_name(&self) -> bool {
matches!(self, Self::Name(_))
}
#[inline(always)]
#[must_use]
pub const fn is_wildcard(&self) -> bool {
matches!(self, Self::Wildcard)
}
#[inline(always)]
#[must_use]
pub const fn is_index(&self) -> bool {
matches!(self, Self::Index(_))
}
#[inline(always)]
#[must_use]
pub const fn is_slice(&self) -> bool {
matches!(self, Self::Slice(_))
}
#[inline(always)]
#[must_use]
pub const fn is_filter(&self) -> bool {
matches!(self, Self::Filter(_))
}
fn is_singular(&self) -> bool {
matches!(self, Self::Name(_) | Self::Index(_))
}
}
impl Index {
#[inline(always)]
#[must_use]
pub const fn is_start_based(&self) -> bool {
matches!(self, Self::FromStart(_))
}
#[inline(always)]
#[must_use]
pub const fn is_end_based(&self) -> bool {
matches!(self, Self::FromEnd(_))
}
}
impl Step {
#[inline(always)]
#[must_use]
pub const fn is_forward(&self) -> bool {
matches!(self, Self::Forward(_))
}
#[inline(always)]
#[must_use]
pub const fn is_backward(&self) -> bool {
matches!(self, Self::Backward(_))
}
}
impl Deref for Selectors {
type Target = [Selector];
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl Display for JsonPathQuery {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "$")?;
for s in &self.segments {
write!(f, "{s}")?;
}
Ok(())
}
}
impl Display for Segment {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Child(s) => write!(f, "{s}"),
Self::Descendant(s) => write!(f, "..{s}"),
}
}
}
impl Display for Selectors {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}", self.first())?;
for s in self.inner.iter().skip(1) {
write!(f, ", {s}")?;
}
write!(f, "]")?;
Ok(())
}
}
impl Display for Selector {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Name(n) => write!(f, "'{}'", str::escape(n.unquoted(), str::EscapeMode::SingleQuoted)),
Self::Wildcard => write!(f, "*"),
Self::Index(idx) => write!(f, "{idx}"),
Self::Slice(slice) => write!(f, "{slice}"),
Self::Filter(filter) => write!(f, "?{filter}"),
}
}
}
impl Display for Index {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::FromStart(idx) => write!(f, "{idx}"),
Self::FromEnd(idx) => write!(f, "-{idx}"),
}
}
}
impl Display for Step {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Forward(idx) => write!(f, "{idx}"),
Self::Backward(idx) => write!(f, "-{idx}"),
}
}
}
impl Display for Slice {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if (self.step.is_forward() && self.start != Self::DEFAULT_START_FORWARDS)
|| (self.step.is_backward() && self.start != Self::default_start_backwards())
{
write!(f, "{}", self.start)?;
}
write!(f, ":")?;
if let Some(end) = self.end {
write!(f, "{end}")?;
}
if self.step != Self::DEFAULT_STEP {
write!(f, ":{}", self.step)?;
}
Ok(())
}
}
impl Display for LogicalExpr {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Or(lhs, rhs) => {
if lhs.precedence() <= self.precedence() {
write!(f, "({lhs})")?;
} else {
write!(f, "{lhs}")?;
}
write!(f, " || ")?;
if rhs.precedence() < self.precedence() {
write!(f, "({rhs})")?;
} else {
write!(f, "{rhs}")?;
}
Ok(())
}
Self::And(lhs, rhs) => {
if lhs.precedence() < self.precedence() {
write!(f, "({lhs})")?;
} else {
write!(f, "{lhs}")?;
}
write!(f, " && ")?;
if rhs.precedence() <= self.precedence() {
write!(f, "({rhs})")?;
} else {
write!(f, "{rhs}")?;
}
Ok(())
}
Self::Not(expr) => {
if expr.precedence() <= self.precedence() {
write!(f, "!({expr})")
} else {
write!(f, "!{expr}")
}
}
Self::Comparison(expr) => write!(f, "{expr}"),
Self::Test(test) => write!(f, "{test}"),
}
}
}
impl Display for ComparisonExpr {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {} {}", self.lhs, self.op, self.rhs)
}
}
impl Display for TestExpr {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Relative(q) => {
write!(f, "@")?;
for s in q.segments() {
write!(f, "{s}")?;
}
}
Self::Absolute(q) => {
write!(f, "$")?;
for s in q.segments() {
write!(f, "{s}")?;
}
}
}
Ok(())
}
}
impl Display for Comparable {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Literal(lit) => write!(f, "{lit}"),
Self::RelativeSingularQuery(q) => {
write!(f, "@")?;
for s in q.segments() {
write!(f, "{s}")?;
}
Ok(())
}
Self::AbsoluteSingularQuery(q) => {
write!(f, "$")?;
for s in q.segments() {
write!(f, "{s}")?;
}
Ok(())
}
}
}
}
impl Display for Literal {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::String(s) => write!(f, "\"{}\"", str::escape(s.unquoted(), str::EscapeMode::DoubleQuoted)),
Self::Number(n) => write!(f, "{n}"),
Self::Bool(true) => write!(f, "true"),
Self::Bool(false) => write!(f, "false"),
Self::Null => write!(f, "null"),
}
}
}
impl Display for ComparisonOp {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::EqualTo => write!(f, "=="),
Self::NotEqualTo => write!(f, "!="),
Self::LesserOrEqualTo => write!(f, "<="),
Self::GreaterOrEqualTo => write!(f, ">="),
Self::LessThan => write!(f, "<"),
Self::GreaterThan => write!(f, ">"),
}
}
}
impl Display for SingularJsonPathQuery {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for s in &self.segments {
write!(f, "[{s}]")?;
}
Ok(())
}
}
impl Display for SingularSegment {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Name(n) => write!(f, "['{}']", str::escape(n.unquoted(), str::EscapeMode::SingleQuoted)),
Self::Index(i) => write!(f, "[{i}]"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn leading_whitespace_is_disallowed() {
let err = parse(" $").expect_err("should fail");
let display = format!("{err}");
let expected = r"error: query starting with whitespace
$
^^ leading whitespace is disallowed
(bytes 0-1)
suggestion: did you mean `$` ?
";
assert_eq!(display, expected);
}
#[test]
fn trailing_whitespace_is_disallowed() {
let err = parse("$ ").expect_err("should fail");
let display = format!("{err}");
let expected = r"error: query ending with whitespace
$
^^ trailing whitespace is disallowed
(bytes 1-2)
suggestion: did you mean `$` ?
";
assert_eq!(display, expected);
}
mod name_selector {
use super::*;
use pretty_assertions::assert_eq;
use test_case::test_case;
fn parse_single_quoted_name_selector(src: &str) -> Result<JsonPathQuery> {
let query_string = format!("$['{src}']");
parse(&query_string)
}
#[test_case("", ""; "empty")]
#[test_case("dog", "dog"; "ascii")]
#[test_case(r"\\", r"\"; "backslash")]
#[test_case("unescaped 🔥 fire emoji", "unescaped 🔥 fire emoji"; "unescaped emoji")]
#[test_case(r"escape \b backspace", "escape \u{0008} backspace"; "BS escape")]
#[test_case(r"escape \t tab", "escape \t tab"; "HT escape")]
#[test_case(r"escape \n endln", "escape \n endln"; "LF escape")]
#[test_case(r"escape \f formfeed", "escape \u{000C} formfeed"; "FF escape")]
#[test_case(r"escape \r carriage", "escape \r carriage"; "CR escape")]
#[test_case(r#"escape \' apost"#, r"escape ' apost"; "apostrophe escape")]
#[test_case(r"escape \/ slash", r"escape / slash"; "slash escape")]
#[test_case(r"escape \\ backslash", r"escape \ backslash"; "backslash escape")]
#[test_case(r"escape \u2112 script L", "escape â„’ script L"; "U+2112 Script Capital L escape")]
#[test_case(r"escape \u211269 script L", "escape â„’69 script L"; "U+2112 Script Capital L escape followed by digits")]
#[test_case(r"escape \u21a7 bar down arrow", "escape ↧ bar down arrow"; "U+21a7 Downwards Arrow From Bar (lowercase hex)")]
#[test_case(r"escape \u21A7 bar down arrow", "escape ↧ bar down arrow"; "U+21A7 Downwards Arrow From Bar (uppercase hex)")]
#[test_case(r"escape \ud83d\udd25 fire emoji", "escape 🔥 fire emoji"; "U+1F525 fire emoji escape (lowercase hex)")]
#[test_case(r"escape \uD83D\uDD25 fire emoji", "escape 🔥 fire emoji"; "U+1F525 fire emoji escape (uppercase hex)")]
fn parse_correct_single_quoted_name(src: &str, expected: &str) {
let res = parse_single_quoted_name_selector(src).expect("should successfully parse");
match res.segments().first() {
Some(Segment::Child(selectors)) => match selectors.first() {
Selector::Name(name) => assert_eq!(name.unquoted(), expected),
_ => panic!("expected to parse a single name selector, got {res:?}"),
},
_ => panic!("expected to parse a single name selector, got {res:?}"),
}
}
#[test]
fn parse_double_quoted_name_with_escaped_double_quote() {
let query_string = r#"$["escape \" quote"]"#;
let res = parse(query_string).expect("should successfully parse");
match res.segments().first() {
Some(Segment::Child(selectors)) => match selectors.first() {
Selector::Name(name) => assert_eq!(name.unquoted(), "escape \" quote"),
_ => panic!("expected to parse a single name selector, got {res:?}"),
},
_ => panic!("expected to parse a single name selector, got {res:?}"),
}
}
}
}