Skip to main content

vitaminc_normalize/
terms.rs

1//! Equality terms as a sink, and encryption that produces them in the
2//! same walk.
3//!
4//! [`Terms`] is a [`Sink`] that produces one [`Term`] per scalar that has
5//! an equality domain. It runs beside encryption as `Tee(EncryptSink,
6//! Terms)`, so a caller holds every term before any cipher is called and,
7//! with an async cipher, before the first await. [`terms`] runs it alone,
8//! and then it refuses what encryption would refuse.
9//!
10//! The sink does the walking and the [`Normalize`] it is given does the
11//! encoding, so an index of another kind (an order index, whose bytes
12//! differ from an equality term's) collects its terms through the same
13//! sink with a normalizer of its own, through [`Terms::by`] and
14//! [`terms_by`].
15
16use core::future::Future;
17use core::marker::PhantomData;
18
19use vitaminc_aead::plaintext::{Error, Path, Step};
20use vitaminc_aead::seal::{seal_all, seal_all_async, AsyncCipher, Cipher};
21use vitaminc_aead::shape::{self, Locate, MapRules, SeqRules, Settle};
22use vitaminc_aead::{
23    Encrypt, EncryptError, EncryptSink, FixedType, IntoAad, Logical, MapSink, Node, Pending,
24    Physical, Refusal, SeqSink, Sink, SourceError, Tee,
25};
26
27use vitaminc_protected::OpaqueDebug;
28
29use crate::scalar::{normalize, NormalizeError, Normalized};
30
31/// How [`Terms`] turns one scalar into a term.
32///
33/// [`Equality`] is the normalizer this crate is about. An index of another
34/// kind supplies its own, with a term type of its own (an order index
35/// seals its term so only a scheme can read it) and the same outcome
36/// contract: a scalar whose kind has no term under this index (a boolean,
37/// or a null where the index gives nulls none) returns
38/// [`NormalizeError::NoDomain`], and [`Terms`] skips it
39/// when it has no logical type; a scalar whose logical type has no term
40/// returns the same error and stops the walk; any other error stops the
41/// walk too.
42///
43/// A normalizer is copied into every child sink of a walk, so it is
44/// `Copy`: it carries settings, such as the type an index declares, but
45/// no state that a walk changes.
46pub trait Normalize: Copy {
47    /// What one scalar becomes.
48    type Term;
49
50    /// The kind of index the terms are for, as a refusal names it:
51    /// `"equality"` for [`Equality`], so a scalar it cannot normalize is
52    /// reported as having no equality term.
53    const INDEX: &'static str;
54
55    /// The term for one scalar, or why it has none.
56    fn normalize(
57        &self,
58        ty: Option<Logical>,
59        value: Physical<'_>,
60    ) -> Result<Self::Term, NormalizeError>;
61}
62
63/// The equality normalizer: [`normalize`].
64#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
65pub struct Equality;
66
67impl Normalize for Equality {
68    type Term = Normalized;
69
70    const INDEX: &'static str = "equality";
71
72    fn normalize(
73        &self,
74        ty: Option<Logical>,
75        value: Physical<'_>,
76    ) -> Result<Normalized, NormalizeError> {
77        normalize(ty, value)
78    }
79}
80
81/// One term: where the scalar sits in the value, and what the normalizer
82/// made of it. An equality term, a [`Normalized`], unless [`Terms::by`]
83/// was given another normalizer.
84///
85/// `Debug` shows the path's shape, with its map keys masked as [`Step`]
86/// masks them, and masks the term.
87#[derive(OpaqueDebug)]
88pub struct Term<T = Normalized> {
89    /// The path from the root, empty for a scalar at the root.
90    #[non_sensitive]
91    pub path: Path,
92    /// The term itself: for equality, the frozen domain label and the
93    /// normalized bytes.
94    pub normalized: T,
95}
96
97/// A scalar [`Terms`] refused: why, and where in the value.
98///
99/// This is the type to downcast a refusal to. Its
100/// [`source`](core::error::Error::source) is the [`NormalizeError`], so a
101/// reporter that walks the error chain reaches the reason too.
102///
103/// `Display` shows only the reason, because the path can hold map keys and
104/// map keys can be user data. `Debug` shows the reason and the path's
105/// shape, with every key masked. Read the path with
106/// [`path`](TermError::path).
107#[derive(Clone, OpaqueDebug, thiserror::Error)]
108#[error("no {index} term: {error}")]
109pub struct TermError {
110    #[non_sensitive]
111    index: &'static str,
112    #[non_sensitive]
113    path: Path,
114    #[non_sensitive]
115    #[source]
116    error: NormalizeError,
117}
118
119impl TermError {
120    /// The kind of index the scalar has no term for: the normalizer's
121    /// [`Normalize::INDEX`].
122    pub fn index(&self) -> &'static str {
123        self.index
124    }
125
126    /// Where the refused scalar sits, from the root.
127    pub fn path(&self) -> &Path {
128        &self.path
129    }
130
131    /// Why the scalar has no term.
132    pub fn error(&self) -> NormalizeError {
133        self.error
134    }
135}
136
137/// What [`terms`] fails with: what [`Terms`] refuses, or the source's own
138/// failure.
139///
140/// A source reports through [`Error::source`] whichever sink it feeds, so
141/// every failure arrives as a [`SourceError`]. [`term`](TermsError::term)
142/// recovers a [`TermError`] from it, the same downcast a caller makes on
143/// [`EncryptError::Source`] when `Terms` runs beside encryption, and
144/// [`refusal`](TermsError::refusal) a value refused for its shape, which
145/// encryption reports as [`EncryptError::Shape`].
146/// [`source_error`](TermsError::source_error) is the error as it arrived.
147#[derive(Debug, thiserror::Error)]
148#[error(transparent)]
149pub struct TermsError(SourceError);
150
151impl TermsError {
152    /// The scalar [`Terms`] refused, or `None` if the source failed on its
153    /// own or the value was refused for its shape.
154    pub fn term(&self) -> Option<&TermError> {
155        self.0.inner().downcast_ref()
156    }
157
158    /// The value refused for its shape, as encryption would refuse it, or
159    /// `None` if it was refused for another reason.
160    pub fn refusal(&self) -> Option<&Refusal> {
161        self.0.inner().downcast_ref()
162    }
163
164    /// The failure as the walk reported it: what [`Terms`] refused, or the
165    /// source's own error.
166    pub fn source_error(&self) -> &SourceError {
167        &self.0
168    }
169}
170
171impl Error for TermsError {
172    fn source(error: SourceError) -> Self {
173        Self(error)
174    }
175}
176
177/// A sink that collects one [`Term`] per scalar with an equality domain,
178/// in walk order.
179///
180/// Generic over its error type so it adopts its [`Tee`] partner's. To
181/// collect terms without encrypting, call [`terms`], whose error type is
182/// [`TermsError`]. Generic too over the [`Normalize`] that encodes each
183/// scalar, [`Equality`] unless [`by`](Terms::by) is given another, so an
184/// index of another kind collects its terms through this same walk.
185///
186/// Under [`Equality`] every null has a term, typed or plain, at the
187/// null's path and under [`domain::NULL`](crate::domain::NULL), so an
188/// absent value is indexed like any other. A layer that must keep a SQL
189/// `NULL` for some column drops that term; its domain says it is a null.
190///
191/// A scalar whose *kind* has no domain (a boolean, an `undefined`, or a
192/// null under a normalizer that gives nulls none) produces no term and
193/// does not stop the walk: an unindexable value is an ordinary thing to
194/// find in a document, and the caller has the paths to see which fields
195/// were indexed. A scalar whose *logical
196/// type* has no domain ([`Logical::Unknown`], or a type this crate does
197/// not know) stops the walk instead: it came from a newer client and
198/// means something, and encrypting it with no term and no error would
199/// leave an index silently incomplete.
200///
201/// Passthrough subtrees travel in the clear and get no terms.
202///
203/// # Only what can be encrypted
204///
205/// A `Terms` refuses every value [`EncryptSink`] refuses, for the same
206/// [`Unencryptable`](vitaminc_aead::Unencryptable) reason and at the same path, so an index never holds
207/// terms for a value that cannot be stored. It keeps the
208/// [`shape`](vitaminc_aead::shape) rules through the code `EncryptSink`
209/// keeps them with: nothing deeper than the transport's depth limit, every
210/// scalar valid for its logical type, every passthrough subtree framable,
211/// no map key given twice, no child sink dropped without a value, and
212/// something sealed in every container with children. The last rule is
213/// about sealing, and this walk seals nothing, but a value that breaks it
214/// cannot be encrypted, so it is refused here too. A passthrough is framed
215/// as encryption frames it, to check that it can be, and the frame is
216/// dropped.
217///
218/// So [`terms`] accepts a value exactly when [`encrypt_with_terms`] does
219/// with a cipher that does not fail, and refuses it for the same reason at
220/// the same path. [`plan_with_terms`], and the encrypting functions built
221/// on it, pair `EncryptSink` with a `Terms` that leaves the shape rules to
222/// it, so they are kept once.
223///
224/// # Errors
225///
226/// The walk stops with `E::source(SourceError::new(error))`, where `error`
227/// is one of:
228///
229/// - a [`Refusal`]: the value breaks a shape rule, as above. A scalar not
230///   valid for its logical type is refused this way, as
231///   [`Unencryptable::InvalidValue`](vitaminc_aead::Unencryptable::InvalidValue), before it is normalized. Through
232///   [`terms`] it is [`TermsError::refusal`]. Beside `EncryptSink`, which
233///   refuses it first, it is [`EncryptError::Shape`].
234/// - a [`TermError`] holding the scalar's path and the
235///   [`NormalizeError`]: the scalar's logical type has no domain
236///   (`NoDomain`), or its text holds a code point unassigned in Unicode 16
237///   (`UnassignedCodePoint`). Beside [`EncryptSink`] that arrives as
238///   [`EncryptError::Source`], and
239///   `source.inner().downcast_ref::<TermError>()` recovers it. Alone,
240///   through [`terms`], it is [`TermsError::term`]. Either way the
241///   [`NormalizeError`] is on the `source()` chain.
242///
243/// # Cost
244///
245/// A path costs nothing until a term is pushed or a refusal is built. Each
246/// child sink holds a link to its container's place and its own index, or
247/// the position of its key in one buffer of the keys on the path being
248/// walked, which the whole walk shares and reuses. So the walk allocates
249/// that buffer, as long as the keys on the longest path, and otherwise
250/// only what each pushed term needs.
251///
252/// The shape rules cost what they cost encryption: a map keeps one copy
253/// of each of its keys until it ends, which is what checking for a repeat
254/// exactly takes, and a passthrough is framed. Beside `EncryptSink`,
255/// through [`plan_with_terms`], none of that is paid twice. A caller that
256/// tees `Terms::new` beside `EncryptSink` itself has both keep the rules.
257pub struct Terms<'t, E, N: Normalize = Equality> {
258    out: &'t mut Vec<Term<N::Term>>,
259    keys: Keys<'t>,
260    at: Place<'t>,
261    /// `None` beside an [`EncryptSink`] that keeps the rules itself.
262    rules: Option<NodeRules<'t>>,
263    by: N,
264    _error: PhantomData<E>,
265}
266
267/// The shape rules one node keeps: how deep it sits, and its hold on its
268/// container's rules, which it settles when it ends. The root has no
269/// container.
270struct NodeRules<'t> {
271    depth: usize,
272    settle: Option<Settle<'t>>,
273}
274
275impl NodeRules<'_> {
276    /// The node put a sealed node: a scalar, or a container.
277    fn sealed(self) {
278        if let Some(settle) = self.settle {
279            settle.sealed();
280        }
281    }
282}
283
284/// A container's shape rules, `R`, with the rules of the node it is.
285struct Held<'t, R> {
286    node: NodeRules<'t>,
287    rules: R,
288}
289
290/// The map keys on the path being walked, end to end. The root owns the
291/// buffer and every sink below it borrows it. A container truncates it
292/// back to where its own keys begin before each child, so it holds only
293/// the keys of the current path.
294enum Keys<'t> {
295    Own(String),
296    Lent(&'t mut String),
297}
298
299impl Keys<'_> {
300    fn get(&mut self) -> &mut String {
301        match self {
302            Keys::Own(keys) => keys,
303            Keys::Lent(keys) => keys,
304        }
305    }
306
307    fn as_str(&self) -> &str {
308        match self {
309            Keys::Own(keys) => keys,
310            Keys::Lent(keys) => keys,
311        }
312    }
313}
314
315/// Where a node sits: a link to its container's place and its own step.
316/// A key is named by its position in [`Keys`].
317#[derive(Clone, Copy)]
318enum Place<'t> {
319    Root,
320    Element {
321        parent: &'t Place<'t>,
322        index: usize,
323    },
324    Entry {
325        parent: &'t Place<'t>,
326        key: (usize, usize),
327    },
328}
329
330impl Place<'_> {
331    /// Append the steps from the root to this node, reading keys from
332    /// `keys`.
333    fn locate(&self, keys: &str, steps: &mut Vec<Step>) {
334        match *self {
335            Place::Root => {}
336            Place::Element { parent, index } => {
337                parent.locate(keys, steps);
338                steps.push(Step::Index(index));
339            }
340            Place::Entry {
341                parent,
342                key: (start, end),
343            } => {
344                parent.locate(keys, steps);
345                steps.extend(keys.get(start..end).map(|key| Step::Key(key.to_owned())));
346            }
347        }
348    }
349
350    /// The path from the root to this node.
351    fn path(&self, keys: &str) -> Path {
352        let mut steps = Vec::new();
353        self.locate(keys, &mut steps);
354        Path::new(steps)
355    }
356}
357
358/// A place with the keys it names: what the shape rules locate a refusal
359/// with.
360struct At<'a> {
361    place: &'a Place<'a>,
362    keys: &'a str,
363}
364
365impl Locate for At<'_> {
366    fn locate(&self, steps: &mut Vec<Step>) {
367        self.place.locate(self.keys, steps);
368    }
369}
370
371/// A shape refusal, reported the only way a sink generic over its error
372/// can report anything.
373fn refused<E: Error>(refusal: Refusal) -> E {
374    E::source(SourceError::new(refusal))
375}
376
377impl<'t, E> Terms<'t, E> {
378    /// A sink that appends equality terms to `out` and keeps the shape
379    /// rules itself.
380    pub fn new(out: &'t mut Vec<Term>) -> Self {
381        Self::by(Equality, out)
382    }
383
384    /// A sink that appends to `out` and leaves the shape rules to the
385    /// [`EncryptSink`] it is teed beside, which refuses first.
386    fn beside_encryption(out: &'t mut Vec<Term>) -> Self {
387        Self {
388            rules: None,
389            ..Self::new(out)
390        }
391    }
392}
393
394impl<'t, E, N: Normalize> Terms<'t, E, N> {
395    /// A sink that appends the terms `by` makes to `out` and keeps the
396    /// shape rules itself.
397    ///
398    /// The normalizer is copied into every child sink, which is why
399    /// [`Normalize`] requires `Copy`.
400    pub fn by(by: N, out: &'t mut Vec<Term<N::Term>>) -> Self {
401        Self {
402            out,
403            keys: Keys::Own(String::new()),
404            at: Place::Root,
405            rules: Some(NodeRules {
406                depth: 0,
407                settle: None,
408            }),
409            by,
410            _error: PhantomData,
411        }
412    }
413
414    fn at(&self) -> At<'_> {
415        At {
416            place: &self.at,
417            keys: self.keys.as_str(),
418        }
419    }
420}
421
422impl<'t, E: Error, N: Normalize> Sink for Terms<'t, E, N> {
423    type Ok = ();
424    type Error = E;
425    type Seq = TermsSeq<'t, E, N>;
426    type Map = TermsMap<'t, E, N>;
427
428    fn scalar(self, ty: Option<Logical>, value: Physical<'_>) -> Result<(), E> {
429        if let Some(rules) = &self.rules {
430            shape::scalar(rules.depth, ty, &value, &self.at()).map_err(refused)?;
431        }
432        match self.by.normalize(ty, value) {
433            Ok(normalized) => {
434                let path = self.at.path(self.keys.as_str());
435                self.out.push(Term { path, normalized });
436            }
437            // No domain for the kind: nothing to index, carry on. No domain
438            // for a logical type: refuse, see the type's docs.
439            Err(NormalizeError::NoDomain) if matches!(ty, None | Some(Logical::Undefined)) => {}
440            Err(error) => {
441                return Err(E::source(SourceError::new(TermError {
442                    index: N::INDEX,
443                    path: self.at.path(self.keys.as_str()),
444                    error,
445                })))
446            }
447        }
448        if let Some(rules) = self.rules {
449            rules.sealed();
450        }
451        Ok(())
452    }
453
454    /// Indexed as a plain null. Equality has no width, so the type adds
455    /// nothing: under [`Equality`] a typed null is the same term as a
456    /// plain one.
457    fn typed_null(self, _: FixedType) -> Result<(), E> {
458        self.scalar(None, Physical::Null)
459    }
460
461    fn seq(self, _: Option<usize>) -> Result<Self::Seq, E> {
462        let rules = match self.rules {
463            Some(node) => {
464                shape::container(
465                    node.depth,
466                    &At {
467                        place: &self.at,
468                        keys: self.keys.as_str(),
469                    },
470                )
471                .map_err(refused)?;
472                Some(Held {
473                    node,
474                    rules: SeqRules::new(),
475                })
476            }
477            None => None,
478        };
479        Ok(TermsSeq {
480            mark: self.keys.as_str().len(),
481            out: self.out,
482            keys: self.keys,
483            at: self.at,
484            next: 0,
485            rules,
486            by: self.by,
487            _error: PhantomData,
488        })
489    }
490
491    fn map(self, _: Option<usize>) -> Result<Self::Map, E> {
492        let rules = match self.rules {
493            Some(node) => {
494                shape::container(
495                    node.depth,
496                    &At {
497                        place: &self.at,
498                        keys: self.keys.as_str(),
499                    },
500                )
501                .map_err(refused)?;
502                Some(Held {
503                    node,
504                    rules: MapRules::new(),
505                })
506            }
507            None => None,
508        };
509        Ok(TermsMap {
510            mark: self.keys.as_str().len(),
511            out: self.out,
512            keys: self.keys,
513            at: self.at,
514            rules,
515            by: self.by,
516            _error: PhantomData,
517        })
518    }
519
520    /// No terms. Keeping the shape rules, the subtree is framed, as
521    /// encryption frames it, and the frame dropped.
522    fn passthrough<P: Encrypt + Clone>(self, value: P) -> Result<(), E> {
523        if let Some(rules) = self.rules {
524            let at = At {
525                place: &self.at,
526                keys: self.keys.as_str(),
527            };
528            shape::frame_passthrough(value, rules.depth, &at, refused)?;
529            if let Some(settle) = rules.settle {
530                settle.passthrough();
531            }
532        }
533        Ok(())
534    }
535}
536
537/// The sequence side of [`Terms`]: each element's path is the sequence's
538/// plus its index.
539pub struct TermsSeq<'t, E, N: Normalize = Equality> {
540    out: &'t mut Vec<Term<N::Term>>,
541    keys: Keys<'t>,
542    /// Where this sequence's own keys would begin: the length of `keys`
543    /// at its start.
544    mark: usize,
545    at: Place<'t>,
546    next: usize,
547    rules: Option<Held<'t, SeqRules>>,
548    by: N,
549    _error: PhantomData<E>,
550}
551
552impl<E: Error, N: Normalize> SeqSink for TermsSeq<'_, E, N> {
553    type Ok = ();
554    type Error = E;
555    type Element<'s>
556        = Terms<'s, E, N>
557    where
558        Self: 's;
559
560    fn element(&mut self) -> Result<Terms<'_, E, N>, E> {
561        let TermsSeq {
562            out,
563            keys,
564            mark,
565            at,
566            next,
567            rules,
568            by,
569            ..
570        } = self;
571        let keys = keys.get();
572        let rules = match rules {
573            Some(held) => Some(NodeRules {
574                depth: held.node.depth + 1,
575                settle: Some(
576                    held.rules
577                        .element(&At { place: at, keys })
578                        .map_err(refused)?,
579                ),
580            }),
581            None => None,
582        };
583        keys.truncate(*mark);
584        let index = *next;
585        *next += 1;
586        Ok(Terms {
587            out,
588            keys: Keys::Lent(keys),
589            at: Place::Element { parent: at, index },
590            rules,
591            by: *by,
592            _error: PhantomData,
593        })
594    }
595
596    fn end(self) -> Result<(), E> {
597        if let Some(held) = self.rules {
598            let at = At {
599                place: &self.at,
600                keys: self.keys.as_str(),
601            };
602            held.rules.end(&at).map_err(refused)?;
603            held.node.sealed();
604        }
605        Ok(())
606    }
607}
608
609/// The map side of [`Terms`]: each entry's path is the map's plus its
610/// key.
611pub struct TermsMap<'t, E, N: Normalize = Equality> {
612    out: &'t mut Vec<Term<N::Term>>,
613    keys: Keys<'t>,
614    /// Where this map's keys begin in `keys`: its length at the map's
615    /// start. Each entry's key replaces the last one there.
616    mark: usize,
617    at: Place<'t>,
618    rules: Option<Held<'t, MapRules>>,
619    by: N,
620    _error: PhantomData<E>,
621}
622
623impl<E: Error, N: Normalize> MapSink for TermsMap<'_, E, N> {
624    type Ok = ();
625    type Error = E;
626    type Entry<'s>
627        = Terms<'s, E, N>
628    where
629        Self: 's;
630
631    fn entry(&mut self, key: &str) -> Result<Terms<'_, E, N>, E> {
632        let TermsMap {
633            out,
634            keys,
635            mark,
636            at,
637            rules,
638            by,
639            ..
640        } = self;
641        let keys = keys.get();
642        let rules = match rules {
643            Some(held) => Some(NodeRules {
644                depth: held.node.depth + 1,
645                settle: Some(
646                    held.rules
647                        .entry(key, &At { place: at, keys })
648                        .map_err(refused)?,
649                ),
650            }),
651            None => None,
652        };
653        keys.truncate(*mark);
654        keys.push_str(key);
655        let key = (*mark, keys.len());
656        Ok(Terms {
657            out,
658            keys: Keys::Lent(keys),
659            at: Place::Entry { parent: at, key },
660            rules,
661            by: *by,
662            _error: PhantomData,
663        })
664    }
665
666    fn end(self) -> Result<(), E> {
667        if let Some(held) = self.rules {
668            let at = At {
669                place: &self.at,
670                keys: self.keys.as_str(),
671            };
672            held.rules.end(&at).map_err(refused)?;
673            held.node.sealed();
674        }
675        Ok(())
676    }
677}
678
679/// Every equality term of `value`, in walk order, without encrypting it.
680///
681/// It refuses every value encryption refuses, for the same reason at the
682/// same path (see [`Terms`]), so it never returns terms for a value that
683/// cannot be stored.
684///
685/// # Examples
686///
687/// ```
688/// use std::collections::BTreeMap;
689/// use vitaminc_normalize::{domain, terms, NormalizeError, Step};
690///
691/// let mut user = BTreeMap::new();
692/// user.insert("name", "Ada");
693/// let found = terms(&user)?;
694/// assert_eq!(found[0].path.steps(), [Step::Key("name".into())]);
695/// assert_eq!(found[0].normalized.domain, domain::TEXT);
696///
697/// // U+0378 is unassigned in Unicode 16, so the name has no term.
698/// user.insert("name", "\u{378}");
699/// let refused = terms(&user).unwrap_err();
700/// let term = refused.term().expect("refused by Terms");
701/// assert_eq!(term.error(), NormalizeError::UnassignedCodePoint);
702/// assert_eq!(term.path().steps(), [Step::Key("name".into())]);
703/// // The message names neither the field nor its value.
704/// assert_eq!(
705///     refused.to_string(),
706///     "no equality term: text contains a code point unassigned in Unicode 16"
707/// );
708///
709/// // A uuid of three bytes cannot be encrypted, so it has no term either:
710/// // `terms` refuses it as encryption does.
711/// use vitaminc_aead::{Logical, Physical, Raw, Unencryptable};
712/// let id = Raw(Some(Logical::Uuid), Physical::Bytes(&[0; 3]));
713/// let refused = terms(id).unwrap_err();
714/// let refusal = refused.refusal().expect("refused for its shape");
715/// assert_eq!(refusal.reason(), Unencryptable::InvalidValue);
716/// # Ok::<(), vitaminc_normalize::TermsError>(())
717/// ```
718///
719/// # Errors
720///
721/// [`TermsError`]: what [`Terms`] refuses, a value encryption would refuse
722/// included, or the source's own failure.
723pub fn terms<P: Encrypt>(value: P) -> Result<Vec<Term>, TermsError> {
724    terms_by(Equality, value)
725}
726
727/// Every term of `value` under the normalizer `by`, in walk order, without
728/// encrypting it: [`terms`] for an index of another kind.
729///
730/// It refuses what [`terms`] refuses for the value's shape, and what `by`
731/// refuses for a scalar, the way [`Terms`] describes.
732///
733/// # Errors
734///
735/// [`TermsError`]: what [`Terms`] refuses, a value encryption would refuse
736/// included, or the source's own failure.
737pub fn terms_by<N, P>(by: N, value: P) -> Result<Vec<Term<N::Term>>, TermsError>
738where
739    N: Normalize,
740    P: Encrypt,
741{
742    let mut out = Vec::new();
743    value.visit(Terms::by(by, &mut out))?;
744    Ok(out)
745}
746
747/// A ciphertext tree and the terms of the value it was sealed from, in
748/// walk order.
749pub type WithTerms<L> = (Node<L>, Vec<Term>);
750
751/// Walk `value` into the encrypting sink and the terms sink at once,
752/// without sealing: the planned tree of pending leaves, and every term.
753///
754/// # Errors
755///
756/// Everything [`EncryptSink`] refuses, plus what [`Terms`] refuses, which
757/// arrives as [`EncryptError::Source`] carrying a [`TermError`].
758pub fn plan_with_terms<'a, P, E, A>(value: P, aad: A) -> Result<WithTerms<Pending>, EncryptError<E>>
759where
760    P: Encrypt,
761    A: IntoAad<'a>,
762{
763    let mut terms = Vec::new();
764    let (planned, ()) = value.visit(Tee(
765        EncryptSink::root(aad),
766        Terms::beside_encryption(&mut terms),
767    ))?;
768    Ok((planned, terms))
769}
770
771/// Encrypt `value` and produce its equality terms in the same walk.
772///
773/// # Errors
774///
775/// As [`plan_with_terms`], then [`EncryptError::Cipher`] if the cipher
776/// refuses a leaf.
777pub fn encrypt_with_terms<'a, P, C, A>(
778    value: P,
779    cipher: &C,
780    aad: A,
781) -> Result<WithTerms<C::Leaf>, EncryptError<C::Error>>
782where
783    P: Encrypt,
784    C: Cipher,
785    A: IntoAad<'a>,
786{
787    let (planned, terms) = plan_with_terms(value, aad)?;
788    Ok((seal_all(cipher, planned)?, terms))
789}
790
791/// Encrypt `value` through an async cipher and produce its equality terms
792/// in the same walk. The walk, and so every term, is complete before the
793/// future is returned, so the future holds only the planned leaves and is
794/// `Send` even when the source is not.
795///
796/// # Errors
797///
798/// As [`encrypt_with_terms`]. A refusal during the walk is held in the
799/// future and returned on its first poll.
800pub fn encrypt_with_terms_async<'a, 'c, P, C, A>(
801    value: P,
802    cipher: &'c C,
803    aad: A,
804) -> impl Future<Output = Result<WithTerms<C::Leaf>, EncryptError<C::Error>>> + Send + 'c
805where
806    P: Encrypt,
807    C: AsyncCipher,
808    A: IntoAad<'a>,
809{
810    let planned = plan_with_terms(value, aad);
811    async move {
812        let (planned, terms) = planned?;
813        Ok((seal_all_async(cipher, planned).await?, terms))
814    }
815}