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}