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