use std::borrow::Cow;
use std::str::CharIndices;
use super::{BorrowedSegment, ValuePath};
#[derive(Clone)]
pub struct JitValuePath<'a> {
path: &'a str,
}
impl JitValuePath<'_> {
pub fn new(path: &str) -> JitValuePath<'_> {
JitValuePath { path }
}
}
#[derive(Clone)]
pub struct JitValuePathIter<'a> {
path: &'a str,
chars: CharIndices<'a>,
state: JitState,
escape_buffer: String,
}
impl<'a> JitValuePathIter<'a> {
fn finish(&mut self) -> Option<BorrowedSegment<'a>> {
let result = match self.state {
JitState::Start
| JitState::IndexStart
| JitState::Index { .. }
| JitState::NegativeIndex { .. }
| JitState::Quote { .. }
| JitState::EscapedQuote
| JitState::Dot => Some(BorrowedSegment::Invalid),
JitState::Continue | JitState::EventRoot | JitState::End => None,
JitState::Field { start } => {
Some(BorrowedSegment::Field(Cow::Borrowed(&self.path[start..])))
}
};
self.state = JitState::End;
result
}
pub fn new(path: &'a str) -> Self {
Self {
chars: path.char_indices(),
path,
state: JitState::Start,
escape_buffer: String::new(),
}
}
}
impl<'a> ValuePath<'a> for JitValuePath<'a> {
type Iter = JitValuePathIter<'a>;
fn segment_iter(&self) -> Self::Iter {
JitValuePathIter::new(self.path)
}
}
#[derive(Clone)]
enum JitState {
EventRoot,
Start,
Continue,
Dot,
IndexStart,
NegativeIndex { value: isize },
Index { value: isize },
Field { start: usize },
Quote { start: usize },
EscapedQuote,
End,
}
type Transition<'a> = (Option<Option<BorrowedSegment<'a>>>, JitState);
impl<'a> JitValuePathIter<'a> {
fn escaped_quote_transition(&mut self, c: char) -> Transition<'a> {
match c {
'"' => (
Some(Some(BorrowedSegment::Field(
std::mem::take(&mut self.escape_buffer).into(),
))),
JitState::Continue,
),
'\\' => match self.chars.next() {
Some((_, escaped @ ('\\' | '"'))) => {
self.escape_buffer.push(escaped);
(None, JitState::EscapedQuote)
}
Some(_) | None => (Some(Some(BorrowedSegment::Invalid)), JitState::End),
},
_ => {
self.escape_buffer.push(c);
(None, JitState::EscapedQuote)
}
}
}
fn index_transition(value: isize, c: char, negative: bool) -> Transition<'a> {
match c {
'0'..='9' => {
let digit = c as isize - '0' as isize;
let value = if negative {
value * 10 - digit
} else {
value * 10 + digit
};
let state = if negative {
JitState::NegativeIndex { value }
} else {
JitState::Index { value }
};
(None, state)
}
']' => (
Some(Some(BorrowedSegment::Index(value))),
JitState::Continue,
),
_ => (Some(Some(BorrowedSegment::Invalid)), JitState::End),
}
}
}
impl<'a> Iterator for JitValuePathIter<'a> {
type Item = BorrowedSegment<'a>;
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.chars.next() {
None => return self.finish(),
Some((index, c)) => {
let (result, state) = match self.state {
JitState::Start => match c {
'.' => (None, JitState::EventRoot),
'A'..='Z' | 'a'..='z' | '_' | '0'..='9' | '@' | '-' => {
(None, JitState::Field { start: index })
}
'[' => (None, JitState::IndexStart),
'\"' => (None, JitState::Quote { start: index + 1 }),
_ => (Some(Some(BorrowedSegment::Invalid)), JitState::End),
},
JitState::Continue => match c {
'.' => (None, JitState::Dot),
'A'..='Z' | 'a'..='z' | '_' | '0'..='9' | '@' | '-' => {
(None, JitState::Field { start: index })
}
'[' => (None, JitState::IndexStart),
'\"' => (None, JitState::Quote { start: index + 1 }),
_ => (Some(Some(BorrowedSegment::Invalid)), JitState::End),
},
JitState::EventRoot => match c {
'A'..='Z' | 'a'..='z' | '_' | '0'..='9' | '@' | '-' => {
(None, JitState::Field { start: index })
}
'[' => (None, JitState::IndexStart),
'\"' => (None, JitState::Quote { start: index + 1 }),
_ => (Some(Some(BorrowedSegment::Invalid)), JitState::End),
},
JitState::Dot => match c {
'A'..='Z' | 'a'..='z' | '_' | '0'..='9' | '@' | '-' => {
(None, JitState::Field { start: index })
}
'\"' => (None, JitState::Quote { start: index + 1 }),
_ => (Some(Some(BorrowedSegment::Invalid)), JitState::End),
},
JitState::Field { start } => match c {
'A'..='Z' | 'a'..='z' | '_' | '0'..='9' | '@' | '-' => {
(None, JitState::Field { start })
}
'.' => (
Some(Some(BorrowedSegment::Field(Cow::Borrowed(
&self.path[start..index],
)))),
JitState::Dot,
),
'[' => (
Some(Some(BorrowedSegment::Field(Cow::Borrowed(
&self.path[start..index],
)))),
JitState::IndexStart,
),
_ => (Some(Some(BorrowedSegment::Invalid)), JitState::End),
},
JitState::Quote { start } => match c {
'\"' => (
Some(Some(BorrowedSegment::Field(Cow::Borrowed(
&self.path[start..index],
)))),
JitState::Continue,
),
'\\' => {
self.path = &self.path[start..];
self.chars = self.path.char_indices();
(None, JitState::EscapedQuote)
}
_ => (None, JitState::Quote { start }),
},
JitState::EscapedQuote => self.escaped_quote_transition(c),
JitState::IndexStart => match c {
'0'..='9' => (
None,
JitState::Index {
value: c as isize - '0' as isize,
},
),
'-' => (None, JitState::NegativeIndex { value: 0 }),
_ => (Some(Some(BorrowedSegment::Invalid)), JitState::End),
},
JitState::Index { value } => Self::index_transition(value, c, false),
JitState::NegativeIndex { value } => Self::index_transition(value, c, true),
JitState::End => (Some(None), JitState::End),
};
self.state = state;
if let Some(result) = result {
return result;
}
}
}
}
}
}
#[cfg(test)]
mod test {
use super::{BorrowedSegment, JitValuePath, ValuePath};
#[test]
fn parsing() {
let test_cases = vec![
("", vec![BorrowedSegment::Invalid]),
(".", vec![]),
("]", vec![BorrowedSegment::Invalid]),
("]foo", vec![BorrowedSegment::Invalid]),
("..", vec![BorrowedSegment::Invalid]),
("...", vec![BorrowedSegment::Invalid]),
("f", vec![BorrowedSegment::Field("f".into())]),
(".f", vec![BorrowedSegment::Field("f".into())]),
(".[", vec![BorrowedSegment::Invalid]),
(
"f.",
vec![BorrowedSegment::Field("f".into()), BorrowedSegment::Invalid],
),
("foo", vec![BorrowedSegment::Field("foo".into())]),
(
"ec2.metadata.\"availability-zone\"",
vec![
BorrowedSegment::Field("ec2".into()),
BorrowedSegment::Field("metadata".into()),
BorrowedSegment::Field("availability-zone".into()),
],
),
(".foo", vec![BorrowedSegment::Field("foo".into())]),
(".a-b", vec![BorrowedSegment::Field("a-b".into())]),
(".a-b-", vec![BorrowedSegment::Field("a-b-".into())]),
(".-a-b", vec![BorrowedSegment::Field("-a-b".into())]),
(
".@timestamp",
vec![BorrowedSegment::Field("@timestamp".into())],
),
(
"foo[",
vec![
BorrowedSegment::Field("foo".into()),
BorrowedSegment::Invalid,
],
),
("foo$", vec![BorrowedSegment::Invalid]),
(
"\"$peci@l chars\"",
vec![BorrowedSegment::Field("$peci@l chars".into())],
),
(
".foo.foo bar",
vec![
BorrowedSegment::Field("foo".into()),
BorrowedSegment::Invalid,
],
),
(
".foo.\"foo bar\".bar",
vec![
BorrowedSegment::Field("foo".into()),
BorrowedSegment::Field("foo bar".into()),
BorrowedSegment::Field("bar".into()),
],
),
("[1]", vec![BorrowedSegment::Index(1)]),
("[42]", vec![BorrowedSegment::Index(42)]),
(".[42]", vec![BorrowedSegment::Index(42)]),
(
"[42].foo",
vec![
BorrowedSegment::Index(42),
BorrowedSegment::Field("foo".into()),
],
),
];
assert_paths(test_cases);
}
#[test]
fn parsing_indexes_and_quotes() {
let test_cases = vec![
(
"foo.[42]",
vec![
BorrowedSegment::Field("foo".into()),
BorrowedSegment::Invalid,
],
),
(
"foo..bar",
vec![
BorrowedSegment::Field("foo".into()),
BorrowedSegment::Invalid,
],
),
(
"[42]foo",
vec![
BorrowedSegment::Index(42),
BorrowedSegment::Field("foo".into()),
],
),
("[-1]", vec![BorrowedSegment::Index(-1)]),
("[-42]", vec![BorrowedSegment::Index(-42)]),
(".[-42]", vec![BorrowedSegment::Index(-42)]),
(
"[-42].foo",
vec![
BorrowedSegment::Index(-42),
BorrowedSegment::Field("foo".into()),
],
),
(
"[-42]foo",
vec![
BorrowedSegment::Index(-42),
BorrowedSegment::Field("foo".into()),
],
),
(
".\"[42]. {}-_\"",
vec![BorrowedSegment::Field("[42]. {}-_".into())],
),
("\"a\\\"a\"", vec![BorrowedSegment::Field("a\"a".into())]),
(".\"a\\\"a\"", vec![BorrowedSegment::Field("a\"a".into())]),
(
".foo.\"a\\\"a\".\"b\\\\b\".bar",
vec![
BorrowedSegment::Field("foo".into()),
BorrowedSegment::Field("a\"a".into()),
BorrowedSegment::Field("b\\b".into()),
BorrowedSegment::Field("bar".into()),
],
),
(r#"."🤖""#, vec![BorrowedSegment::Field("🤖".into())]),
("(a)", vec![BorrowedSegment::Invalid]),
];
assert_paths(test_cases);
}
fn assert_paths(test_cases: Vec<(&str, Vec<BorrowedSegment<'_>>)>) {
for (path, expected) in test_cases {
let jit = JitValuePath::new(path);
assert!(
ValuePath::eq(&jit, &expected),
"Not equal. Input={:?}\nExpected: {:?}\nActual: {:?}",
path,
(&expected).segment_iter().collect::<Vec<_>>(),
jit.segment_iter().collect::<Vec<_>>()
);
}
}
}