pub enum Grain {
Year,
Month,
Day,
Initial(usize),
}Expand description
A coarsening: how finely a value is cut into groups.
Not a date vocabulary. A grain is any many-to-one function from a value to a
group key, and the calendar grains are one family of them — year is
“the first four characters, if they are a year”, and Initial
is “the first n characters” with no such condition. What makes something a
grain is the two properties below, not what it is about.
§Two properties, and what each one licenses
cut— value → key. This is allbyneeds, because grouping is a reading operation with no invariant to keep.chain— the coarser grains this one refines, coarsest first. This is whatnestneeds, and it is a strictly stronger requirement: nesting builds a hierarchy of index documents, so each level’s key must be determined by the finer level’s (2026-07-24→2026-07→2026,Ada→Ad→A). A coarsening with no such chain can group but cannot nest.
The second constraint is prov’s, not taste. nest files a record into the
spanning relation, which is single-parent, so a nest chain must also be
single-valued per document — see ViewSpec::nest_route, which returns
None rather than guessing which of a multi-valued field’s values a
document should be filed under.
§Adding a grain
The rule is the one crate::filter uses for predicates: a concrete lens
that cannot otherwise be said, not a shape that seems likely to be wanted.
initial earns its place as the A–Z index every list of names and places
eventually wants. A numeric bucket (ratings by tens) is the obvious next
one and is deliberately not here: nobody has asked for it, and it would
arrive with a problem the calendar grains do not have — its keys sort
lexically as 0, 10, 100, 20, so it needs group ordering to become
grain-aware, which is really the deferred sort: axis wearing a disguise.
Variants§
Year
2026 — the default, and what a lifetime of entries wants.
Month
2026-07.
Day
2026-07-25.
Initial(usize)
The first n characters, upper-cased — the A–Z index.
Upper-casing is a deliberate normalization rather than a faithful cut:
an alphabetical index that files ada apart from Ada is not an index.
It is the same kind of choice a date cut makes when it reports 2026
for a value that says 2026-07-24; a group key describes a bucket, not
a value that appears in the data.
Implementations§
Source§impl Grain
impl Grain
Sourcepub fn as_config_str(self) -> Option<&'static str>
pub fn as_config_str(self) -> Option<&'static str>
The config spelling, when this grain has a bare-word one.
None for a parameterized grain that is not at its default — write
to_value instead, which always round-trips.
Sourcepub fn from_config_str(text: &str) -> Option<Self>
pub fn from_config_str(text: &str) -> Option<Self>
Parse a bare-word config spelling. Unknown text is not silently
defaulted — a by: yearr that quietly grouped by year would look
applied and be wrong, which is the failure a config linter exists to
prevent.
Sourcepub fn parse(value: &Value) -> Option<Self>
pub fn parse(value: &Value) -> Option<Self>
Read a by:/nest: value: a bare word, or a one-key mapping naming a
parameterized grain ({ initial: 2 }).
A parameter of zero is rejected rather than clamped: { initial: 0 }
would put every document in one group called “”, which is a view that
has stopped being one.
Sourcepub fn to_value(self) -> Value
pub fn to_value(self) -> Value
The value this grain writes back as — a bare word where it has one, a one-key mapping otherwise.
Sourcepub fn chain(self) -> Vec<Grain>
pub fn chain(self) -> Vec<Grain>
The grains to nest through to reach self, coarsest first.
Filing at month grain means a year index and then a month index inside
it: a month index that is not inside its year is not where anyone looks
for it. The alphabetical case is the same shape — filing at initial 2
means an A index holding an Ad index.
Each step must be determined by the one after it, which is what makes the hierarchy well defined. That is why this is a property of the grain rather than something a caller can assemble: an arbitrary sequence of coarsenings is not a nest.
Sourcepub fn cut(self, value: &str) -> Option<String>
pub fn cut(self, value: &str) -> Option<String>
Cut value to this grain, or None if the value does not reach it.
The calendar grains validate rather than taking a blind prefix, which
is what keeps by: usable on a view whose field is only usually a date:
banana cut to a year would otherwise group under bana, a group key
that looks like data. A value this rejects falls to the ungrouped
bucket, where it is visible as something that did not sort.
Anything after the cut is ignored, so an RFC 3339 instant
(2026-07-24T07:32:00Z — what a machine-maintained updated field
carries) cuts exactly like the plain date it starts with.
Trait Implementations§
impl Copy for Grain
impl Eq for Grain
impl StructuralPartialEq for Grain
Auto Trait Implementations§
impl Freeze for Grain
impl RefUnwindSafe for Grain
impl Send for Grain
impl Sync for Grain
impl Unpin for Grain
impl UnsafeUnpin for Grain
impl UnwindSafe for Grain
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.