use crate::raw_token::RawToken;
mod private {
pub trait Sealed {}
impl Sealed for super::All {}
impl Sealed for super::Keys {}
impl Sealed for super::Key {}
impl Sealed for super::Index {}
impl Sealed for super::Values {}
impl Sealed for super::Depth {}
impl<M1, M2> Sealed for super::And<M1, M2> {}
impl<M1, M2> Sealed for super::Or<M1, M2> {}
}
pub trait Mask: Sized + private::Sealed {
#[doc(hidden)]
fn match_token(&mut self, token: &RawToken) -> bool;
fn and<O: Mask>(self, other: O) -> And<Self, O> {
And {
left: self,
right: other,
}
}
fn or<O: Mask>(self, other: O) -> Or<Self, O> {
Or {
left: self,
right: other,
}
}
}
pub struct All;
impl Mask for All {
#[inline]
fn match_token(&mut self, _: &RawToken) -> bool {
true
}
}
pub struct Values;
impl Mask for Values {
#[inline]
fn match_token(&mut self, token: &RawToken) -> bool {
token.is_primitive_value()
}
}
pub struct Keys;
impl Mask for Keys {
#[inline]
fn match_token(&mut self, token: &RawToken) -> bool {
matches!(token, RawToken::ObjectKey(_))
}
}
pub struct And<M1, M2> {
left: M1,
right: M2,
}
impl<M1: Mask, M2: Mask> Mask for And<M1, M2> {
#[inline]
fn match_token(&mut self, token: &RawToken) -> bool {
let matcha = self.left.match_token(token);
let matchb = self.right.match_token(token);
matcha && matchb
}
}
pub struct Or<M1, M2> {
left: M1,
right: M2,
}
impl<M1: Mask, M2: Mask> Mask for Or<M1, M2> {
#[inline]
fn match_token(&mut self, token: &RawToken) -> bool {
let matcha = self.right.match_token(token);
let matchb = self.left.match_token(token);
matcha || matchb
}
}
pub fn all() -> All {
All
}
pub fn key(name: impl AsRef<str>) -> Key {
Key {
name: name.as_ref().to_string(),
state: KeyState::None,
container_count: 0,
}
}
pub fn index(idx: usize) -> Index {
Index {
idx,
container_count: 0,
structure: vec![],
state: IndexState::None,
}
}
pub fn depth(depth: usize) -> Depth {
Depth {
depth,
current_depth: 0,
}
}
pub fn keys() -> Keys {
Keys
}
pub fn values() -> Values {
Values
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum KeyState {
None,
Key,
Colon,
}
#[derive(Debug, Clone)]
pub struct Key {
name: String,
state: KeyState,
container_count: usize,
}
impl Mask for Key {
#[inline]
fn match_token(&mut self, token: &RawToken) -> bool {
use KeyState::*;
let token_matches = self.state == Colon;
match (&self.state, token) {
(None, RawToken::ObjectKey(s)) if s == &self.name => {
self.state = Key;
}
(Key, RawToken::Colon) => {
self.state = Colon;
}
(Colon, RawToken::ArrayStart | RawToken::ObjectStart) => {
self.container_count += 1;
}
(Colon, RawToken::ArrayEnd | RawToken::ObjectEnd) => {
self.container_count -= 1;
if self.container_count == 0 {
self.state = None;
}
}
(Colon, t) if t.is_primitive_value() && self.container_count == 0 => {
self.state = None;
}
_ => {}
};
token_matches
}
}
#[derive(Debug, Clone)]
enum Container {
Array(usize),
Object,
}
#[derive(Debug, Clone)]
pub struct Index {
idx: usize,
container_count: usize,
structure: Vec<Container>,
state: IndexState,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum IndexState {
None,
StructuralMatch,
}
impl Mask for Index {
fn match_token(&mut self, token: &RawToken) -> bool {
let matched_index = || {
if let Some(Container::Array(current)) = self.structure.last() {
current == &self.idx
} else {
false
}
};
use IndexState::*;
let token_matches = match (self.state, token) {
(None, RawToken::ArrayStart | RawToken::ObjectStart) if matched_index() => {
self.state = StructuralMatch;
self.container_count += 1;
true
}
(None, RawToken::ArrayStart) => {
self.structure.push(Container::Array(0));
false
}
(None, RawToken::ObjectStart) => {
self.structure.push(Container::Object);
false
}
(None, RawToken::ObjectEnd | RawToken::ArrayEnd) => {
self.structure.pop();
false
}
(None, RawToken::Comma) => {
if let Some(Container::Array(current)) = self.structure.last_mut() {
*current += 1;
}
false
}
(None, t) if t.is_primitive_value() && matched_index() => true,
(None, _) => false,
(StructuralMatch, RawToken::ArrayStart | RawToken::ObjectStart) => {
self.container_count += 1;
true
}
(StructuralMatch, RawToken::ArrayEnd | RawToken::ObjectEnd) => {
self.container_count -= 1;
if self.container_count == 0 {
self.state = None;
}
true
}
(StructuralMatch, _) => true,
};
token_matches
}
}
#[derive(Debug, Clone, Copy)]
pub struct Depth {
depth: usize,
current_depth: usize,
}
impl Mask for Depth {
fn match_token(&mut self, token: &RawToken) -> bool {
match token {
RawToken::ArrayStart | RawToken::ObjectStart => {
self.current_depth += 1;
self.current_depth == self.depth || self.current_depth - 1 == self.depth
}
RawToken::ArrayEnd | RawToken::ObjectEnd => {
self.current_depth -= 1;
self.current_depth == self.depth || self.current_depth + 1 == self.depth
}
_ => self.depth == self.current_depth,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Format;
use crate::TokenReader;
#[track_caller]
fn test_mask<I>(json: &str, mask: impl Mask, tokens: I)
where
I: IntoIterator<Item = &'static str>,
{
let actual_tokens = TokenReader::new(json.as_bytes())
.with_mask(mask)
.into_iter()
.flatten()
.map(|t| t.to_string())
.collect::<Vec<_>>();
let expected_tokens = tokens
.into_iter()
.map(|s| s.to_string())
.collect::<Vec<_>>();
assert_eq!(actual_tokens, expected_tokens);
}
#[track_caller]
fn test_mask_cjson<I>(json: &str, mask: impl Mask, tokens: I)
where
I: IntoIterator<Item = &'static str>,
{
let actual_tokens = TokenReader::new(json.as_bytes())
.with_mask(mask)
.with_format(Format::Concatenated)
.into_iter()
.flatten()
.map(|t| t.to_string())
.collect::<Vec<_>>();
let expected_tokens = tokens
.into_iter()
.map(|s| s.to_string())
.collect::<Vec<_>>();
assert_eq!(actual_tokens, expected_tokens);
}
#[test]
fn key_mask() {
test_mask(r#"[ false ]"#, key("key"), []);
test_mask(r#"{"key1": "value"}"#, key("key"), []);
test_mask(
r#"{ "key": true, "key": false }"#,
key("key"),
["true", "false"],
);
test_mask(
r#"{ "key" : { "key": true } }"#,
key("key"),
["{", "\"key\"", ":", "true", "}"],
);
test_mask(
r#"{ "key": { "object": true } }"#,
key("key"),
["{", "\"object\"", ":", "true", "}"],
);
test_mask(
r#"{ "key": [true, false] }"#,
key("key"),
["[", "true", ",", "false", "]"],
);
}
#[test]
fn index_mask() {
test_mask(r#"{ "key": false }"#, index(0), []);
test_mask("[]", index(0), []);
test_mask("[1, [2, 3, 4, 5], 6]", index(3), ["5"]);
test_mask(
"[1, [2, [3]]]",
index(1),
["[", "2", ",", "[", "3", "]", "]"],
);
test_mask(
"[[true], [true], [true]]",
index(0),
["[", "true", "]", "true", "true"],
)
}
#[test]
fn depth_mask() {
test_mask("[]", depth(0), ["[", "]"]);
test_mask("[]", depth(1), ["[", "]"]);
test_mask("{}", depth(0), ["{", "}"]);
test_mask("{}", depth(1), ["{", "}"]);
test_mask(
"[1, [2], 3]",
depth(1),
["[", "1", ",", "[", "]", ",", "3", "]"],
);
}
#[test]
fn keys_mask() {
test_mask("{}", keys(), []);
test_mask(
r#"{ "key1": { "key2": "something" } }"#,
keys(),
["\"key1\"", "\"key2\""],
);
}
#[test]
fn combining_masks() {
let json = r#"
[
{
"name": "John Smith",
"age": 40,
"phones": [ 111, 222 ]
},
{
"name": "Jane Smith",
"age": 41,
"phones": [ 333, 444, 555 ]
},
]
"#;
let mask = key("phones").and(values()).or(key("name"));
test_mask(
json,
mask,
[
r#""John Smith""#,
"111",
"222",
r#""Jane Smith""#,
"333",
"444",
"555",
],
);
}
#[test]
fn masks_are_commutative() {
test_mask(
r#"{ "hello": [ 3 ] }"#,
depth(1).and(key("hello")),
["[", "]"],
);
test_mask(
r#"{ "hello": [ 3 ] }"#,
key("hello").and(depth(1)),
["[", "]"],
);
}
#[test]
fn masks_work_with_concatenated_json() {
test_mask_cjson(
r#"[{ "idx": 1 }][{ "idx": 2 }][{ "idx": 3 }]"#,
index(0).and(values()),
["1", "2", "3"],
);
test_mask_cjson(
r#"
{ "name": "serde", "author": "someperson" }
{ "name": "jsn" }
"#,
key("name").or(key("author")),
["\"serde\"", "\"someperson\"", "\"jsn\""],
);
}
}