From 707a0492f939e2bf1a5aef1a89d2a641cbd8c54d Mon Sep 17 00:00:00 2001
From: Eric Rodrigues Pires
Date: Sat, 29 Nov 2025 15:44:19 -0300
Subject: [PATCH] Support casting and fix ambiguity with keys/operators
---
Cargo.lock | 11 ++
duperq/Cargo.toml | 3 +-
duperq/README.md | 2 +-
duperq/src/filter.rs | 104 ++++++-------
duperq/src/lib.rs | 1 +
duperq/src/main.rs | 125 +++++++++++++---
duperq/src/processor.rs | 6 +-
duperq/src/query.rs | 136 ++++++++++-------
duperq/src/types.rs | 325 ++++++++++++++++++++++++++++++++++++++++
9 files changed, 576 insertions(+), 137 deletions(-)
create mode 100644 duperq/src/types.rs
diff --git a/Cargo.lock b/Cargo.lock
index afca199..9bd490e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1030,6 +1030,7 @@ dependencies = [
"duper",
"futures",
"glob",
+ "num_cpus",
"regex",
"smol",
"temporal_rs",
@@ -1912,6 +1913,16 @@ dependencies = [
"autocfg",
]
+[[package]]
+name = "num_cpus"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
[[package]]
name = "object"
version = "0.32.2"
diff --git a/duperq/Cargo.toml b/duperq/Cargo.toml
index 8e026a5..f44eba9 100644
--- a/duperq/Cargo.toml
+++ b/duperq/Cargo.toml
@@ -15,9 +15,10 @@ anyhow = "1.0.100"
async-trait = "0.1.89"
chumsky = "0.11.2"
clap = { version = "4.5.53", features = ["derive"] }
-duper = { version = "0.4.3", path = "../duper" }
+duper = { version = "0.4.3", path = "../duper", features = ["ansi"] }
futures = "0.3.31"
glob = "0.3.3"
+num_cpus = "1.17.0"
regex = "1.12.2"
smol = "2.0.2"
temporal_rs = "0.1.2"
diff --git a/duperq/README.md b/duperq/README.md
index 5dadaf1..25f0c69 100644
--- a/duperq/README.md
+++ b/duperq/README.md
@@ -8,6 +8,6 @@
-A high-performance Duper filter and processor.
+A high-performance Duper/JSON filter and processor.
[Check out the official website for Duper.](https://duper.dev.br)
diff --git a/duperq/src/filter.rs b/duperq/src/filter.rs
index 280510b..2b32dbf 100644
--- a/duperq/src/filter.rs
+++ b/duperq/src/filter.rs
@@ -7,10 +7,10 @@ use temporal_rs::{
options::{Disambiguation, OffsetDisambiguation},
};
-use crate::accessor::DuperAccessor;
+use crate::{accessor::DuperAccessor, types::DuperType};
pub(crate) trait DuperFilter {
- fn filter<'v>(&self, value: &'v DuperValue<'_>) -> bool;
+ fn filter<'v>(&self, value: &DuperValue<'_>) -> bool;
}
// Branchless filters
@@ -18,7 +18,7 @@ pub(crate) trait DuperFilter {
pub(crate) struct TrueFilter;
impl DuperFilter for TrueFilter {
- fn filter<'v>(&self, _: &'v DuperValue<'v>) -> bool {
+ fn filter<'v>(&self, _: &DuperValue<'v>) -> bool {
true
}
}
@@ -29,7 +29,7 @@ impl DuperFilter for TrueFilter {
pub(crate) struct AndFilter(pub(crate) Vec>);
impl DuperFilter for AndFilter {
- fn filter<'v>(&self, value: &'v DuperValue<'v>) -> bool {
+ fn filter<'v>(&self, value: &DuperValue<'v>) -> bool {
self.0.iter().all(|inner| inner.filter(value))
}
}
@@ -50,7 +50,7 @@ impl chumsky::container::Container> for AndFilter {
pub(crate) struct OrFilter(pub(crate) Vec>);
impl DuperFilter for OrFilter {
- fn filter<'v>(&self, value: &'v DuperValue<'v>) -> bool {
+ fn filter<'v>(&self, value: &DuperValue<'v>) -> bool {
self.0.iter().any(|inner| inner.filter(value))
}
}
@@ -70,18 +70,32 @@ impl<'a> chumsky::container::Container> for OrFilter {
pub(crate) struct NotFilter(pub(crate) Box);
impl DuperFilter for NotFilter {
- fn filter<'v>(&self, value: &'v DuperValue<'v>) -> bool {
+ fn filter<'v>(&self, value: &DuperValue<'v>) -> bool {
!self.0.filter(value)
}
}
+pub(crate) struct CastFilter {
+ pub(crate) typ: DuperType,
+ pub(crate) filter: Box,
+}
+
+impl DuperFilter for CastFilter {
+ fn filter<'v>(&self, value: &DuperValue<'v>) -> bool {
+ self.typ
+ .cast(value)
+ .as_ref()
+ .is_some_and(|value| self.filter.filter(value))
+ }
+}
+
pub(crate) struct AccessorFilter {
pub(crate) filter: Box,
pub(crate) accessor: Box,
}
impl DuperFilter for AccessorFilter {
- fn filter<'v>(&self, value: &'v DuperValue<'_>) -> bool {
+ fn filter<'v>(&self, value: &DuperValue<'_>) -> bool {
self.accessor
.access(value)
.any(|inner| self.filter.filter(inner))
@@ -201,7 +215,7 @@ impl EqValue {
pub(crate) struct EqFilter(pub(crate) EqValue);
impl DuperFilter for EqFilter {
- fn filter<'v>(&self, value: &'v DuperValue<'v>) -> bool {
+ fn filter<'v>(&self, value: &DuperValue<'v>) -> bool {
match (&self.0, &value.inner) {
(EqValue::Identifier(this), _) => match this {
Some(this) => value
@@ -272,7 +286,7 @@ impl DuperFilter for EqFilter {
pub(crate) struct NeFilter(pub(crate) EqValue);
impl DuperFilter for NeFilter {
- fn filter<'v>(&self, value: &'v DuperValue<'v>) -> bool {
+ fn filter<'v>(&self, value: &DuperValue<'v>) -> bool {
match (&self.0, &value.inner) {
(EqValue::Identifier(this), _) => match this {
Some(this) => value
@@ -425,7 +439,7 @@ macro_rules! cmp_filter {
pub(crate) struct $filter(pub(crate) CmpValue);
impl DuperFilter for $filter {
- fn filter<'v>(&self, value: &'v DuperValue<'v>) -> bool {
+ fn filter<'v>(&self, value: &DuperValue<'v>) -> bool {
match (&self.0, &value.inner) {
(CmpValue::Len(this), DuperInner::Object(that)) => {
matches!(that.len().cmp(this), $ord)
@@ -496,40 +510,20 @@ cmp_filter!(LeFilter, Ordering::Less | Ordering::Equal);
cmp_filter!(LtFilter, Ordering::Less);
#[derive(Clone)]
-pub(crate) enum IsFilter {
- Object,
- Array,
- Tuple,
- String,
- Bytes,
- TemporalInstant,
- TemporalZonedDateTime,
- TemporalPlainDate,
- TemporalPlainTime,
- TemporalPlainDateTime,
- TemporalPlainYearMonth,
- TemporalPlainMonthDay,
- TemporalDuration,
- TemporalUnspecified,
- Integer,
- Float,
- Number,
- Boolean,
- Null,
-}
+pub(crate) struct IsFilter(pub(crate) DuperType);
impl DuperFilter for IsFilter {
- fn filter<'v>(&self, value: &'v DuperValue<'v>) -> bool {
- match (&self, &value.inner) {
- (IsFilter::Object, DuperInner::Object(_)) => true,
- (IsFilter::Array, DuperInner::Array(_)) => true,
- (IsFilter::Tuple, DuperInner::Tuple(_)) => true,
- (IsFilter::String, DuperInner::String(_)) => true,
- (IsFilter::Bytes, DuperInner::Bytes(_)) => true,
- (IsFilter::TemporalInstant, DuperInner::Temporal(that)) => {
+ fn filter<'v>(&self, value: &DuperValue<'v>) -> bool {
+ match (&self.0, &value.inner) {
+ (DuperType::Object, DuperInner::Object(_)) => true,
+ (DuperType::Array, DuperInner::Array(_)) => true,
+ (DuperType::Tuple, DuperInner::Tuple(_)) => true,
+ (DuperType::String, DuperInner::String(_)) => true,
+ (DuperType::Bytes, DuperInner::Bytes(_)) => true,
+ (DuperType::TemporalInstant, DuperInner::Temporal(that)) => {
Instant::from_str(that.as_ref()).is_ok()
}
- (IsFilter::TemporalZonedDateTime, DuperInner::Temporal(that)) => {
+ (DuperType::TemporalZonedDateTime, DuperInner::Temporal(that)) => {
ZonedDateTime::from_utf8(
that.as_ref().as_bytes(),
Disambiguation::Compatible,
@@ -537,30 +531,30 @@ impl DuperFilter for IsFilter {
)
.is_ok()
}
- (IsFilter::TemporalPlainDate, DuperInner::Temporal(that)) => {
+ (DuperType::TemporalPlainDate, DuperInner::Temporal(that)) => {
PlainDate::from_str(that.as_ref()).is_ok()
}
- (IsFilter::TemporalPlainTime, DuperInner::Temporal(that)) => {
+ (DuperType::TemporalPlainTime, DuperInner::Temporal(that)) => {
PlainTime::from_str(that.as_ref()).is_ok()
}
- (IsFilter::TemporalPlainDateTime, DuperInner::Temporal(that)) => {
+ (DuperType::TemporalPlainDateTime, DuperInner::Temporal(that)) => {
PlainDateTime::from_str(that.as_ref()).is_ok()
}
- (IsFilter::TemporalPlainYearMonth, DuperInner::Temporal(that)) => {
+ (DuperType::TemporalPlainYearMonth, DuperInner::Temporal(that)) => {
PlainYearMonth::from_str(that.as_ref()).is_ok()
}
- (IsFilter::TemporalPlainMonthDay, DuperInner::Temporal(that)) => {
+ (DuperType::TemporalPlainMonthDay, DuperInner::Temporal(that)) => {
PlainMonthDay::from_str(that.as_ref()).is_ok()
}
- (IsFilter::TemporalDuration, DuperInner::Temporal(that)) => {
+ (DuperType::TemporalDuration, DuperInner::Temporal(that)) => {
Duration::from_str(that.as_ref()).is_ok()
}
- (IsFilter::TemporalUnspecified, DuperInner::Temporal(_)) => true,
- (IsFilter::Integer, DuperInner::Integer(_)) => true,
- (IsFilter::Float, DuperInner::Float(_)) => true,
- (IsFilter::Number, DuperInner::Integer(_) | DuperInner::Float(_)) => true,
- (IsFilter::Boolean, DuperInner::Boolean(_)) => true,
- (IsFilter::Null, DuperInner::Null) => true,
+ (DuperType::TemporalUnspecified, DuperInner::Temporal(_)) => true,
+ (DuperType::Integer, DuperInner::Integer(_)) => true,
+ (DuperType::Float, DuperInner::Float(_)) => true,
+ (DuperType::Number, DuperInner::Integer(_) | DuperInner::Float(_)) => true,
+ (DuperType::Boolean, DuperInner::Boolean(_)) => true,
+ (DuperType::Null, DuperInner::Null) => true,
_ => false,
}
}
@@ -569,7 +563,7 @@ impl DuperFilter for IsFilter {
pub(crate) struct RegexFilter(pub(crate) regex::bytes::Regex);
impl DuperFilter for RegexFilter {
- fn filter<'v>(&self, value: &'v DuperValue<'v>) -> bool {
+ fn filter<'v>(&self, value: &DuperValue<'v>) -> bool {
match &value.inner {
DuperInner::String(string) => self.0.find(string.as_ref().as_bytes()).is_some(),
DuperInner::Bytes(bytes) => self.0.find(bytes.as_ref()).is_some(),
@@ -582,7 +576,7 @@ impl DuperFilter for RegexFilter {
pub(crate) struct RegexIdentifierFilter(pub(crate) regex::Regex);
impl DuperFilter for RegexIdentifierFilter {
- fn filter<'v>(&self, value: &'v DuperValue<'v>) -> bool {
+ fn filter<'v>(&self, value: &DuperValue<'v>) -> bool {
value
.identifier
.as_ref()
@@ -593,7 +587,7 @@ impl DuperFilter for RegexIdentifierFilter {
pub(crate) struct IsTruthyFilter;
impl DuperFilter for IsTruthyFilter {
- fn filter<'v>(&self, value: &'v DuperValue<'_>) -> bool {
+ fn filter<'v>(&self, value: &DuperValue<'_>) -> bool {
match &value.inner {
DuperInner::Object(object) => !object.is_empty(),
DuperInner::Array(array) => !array.is_empty(),
diff --git a/duperq/src/lib.rs b/duperq/src/lib.rs
index c1d418c..96bb0c4 100644
--- a/duperq/src/lib.rs
+++ b/duperq/src/lib.rs
@@ -3,5 +3,6 @@ mod filter;
mod formatter;
mod processor;
mod query;
+mod types;
pub use query::query;
diff --git a/duperq/src/main.rs b/duperq/src/main.rs
index a128e1c..c1954c5 100644
--- a/duperq/src/main.rs
+++ b/duperq/src/main.rs
@@ -1,3 +1,5 @@
+use std::{fmt::Display, path::PathBuf};
+
use chumsky::Parser as _;
use clap::Parser;
use duper::DuperParser;
@@ -23,6 +25,32 @@ struct Cli {
disable_stderr: bool,
}
+enum FileReadError {
+ Glob(glob::GlobError),
+ Io(std::io::Error),
+}
+
+impl From for FileReadError {
+ fn from(value: glob::GlobError) -> Self {
+ Self::Glob(value)
+ }
+}
+
+impl From for FileReadError {
+ fn from(value: std::io::Error) -> Self {
+ Self::Io(value)
+ }
+}
+
+impl Display for FileReadError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ FileReadError::Glob(error) => error.fmt(f),
+ FileReadError::Io(error) => error.fmt(f),
+ }
+ }
+}
+
fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
@@ -42,7 +70,7 @@ fn main() -> anyhow::Result<()> {
let mut sink = pipeline_fns
.into_iter()
.rfold(output, |mut output, pipeline_fn| {
- let (sender, receiver) = smol::channel::bounded(1024);
+ let (sender, receiver) = smol::channel::bounded(128);
tasks.push(executor.spawn(async move {
while let Ok(value) = receiver.recv().await {
output.process(value).await;
@@ -57,41 +85,90 @@ fn main() -> anyhow::Result<()> {
None
};
- tasks.push(executor.spawn(async move {
- if let Some(glob) = glob {
- // Read from files
+ if let Some(glob) = glob {
+ let (pathbuf_sender, pathbuf_receiver) =
+ smol::channel::bounded::>(128);
+ let (file_sender, file_receiver) =
+ smol::channel::bounded::>(128);
+ // Iterate over glob
+ tasks.push(executor.spawn(async move {
for entry in glob {
match entry {
- Ok(path) => match smol::fs::read_to_string(&path).await {
- Ok(input) => match DuperParser::parse_duper_trunk(&input) {
- Ok(trunk) => sink.process(trunk.static_clone()).await,
- Err(errors) => {
- if !cli.disable_stderr {
- if let Ok(parse_error) = DuperParser::prettify_error(
- &input,
- &errors,
- Some(path.to_string_lossy().as_ref()),
- ) {
- let _ = stderr.write_all(parse_error.as_bytes()).await;
+ Err(_) if cli.disable_stderr => continue,
+ Ok(_) | Err(_) => {
+ if pathbuf_sender
+ .send(entry.map_err(|error| error.into()))
+ .await
+ .is_err()
+ {
+ break;
+ }
+ }
+ }
+ }
+ }));
+ // Read files
+ tasks.extend((0..num_cpus::get()).map(|_| {
+ let file_sender = file_sender.clone();
+ let pathbuf_receiver = pathbuf_receiver.clone();
+ executor.spawn(async move {
+ while let Ok(msg) = pathbuf_receiver.recv().await {
+ match msg {
+ Ok(pathbuf) => {
+ let string = smol::fs::read_to_string(&pathbuf).await;
+ match string {
+ Err(_) if cli.disable_stderr => continue,
+ Ok(_) | Err(_) => {
+ if file_sender
+ .send(
+ string
+ .map(move |string| (pathbuf, string))
+ .map_err(|error| error.into()),
+ )
+ .await
+ .is_err()
+ {
+ break;
}
}
}
- },
+ }
Err(error) => {
+ if file_sender.send(Err(error)).await.is_err() {
+ break;
+ }
+ }
+ }
+ }
+ })
+ }));
+ // Parse and process values
+ tasks.push(executor.spawn(async move {
+ while let Ok(input) = file_receiver.recv().await {
+ match input {
+ Ok((pathbuf, string)) => match DuperParser::parse_duper_trunk(&string) {
+ Ok(trunk) => sink.process(trunk.static_clone()).await,
+ Err(errors) => {
if !cli.disable_stderr {
- let _ = stderr.write_all(error.to_string().as_bytes()).await;
+ if let Ok(parse_error) = DuperParser::prettify_error(
+ &string,
+ &errors,
+ Some(pathbuf.to_string_lossy().as_ref()),
+ ) {
+ let _ = stderr.write_all(parse_error.as_bytes()).await;
+ }
}
}
},
Err(error) => {
- if !cli.disable_stderr {
- let _ = stderr.write_all(error.to_string().as_bytes()).await;
- }
+ let _ = stderr.write_all(error.to_string().as_bytes()).await;
}
}
}
- } else {
- // Read from stdin
+ }));
+ } else {
+ // Read from stdin
+ tasks.push(executor.spawn(async move {
let stdin = BufReader::new(Unblock::new(std::io::stdin()));
let mut lines = stdin.lines();
while let Some(Ok(line)) = lines.next().await {
@@ -108,8 +185,8 @@ fn main() -> anyhow::Result<()> {
}
}
}
- }
- }));
+ }));
+ }
smol::block_on(executor.run(async move { futures::future::join_all(tasks).await }));
diff --git a/duperq/src/processor.rs b/duperq/src/processor.rs
index 3da85ad..020c0b9 100644
--- a/duperq/src/processor.rs
+++ b/duperq/src/processor.rs
@@ -99,11 +99,11 @@ impl Processor for SkipProcessor {
pub(crate) struct OutputProcessor {
stdout: Unblock,
- printer: Box) -> String>,
+ printer: Box) -> Vec>,
}
impl OutputProcessor {
- pub(crate) fn new(printer: Box) -> String>) -> Self {
+ pub(crate) fn new(printer: Box) -> Vec>) -> Self {
Self {
stdout: Unblock::new(std::io::stdout()),
printer,
@@ -115,7 +115,7 @@ impl OutputProcessor {
impl Processor for OutputProcessor {
async fn process(&mut self, value: DuperValue<'static>) {
self.stdout
- .write_all((self.printer)(value).as_bytes())
+ .write_all((self.printer)(value).as_ref())
.await
.expect("stdout was closed");
self.stdout
diff --git a/duperq/src/query.rs b/duperq/src/query.rs
index bac647d..3cae284 100644
--- a/duperq/src/query.rs
+++ b/duperq/src/query.rs
@@ -1,6 +1,6 @@
use chumsky::prelude::*;
use duper::{
- DuperInner, DuperValue, PrettyPrinter, Serializer,
+ Ansi, DuperInner, DuperValue, PrettyPrinter, Serializer,
escape::unescape_str,
parser::{identified_value, identifier, integer, object_key},
};
@@ -12,12 +12,13 @@ use crate::{
IndexAccessor, RangeIndexAccessor, ReverseIndexAccessor,
},
filter::{
- AccessorFilter, AndFilter, CmpValue, DuperFilter, EqFilter, EqValue, GeFilter, GtFilter,
- IsFilter, IsTruthyFilter, LeFilter, LtFilter, NeFilter, NotFilter, OrFilter, RegexFilter,
- RegexIdentifierFilter, TrueFilter, TryFromDuperValueError,
+ AccessorFilter, AndFilter, CastFilter, CmpValue, DuperFilter, EqFilter, EqValue, GeFilter,
+ GtFilter, IsFilter, IsTruthyFilter, LeFilter, LtFilter, NeFilter, NotFilter, OrFilter,
+ RegexFilter, RegexIdentifierFilter, TrueFilter, TryFromDuperValueError,
},
formatter::{Formatter, FormatterAtom},
processor::{FilterProcessor, OutputProcessor, Processor, SkipProcessor, TakeProcessor},
+ types::DuperType,
};
pub(crate) type CreateProcessorFn =
@@ -65,17 +66,28 @@ pub fn query<'a>()
.then(
just('|')
.padded()
- .ignore_then(just("format").padded().ignore_then(fmt().padded()).or(
+ .ignore_then(choice((
+ just("format").padded().ignore_then(fmt().padded()),
+ just("ansi").padded().map(|_| {
+ let mut ansi = Ansi::default();
+ OutputProcessor::new(Box::new(move |value| {
+ ansi.to_ansi(value).unwrap_or_default()
+ }))
+ }),
just("pretty-print").padded().map(|_| {
let mut pretty_printer = PrettyPrinter::default();
- OutputProcessor::new(Box::new(move |value| pretty_printer.pretty_print(value)))
+ OutputProcessor::new(Box::new(move |value| {
+ pretty_printer.pretty_print(value).into_bytes()
+ }))
}),
- ))
+ )))
.or_not()
.map(|processor| {
Box::new(processor.unwrap_or_else(|| {
let mut serializer = Serializer::default();
- OutputProcessor::new(Box::new(move |value| serializer.serialize(value)))
+ OutputProcessor::new(Box::new(move |value| {
+ serializer.serialize(value).into_bytes()
+ }))
})) as Box
}),
)
@@ -84,8 +96,9 @@ pub fn query<'a>()
fn filter<'a>() -> impl Parser<'a, &'a str, Box, extra::Err>> + Clone
{
recursive(|filter| {
- let atom = leaf_filter(accessor())
- .or(filter.delimited_by(just('('), just(')')))
+ let atom = filter
+ .delimited_by(just('('), just(')'))
+ .or(leaf_filter(accessor()))
.padded();
let unary = just('!')
@@ -126,8 +139,8 @@ fn filter<'a>() -> impl Parser<'a, &'a str, Box, extra::Err()
-> impl Parser<'a, &'a str, Box, extra::Err>> + Clone {
recursive(|accessor| {
- let access = just('.').or_not().ignore_then(choice((
- object_key().padded().map(|key: duper::DuperKey<'a>| {
+ let access = just('.').ignore_then(choice((
+ object_key().map(|key: duper::DuperKey<'a>| {
Box::new(FieldAccessor(key.as_ref().into())) as Box
}),
integer()
@@ -137,7 +150,6 @@ fn accessor<'a>()
.then(just('=').or_not())
.then(integer().or_not().padded())
.delimited_by(just('['), just(']'))
- .padded()
.try_map(|((start, end_inclusive), end), span| match (start, end) {
(Some(start), _) if start < 0 => {
Err(Rich::custom(span, "range start must be positive"))
@@ -174,7 +186,6 @@ fn accessor<'a>()
integer()
.padded()
.delimited_by(just('['), just(']'))
- .padded()
.map(|int| {
if int < 0 {
Box::new(ReverseIndexAccessor(int.unsigned_abs() as usize))
@@ -186,11 +197,9 @@ fn accessor<'a>()
leaf_filter(accessor)
.padded()
.delimited_by(just('['), just(']'))
- .padded()
.map(|filter| Box::new(FilterAccessor(filter)) as Box),
text::whitespace()
.delimited_by(just('['), just(']'))
- .padded()
.map(|_| Box::new(AnyAccessor) as Box),
)));
@@ -207,6 +216,31 @@ fn accessor<'a>()
fn leaf_filter<'a>(
accessor: impl Parser<'a, &'a str, Box, extra::Err>> + Clone,
) -> impl Parser<'a, &'a str, Box, extra::Err>> + Clone {
+ type ConsumeAccessor = Box) -> Box>;
+
+ let cast_accessor = just("cast")
+ .padded()
+ .ignore_then(
+ accessor
+ .clone()
+ .then_ignore(just(','))
+ .then(duper_type().padded())
+ .map(|(accessor, typ)| {
+ Box::new(|filter: Box| {
+ Box::new(AccessorFilter {
+ filter: Box::new(CastFilter { filter, typ }),
+ accessor,
+ }) as Box
+ }) as ConsumeAccessor
+ })
+ .delimited_by(just('('), just(')')),
+ )
+ .or(accessor.map(|accessor| {
+ Box::new(|filter: Box| {
+ Box::new(AccessorFilter { filter, accessor }) as Box
+ }) as ConsumeAccessor
+ }));
+
let eq_op = just("==").ignored().or(just('=').ignored()).padded();
let ne_op = just("!=").ignored().or(just("<>").ignored()).padded();
let lt_op = just("<").ignored().padded();
@@ -217,7 +251,7 @@ fn leaf_filter<'a>(
let is_op = just("is").ignored().padded();
let len_filter = just("len")
- .ignore_then(accessor.clone().delimited_by(just('('), just(')')))
+ .ignore_then(cast_accessor.clone().delimited_by(just('('), just(')')))
.then(choice((
eq_op
.clone()
@@ -306,7 +340,7 @@ fn leaf_filter<'a>(
)));
let identifier_filter = just("identifier")
- .ignore_then(accessor.clone().delimited_by(just('('), just(')')))
+ .ignore_then(cast_accessor.clone().delimited_by(just('('), just(')')))
.then(choice((
eq_op
.clone()
@@ -345,14 +379,14 @@ fn leaf_filter<'a>(
)));
let exists_filter = just("exists")
- .ignore_then(accessor.clone().delimited_by(just('('), just(')')))
+ .ignore_then(cast_accessor.clone().delimited_by(just('('), just(')')))
.map(|accessor| (accessor, Box::new(TrueFilter) as Box));
choice((
len_filter,
identifier_filter,
exists_filter,
- accessor.clone().then(choice((
+ cast_accessor.clone().then(choice((
eq_op
.ignore_then(identified_value().padded())
.try_map(|value, span| {
@@ -407,40 +441,36 @@ fn leaf_filter<'a>(
)),
}),
is_op
- .ignore_then(
- choice((
- just("Object").to(IsFilter::Object),
- just("Array").to(IsFilter::Array),
- just("Tuple").to(IsFilter::Tuple),
- just("String").to(IsFilter::String),
- just("Bytes").to(IsFilter::Bytes),
- just("Instant").to(IsFilter::TemporalInstant),
- just("ZonedDateTime").to(IsFilter::TemporalZonedDateTime),
- just("PlainDate").to(IsFilter::TemporalPlainDate),
- just("PlainTime").to(IsFilter::TemporalPlainTime),
- just("PlainDateTime").to(IsFilter::TemporalPlainDateTime),
- just("PlainYearMonth").to(IsFilter::TemporalPlainYearMonth),
- just("PlainMonthDay").to(IsFilter::TemporalPlainMonthDay),
- just("Duration").to(IsFilter::TemporalDuration),
- just("Temporal").to(IsFilter::TemporalUnspecified),
- just("Integer").to(IsFilter::Integer),
- just("Float").to(IsFilter::Float),
- just("Number").to(IsFilter::Number),
- just("Boolean").to(IsFilter::Boolean),
- just("Null").to(IsFilter::Null),
- ))
- .padded(),
- )
- .map(|value| Box::new(value) as Box),
+ .ignore_then(duper_type().padded())
+ .map(|typ| Box::new(IsFilter(typ)) as Box),
))),
))
- .map(|(accessor, filter)| Box::new(AccessorFilter { accessor, filter }) as Box)
- .or(accessor.map(|accessor| {
- Box::new(AccessorFilter {
- accessor,
- filter: Box::new(IsTruthyFilter),
- }) as Box
- }))
+ .map(|(accessor, filter)| (accessor)(filter))
+ .or(cast_accessor.map(|accessor| (accessor)(Box::new(IsTruthyFilter))))
+}
+
+fn duper_type<'a>() -> impl Parser<'a, &'a str, DuperType, extra::Err>> + Clone {
+ choice((
+ just("Object").to(DuperType::Object),
+ just("Array").to(DuperType::Array),
+ just("Tuple").to(DuperType::Tuple),
+ just("String").to(DuperType::String),
+ just("Bytes").to(DuperType::Bytes),
+ just("Instant").to(DuperType::TemporalInstant),
+ just("ZonedDateTime").to(DuperType::TemporalZonedDateTime),
+ just("PlainDate").to(DuperType::TemporalPlainDate),
+ just("PlainTime").to(DuperType::TemporalPlainTime),
+ just("PlainDateTime").to(DuperType::TemporalPlainDateTime),
+ just("PlainYearMonth").to(DuperType::TemporalPlainYearMonth),
+ just("PlainMonthDay").to(DuperType::TemporalPlainMonthDay),
+ just("Duration").to(DuperType::TemporalDuration),
+ just("Temporal").to(DuperType::TemporalUnspecified),
+ just("Integer").to(DuperType::Integer),
+ just("Float").to(DuperType::Float),
+ just("Number").to(DuperType::Number),
+ just("Boolean").to(DuperType::Boolean),
+ just("Null").to(DuperType::Null),
+ ))
}
fn fmt<'a>() -> impl Parser<'a, &'a str, OutputProcessor, extra::Err>> {
@@ -458,7 +488,7 @@ fn fmt<'a>() -> impl Parser<'a, &'a str, OutputProcessor, extra::Err(
+ &self,
+ value: &'value DuperValue<'value>,
+ ) -> Option> {
+ match (self, &value.inner) {
+ // Trivial casts
+ (DuperType::Object, DuperInner::Object(_)) => Some(value.clone()),
+ (DuperType::Array, DuperInner::Array(_)) => Some(value.clone()),
+ (DuperType::Tuple, DuperInner::Tuple(_)) => Some(value.clone()),
+ (DuperType::String, DuperInner::String(_)) => Some(value.clone()),
+ (DuperType::Bytes, DuperInner::Bytes(_)) => Some(value.clone()),
+ (
+ DuperType::TemporalInstant
+ | DuperType::TemporalZonedDateTime
+ | DuperType::TemporalPlainDate
+ | DuperType::TemporalPlainTime
+ | DuperType::TemporalPlainDateTime
+ | DuperType::TemporalPlainYearMonth
+ | DuperType::TemporalPlainMonthDay
+ | DuperType::TemporalDuration
+ | DuperType::TemporalUnspecified,
+ DuperInner::Temporal(_),
+ ) => Some(value.clone()),
+ (DuperType::Integer, DuperInner::Integer(_)) => Some(value.clone()),
+ (DuperType::Float, DuperInner::Float(_)) => Some(value.clone()),
+ (DuperType::Number, DuperInner::Integer(_) | DuperInner::Float(_)) => {
+ Some(value.clone())
+ }
+ (DuperType::Boolean, DuperInner::Boolean(_)) => Some(value.clone()),
+ (DuperType::Null, DuperInner::Null) => Some(value.clone()),
+
+ // Non-trivial casts
+ (DuperType::Array, DuperInner::Tuple(tuple)) => Some(DuperValue {
+ identifier: value.identifier.clone(),
+ inner: DuperInner::Array(DuperArray::from(
+ tuple.iter().cloned().collect::>(),
+ )),
+ }),
+ (DuperType::Tuple, DuperInner::Array(array)) => Some(DuperValue {
+ identifier: value.identifier.clone(),
+ inner: DuperInner::Tuple(DuperTuple::from(
+ array.iter().cloned().collect::>(),
+ )),
+ }),
+ (DuperType::String, DuperInner::Bytes(bytes)) => str::from_utf8(bytes.as_ref())
+ .ok()
+ .map(|string| DuperValue {
+ identifier: value.identifier.clone(),
+ inner: DuperInner::String(DuperString::from(Cow::Borrowed(string))),
+ }),
+ (DuperType::String, DuperInner::Temporal(temporal)) => Some(DuperValue {
+ identifier: value.identifier.clone(),
+ inner: DuperInner::String(DuperString::from(Cow::Borrowed(temporal.as_ref()))),
+ }),
+ (DuperType::Bytes, DuperInner::String(string)) => Some(DuperValue {
+ identifier: value.identifier.clone(),
+ inner: DuperInner::Bytes(DuperBytes::from(Cow::Borrowed(
+ string.as_ref().as_bytes(),
+ ))),
+ }),
+ (DuperType::Bytes, DuperInner::Temporal(temporal)) => Some(DuperValue {
+ identifier: value.identifier.clone(),
+ inner: DuperInner::Bytes(DuperBytes::from(Cow::Borrowed(
+ temporal.as_ref().as_bytes(),
+ ))),
+ }),
+ (DuperType::TemporalInstant, DuperInner::String(string)) => {
+ DuperTemporal::try_instant_from(Cow::Borrowed(string.as_ref()))
+ .ok()
+ .map(|temporal| DuperValue {
+ identifier: Some(
+ DuperIdentifier::try_from("Instant").expect("valid identifier"),
+ ),
+ inner: DuperInner::Temporal(temporal),
+ })
+ }
+ (DuperType::TemporalInstant, DuperInner::Bytes(bytes)) => {
+ str::from_utf8(bytes.as_ref())
+ .ok()
+ .and_then(|string| {
+ DuperTemporal::try_instant_from(Cow::Borrowed(string.as_ref())).ok()
+ })
+ .map(|temporal| DuperValue {
+ identifier: Some(
+ DuperIdentifier::try_from("Instant").expect("valid identifier"),
+ ),
+ inner: DuperInner::Temporal(temporal),
+ })
+ }
+ (DuperType::TemporalZonedDateTime, DuperInner::String(string)) => {
+ DuperTemporal::try_zoned_date_time_from(Cow::Borrowed(string.as_ref()))
+ .ok()
+ .map(|temporal| DuperValue {
+ identifier: Some(
+ DuperIdentifier::try_from("ZonedDateTime").expect("valid identifier"),
+ ),
+ inner: DuperInner::Temporal(temporal),
+ })
+ }
+ (DuperType::TemporalZonedDateTime, DuperInner::Bytes(bytes)) => {
+ str::from_utf8(bytes.as_ref())
+ .ok()
+ .and_then(|string| {
+ DuperTemporal::try_zoned_date_time_from(Cow::Borrowed(string.as_ref())).ok()
+ })
+ .map(|temporal| DuperValue {
+ identifier: Some(
+ DuperIdentifier::try_from("ZonedDateTime").expect("valid identifier"),
+ ),
+ inner: DuperInner::Temporal(temporal),
+ })
+ }
+ (DuperType::TemporalPlainDate, DuperInner::String(string)) => {
+ DuperTemporal::try_plain_date_from(Cow::Borrowed(string.as_ref()))
+ .ok()
+ .map(|temporal| DuperValue {
+ identifier: Some(
+ DuperIdentifier::try_from("PlainDate").expect("valid identifier"),
+ ),
+ inner: DuperInner::Temporal(temporal),
+ })
+ }
+ (DuperType::TemporalPlainDate, DuperInner::Bytes(bytes)) => {
+ str::from_utf8(bytes.as_ref())
+ .ok()
+ .and_then(|string| {
+ DuperTemporal::try_plain_date_from(Cow::Borrowed(string.as_ref())).ok()
+ })
+ .map(|temporal| DuperValue {
+ identifier: Some(
+ DuperIdentifier::try_from("PlainDate").expect("valid identifier"),
+ ),
+ inner: DuperInner::Temporal(temporal),
+ })
+ }
+ (DuperType::TemporalPlainTime, DuperInner::String(string)) => {
+ DuperTemporal::try_plain_time_from(Cow::Borrowed(string.as_ref()))
+ .ok()
+ .map(|temporal| DuperValue {
+ identifier: Some(
+ DuperIdentifier::try_from("PlainTime").expect("valid identifier"),
+ ),
+ inner: DuperInner::Temporal(temporal),
+ })
+ }
+ (DuperType::TemporalPlainTime, DuperInner::Bytes(bytes)) => {
+ str::from_utf8(bytes.as_ref())
+ .ok()
+ .and_then(|string| {
+ DuperTemporal::try_plain_time_from(Cow::Borrowed(string.as_ref())).ok()
+ })
+ .map(|temporal| DuperValue {
+ identifier: Some(
+ DuperIdentifier::try_from("PlainTime").expect("valid identifier"),
+ ),
+ inner: DuperInner::Temporal(temporal),
+ })
+ }
+ (DuperType::TemporalPlainDateTime, DuperInner::String(string)) => {
+ DuperTemporal::try_plain_date_time_from(Cow::Borrowed(string.as_ref()))
+ .ok()
+ .map(|temporal| DuperValue {
+ identifier: Some(
+ DuperIdentifier::try_from("PlainDateTime").expect("valid identifier"),
+ ),
+ inner: DuperInner::Temporal(temporal),
+ })
+ }
+ (DuperType::TemporalPlainDateTime, DuperInner::Bytes(bytes)) => {
+ str::from_utf8(bytes.as_ref())
+ .ok()
+ .and_then(|string| {
+ DuperTemporal::try_plain_date_time_from(Cow::Borrowed(string.as_ref())).ok()
+ })
+ .map(|temporal| DuperValue {
+ identifier: Some(
+ DuperIdentifier::try_from("PlainDateTime").expect("valid identifier"),
+ ),
+ inner: DuperInner::Temporal(temporal),
+ })
+ }
+ (DuperType::TemporalPlainYearMonth, DuperInner::String(string)) => {
+ DuperTemporal::try_plain_year_month_from(Cow::Borrowed(string.as_ref()))
+ .ok()
+ .map(|temporal| DuperValue {
+ identifier: Some(
+ DuperIdentifier::try_from("PlainYearMonth").expect("valid identifier"),
+ ),
+ inner: DuperInner::Temporal(temporal),
+ })
+ }
+ (DuperType::TemporalPlainYearMonth, DuperInner::Bytes(bytes)) => {
+ str::from_utf8(bytes.as_ref())
+ .ok()
+ .and_then(|string| {
+ DuperTemporal::try_plain_year_month_from(Cow::Borrowed(string.as_ref()))
+ .ok()
+ })
+ .map(|temporal| DuperValue {
+ identifier: Some(
+ DuperIdentifier::try_from("PlainYearMonth").expect("valid identifier"),
+ ),
+ inner: DuperInner::Temporal(temporal),
+ })
+ }
+ (DuperType::TemporalPlainMonthDay, DuperInner::String(string)) => {
+ DuperTemporal::try_plain_month_day_from(Cow::Borrowed(string.as_ref()))
+ .ok()
+ .map(|temporal| DuperValue {
+ identifier: Some(
+ DuperIdentifier::try_from("PlainMonthDay").expect("valid identifier"),
+ ),
+ inner: DuperInner::Temporal(temporal),
+ })
+ }
+ (DuperType::TemporalPlainMonthDay, DuperInner::Bytes(bytes)) => {
+ str::from_utf8(bytes.as_ref())
+ .ok()
+ .and_then(|string| {
+ DuperTemporal::try_plain_month_day_from(Cow::Borrowed(string.as_ref())).ok()
+ })
+ .map(|temporal| DuperValue {
+ identifier: Some(
+ DuperIdentifier::try_from("PlainMonthDay").expect("valid identifier"),
+ ),
+ inner: DuperInner::Temporal(temporal),
+ })
+ }
+ (DuperType::TemporalDuration, DuperInner::String(string)) => {
+ DuperTemporal::try_duration_from(Cow::Borrowed(string.as_ref()))
+ .ok()
+ .map(|temporal| DuperValue {
+ identifier: Some(
+ DuperIdentifier::try_from("Duration").expect("valid identifier"),
+ ),
+ inner: DuperInner::Temporal(temporal),
+ })
+ }
+ (DuperType::TemporalDuration, DuperInner::Bytes(bytes)) => {
+ str::from_utf8(bytes.as_ref())
+ .ok()
+ .and_then(|string| {
+ DuperTemporal::try_duration_from(Cow::Borrowed(string.as_ref())).ok()
+ })
+ .map(|temporal| DuperValue {
+ identifier: Some(
+ DuperIdentifier::try_from("Duration").expect("valid identifier"),
+ ),
+ inner: DuperInner::Temporal(temporal),
+ })
+ }
+ (DuperType::TemporalUnspecified, DuperInner::String(string)) => {
+ DuperTemporal::try_unspecified_from(Cow::Borrowed(string.as_ref()))
+ .ok()
+ .map(|temporal| DuperValue {
+ identifier: None,
+ inner: DuperInner::Temporal(temporal),
+ })
+ }
+ (DuperType::TemporalUnspecified, DuperInner::Bytes(bytes)) => {
+ str::from_utf8(bytes.as_ref())
+ .ok()
+ .and_then(|string| {
+ DuperTemporal::try_unspecified_from(Cow::Borrowed(string.as_ref())).ok()
+ })
+ .map(|temporal| DuperValue {
+ identifier: None,
+ inner: DuperInner::Temporal(temporal),
+ })
+ }
+ (DuperType::Integer, DuperInner::Float(float)) => Some(DuperValue {
+ identifier: value.identifier.clone(),
+ inner: DuperInner::Integer(*float as i64),
+ }),
+ (DuperType::Float, DuperInner::Integer(integer)) => Some(DuperValue {
+ identifier: value.identifier.clone(),
+ inner: DuperInner::Float(*integer as f64),
+ }),
+ (DuperType::Boolean, _) => Some(DuperValue {
+ identifier: value.identifier.clone(),
+ inner: DuperInner::Boolean(IsTruthyFilter.filter(value)),
+ }),
+ (DuperType::Null, _) => Some(DuperValue {
+ identifier: value.identifier.clone(),
+ inner: DuperInner::Null,
+ }),
+
+ // Unsupported casts
+ _ => None,
+ }
+ }
+}
--
2.51.2