1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
//! Which of a request's filters and tab a table applies: the predicate they build, and the
//! ones it ignores, which the list's banner names and the export and a selection refuse.
use toasty::stmt::Expr;
use topcoat::context::Cx;
use super::{Table, TableState};
use crate::dom::ignored;
impl<M> Table<M> {
/// The names of the requested filters whose value [`Filter::admits`] refuses: values naming
/// records the user may not see, which [`Self::filter_expr`] and
/// [`Self::unapplied_filters`] treat as invalid.
///
/// # Errors
///
/// The database failing a filter's check.
pub(crate) async fn refused_filters(
&self,
cx: &topcoat::context::Cx,
state: &TableState,
) -> topcoat::Result<Vec<String>> {
let mut refused = Vec::new();
for f in &self.filters {
if let Some(value) = state.filters.get(f.name())
&& !f.internals().admits(cx, value).await?
{
refused.push(f.name().to_string());
}
}
Ok(refused)
}
/// Filter predicate for the current `TableState`, without the `refused` filters.
pub(crate) fn filter_expr(
&self,
cx: &Cx,
state: &TableState,
refused: &[String],
) -> Option<Expr<bool>>
where
M: toasty::schema::Model,
{
let mut exprs = Vec::new();
for f in &self.filters {
if let Some(v) = state.filters.get(f.name())
&& !refused.iter().any(|name| name == f.name())
&& let Some(e) = f.to_expr(cx, v)
{
exprs.push(e);
}
}
if exprs.is_empty() {
None
} else {
let mut iter = exprs.into_iter();
let first = iter.next().unwrap();
Some(iter.fold(first, |acc, e| acc.and(e)))
}
}
/// Requested filters that produce no predicate, each spelled as its own parameters
/// (`f.status=draft&f.status=review`), beside one entry for a `?tab=` naming no tab and one
/// for dropped parameters. A `refused`
/// filter reads as an invalid value, as one naming no record does, so the answer does not
/// tell a hidden record from a missing one. Each names why as a [`crate::dom::ignored`] kind,
/// which [`ignored_reason`] words.
pub(crate) fn unapplied_filters(
&self,
cx: &Cx,
state: &TableState,
refused: &[String],
) -> Vec<(String, &'static str)>
where
M: toasty::schema::Model,
{
let mut out = Vec::new();
for (key, value) in &state.filters {
match self.filters.iter().find(|f| f.name() == key) {
None => out.push((state.spell_filter(key, value), ignored::UNKNOWN)),
Some(f)
if refused.iter().any(|name| name == key)
|| (f.to_expr(cx, value).is_none() && !f.is_noop_value(value)) =>
{
out.push((state.spell_filter(key, value), ignored::INVALID))
}
Some(_) => {}
}
}
if let Some(tab) = self.unknown_tab(state) {
out.push((state.spell_tab(tab), ignored::UNKNOWN));
}
if state.filters_dropped {
out.push(("dropped filters".to_string(), ignored::DROPPED));
}
out.sort();
out
}
}
/// The wording of a [`crate::dom::ignored`] kind, which the list's banner and the export's
/// refusal show.
pub(crate) fn ignored_reason(kind: &str) -> crate::Text {
match kind {
ignored::UNKNOWN => crate::text!("tablo-filter-ignored-unknown"),
ignored::DROPPED => crate::text!(
"tablo-filter-ignored-dropped",
filters = super::state::MAX_FILTERS,
values = super::state::MAX_FILTER_VALUES,
bytes = super::state::MAX_FILTER_LEN,
),
_ => crate::text!("tablo-filter-ignored-invalid"),
}
}