Skip to main content

qubit_codec/transcode/engine/
transcode_encode_engine.rs

1// =============================================================================
2//    Copyright (c) 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Reusable buffered encoder engine.
9
10use core::num::NonZeroUsize;
11
12use super::super::internal::encode_attempt::EncodeAttempt;
13use super::super::internal::encode_state::EncodeState;
14use super::super::internal::lifecycle_guard::LifecycleGuard;
15use super::EncodeOutcome;
16use super::EncodeUnencodableAction;
17use super::TranscodeEncodeHooks;
18use crate::CapacityError;
19use crate::Codec;
20use crate::TranscodeEncodeError;
21use crate::TranscodeEncodeErrorOf;
22use crate::TranscodeEncoder;
23use crate::TranscodeFailure;
24use crate::TranscodeProgress;
25use crate::Transcoder;
26use crate::codec::assert_unit_bounds;
27
28/// Reusable buffered encoding engine for codec-backed encoders.
29///
30/// The engine owns the low-level codec and hook object. It keeps the common
31/// buffered encoding loop private: input-index validation, output-capacity
32/// checks, input consumption, output progress, and [`crate::TranscodeStatus`]
33/// reporting.
34///
35/// Use this type to build a streaming encoder over a one-value [`Codec`]. The
36/// engine does not allocate output. It encodes values accepted by
37/// [`Codec::can_encode_value`] directly through the codec. When a value is
38/// outside the codec's encodable domain, the engine asks hooks whether to
39/// reject, skip, or replace it. If the current output buffer is too small for
40/// the selected value, the engine returns
41/// [`crate::TranscodeStatus::NeedOutput`] without consuming that input; the
42/// caller can provide a larger or fresh output buffer and resume with the
43/// returned input index.
44///
45/// For the common strict policy that simply wraps codec errors, use
46/// [`crate::CodecTranscodeEncoder`]. Use `TranscodeEncodeEngine` directly when
47/// the encode policy needs custom planning, replacement, skipped values, or
48/// finish-time output.
49///
50/// # Examples
51///
52/// ```rust
53/// use core::convert::Infallible;
54/// use core::num::NonZeroUsize;
55///
56/// use qubit_codec::Codec;
57/// use qubit_codec::DecodeFailure;
58/// use qubit_codec::TranscodeEncodeErrorOf;
59/// use qubit_codec::TranscodeStatus;
60/// use qubit_codec::engine::EncodeContext;
61/// use qubit_codec::engine::EncodeUnencodableAction;
62/// use qubit_codec::engine::TranscodeEncodeEngine;
63/// use qubit_codec::engine::TranscodeEncodeHooks;
64///
65/// #[derive(Clone, Copy)]
66/// struct ByteCodec;
67///
68/// impl Codec for ByteCodec {
69///     type Value = u8;
70///     type Unit = u8;
71///     type DecodeError = Infallible;
72///     type EncodeError = Infallible;
73///
74///     const MIN_UNITS_PER_VALUE: usize = 1;
75///     const MAX_ENCODE_UNITS_PER_VALUE: usize = 1;
76///     const MAX_DECODE_UNITS_PER_VALUE: usize = 1;
77///
78///     unsafe fn decode(
79///         &mut self,
80///         input: &[u8],
81///         index: usize,
82///     ) -> Result<(u8, NonZeroUsize), DecodeFailure<Self::DecodeError>> {
83///         Ok((input[index], NonZeroUsize::MIN))
84///     }
85///
86///     unsafe fn encode(
87///         &mut self,
88///         value: &u8,
89///         output: &mut [u8],
90///         index: usize,
91///     ) -> Result<usize, Self::EncodeError> {
92///         output[index] = *value;
93///         Ok(1)
94///     }
95/// }
96///
97/// struct StrictHooks;
98///
99/// impl TranscodeEncodeHooks<ByteCodec> for StrictHooks {
100///     fn handle_unencodable_encode(
101///         &mut self,
102///         _codec: &mut ByteCodec,
103///         _context: &EncodeContext<'_, u8>,
104///     ) -> Result<EncodeUnencodableAction<u8>, TranscodeEncodeErrorOf<ByteCodec>> {
105///         unreachable!("ByteCodec accepts every u8")
106///     }
107/// }
108///
109/// let mut engine = TranscodeEncodeEngine::new(ByteCodec, StrictHooks);
110/// let input = [1_u8, 2, 3];
111/// let mut output = [0_u8; 2];
112/// let mut reset_output = [];
113/// engine.reset(&mut reset_output, 0)?;
114///
115/// let progress = engine.transcode(&input, 0, &mut output, 0)?;
116/// match progress.status() {
117///     TranscodeStatus::Complete => unreachable!("output is intentionally short"),
118///     TranscodeStatus::NeedOutput { .. } => {
119///         assert_eq!(2, progress.written());
120///         assert_eq!([1, 2], output);
121///         // Write out `output[..progress.written()]`, then resume at
122///         // `progress.read()` with fresh output capacity.
123///     }
124///     TranscodeStatus::NeedInput { .. } => unreachable!("encoders do not read encoded input"),
125/// }
126/// # Ok::<(), TranscodeEncodeErrorOf<ByteCodec>>(())
127/// ```
128///
129/// # Type Parameters
130///
131/// - `C`: Low-level codec used by the engine.
132/// - `H`: Policy hook object used by the engine.
133#[derive(Debug)]
134pub struct TranscodeEncodeEngine<C, H> {
135    /// Owned codec carrying the encode-side stream state.
136    codec: C,
137    /// Owned policy hooks for unsupported values and lifecycle output.
138    hooks: H,
139    /// Guard for the `reset → transcode* → finish` lifecycle in every build
140    /// profile.
141    lifecycle: LifecycleGuard,
142}
143
144impl<C, H> TranscodeEncodeEngine<C, H>
145where
146    C: Codec,
147    H: TranscodeEncodeHooks<C>,
148{
149    /// Creates a buffered encoder engine.
150    ///
151    /// # Parameters
152    ///
153    /// - `codec`: Low-level codec used for one-value encoding.
154    /// - `hooks`: Policy hooks used for planning and writing values.
155    ///
156    /// # Returns
157    ///
158    /// Returns a buffered encoder engine.
159    ///
160    /// # Compile-Time Checks
161    ///
162    /// Fails to compile when the supplied codec declares a zero decode unit
163    /// bound or when [`Codec::MIN_UNITS_PER_VALUE`] exceeds
164    /// [`Codec::MAX_DECODE_UNITS_PER_VALUE`]. The encode bound may be zero for
165    /// a fully buffered codec.
166    #[inline]
167    #[must_use]
168    pub fn new(codec: C, hooks: H) -> Self {
169        assert_unit_bounds::<C>();
170        Self {
171            codec,
172            hooks,
173            lifecycle: LifecycleGuard::new(),
174        }
175    }
176
177    /// Returns the wrapped low-level codec.
178    ///
179    /// # Returns
180    ///
181    /// Returns a shared reference to the codec owned by this engine.
182    #[inline]
183    #[must_use]
184    pub const fn codec(&self) -> &C {
185        &self.codec
186    }
187
188    /// Returns the wrapped low-level codec mutably.
189    ///
190    /// # Returns
191    ///
192    /// Returns a mutable reference to the codec owned by this engine.
193    #[inline]
194    #[must_use]
195    pub fn codec_mut(&mut self) -> &mut C {
196        &mut self.codec
197    }
198
199    /// Returns the encode hooks used by this engine.
200    ///
201    /// # Returns
202    ///
203    /// Returns a shared reference to the hook object owned by this engine.
204    #[inline]
205    #[must_use]
206    pub const fn hooks(&self) -> &H {
207        &self.hooks
208    }
209
210    /// Returns the encode hooks mutably.
211    ///
212    /// # Returns
213    ///
214    /// Returns a mutable reference to the hook object owned by this engine.
215    #[inline]
216    #[must_use]
217    pub fn hooks_mut(&mut self) -> &mut H {
218        &mut self.hooks
219    }
220
221    /// Consumes the engine and returns its codec and hooks.
222    ///
223    /// Any lifecycle state owned by the engine is discarded.
224    ///
225    /// # Returns
226    ///
227    /// Returns the wrapped codec followed by the encode hooks.
228    #[inline]
229    #[must_use]
230    pub fn into_parts(self) -> (C, H) {
231        let Self { codec, hooks, .. } = self;
232        (codec, hooks)
233    }
234
235    /// Gets a conservative upper bound for output units needed for
236    /// `input_len` values.
237    ///
238    /// This bound covers only the streaming encode phase. It is delegated to
239    /// [`TranscodeEncodeHooks::max_transcode_output_len`], so it includes hook
240    /// policy and is valid for every reachable transient codec and hook state.
241    /// Downstream encoders must use this engine-level API for capacity planning
242    /// instead of recomputing the bound from [`Codec`] constants.
243    ///
244    /// # Parameters
245    ///
246    /// - `input_len`: Number of input values the caller plans to encode.
247    ///
248    /// # Returns
249    ///
250    /// Returns a conservative upper bound for output units.
251    ///
252    /// # Errors
253    ///
254    /// Returns a capacity error when hook planning overflows.
255    #[inline]
256    #[must_use = "capacity planning can fail on overflow"]
257    pub fn max_transcode_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
258        self.hooks.max_transcode_output_len(&self.codec, input_len)
259    }
260
261    /// Gets the global maximum output units emitted by stream reset.
262    ///
263    /// # Returns
264    ///
265    /// Returns the codec's reset-output upper bound. This query never fails.
266    #[inline]
267    #[must_use = "capacity planning can fail on overflow"]
268    pub fn max_reset_output_len(&self) -> Result<usize, CapacityError> {
269        Ok(C::MAX_ENCODE_RESET_UNITS)
270    }
271
272    /// Gets the global maximum output units emitted by finishing codec and hook
273    /// state.
274    ///
275    /// Returns the sum of [`Codec::MAX_ENCODE_FINISH_UNITS`] and the
276    /// hook-provided final-output bound. The codec finish portion covers units
277    /// written by [`Codec::encode_finish`]; hook implementations must not
278    /// include that portion in
279    /// [`TranscodeEncodeHooks::max_finish_output_len`]. Both component bounds
280    /// cover every reachable transient state.
281    ///
282    /// # Returns
283    ///
284    /// Returns the combined codec-finish and hook-finish output bound.
285    ///
286    /// # Errors
287    ///
288    /// Returns [`CapacityError::OutputLengthOverflow`] when the sum overflows.
289    #[inline]
290    #[must_use = "capacity planning can fail on overflow"]
291    pub fn max_finish_output_len(&self) -> Result<usize, CapacityError> {
292        C::MAX_ENCODE_FINISH_UNITS
293            .checked_add(self.hooks.max_finish_output_len(&self.codec))
294            .ok_or(CapacityError::OutputLengthOverflow)
295    }
296
297    /// Gets the maximum output units needed by a complete one-shot encode
298    /// stream.
299    ///
300    /// The returned bound covers reset output, the streaming encode phase for
301    /// `input_len` values, and finish output. Its components are global across
302    /// transient state. Higher-level complete encode helpers should use this
303    /// engine-level bound instead of recomputing capacity from [`Codec`]
304    /// constants, because hook policy may change streaming or finish output.
305    ///
306    /// # Parameters
307    ///
308    /// - `input_len`: Number of input values in the complete stream.
309    ///
310    /// # Returns
311    ///
312    /// Returns the complete-stream output bound.
313    ///
314    /// # Errors
315    ///
316    /// Returns a capacity error if a component query or their sum overflows.
317    #[inline]
318    #[must_use = "capacity planning can fail on overflow"]
319    pub fn max_total_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
320        let reset = self.max_reset_output_len()?;
321        let transcode = self.max_transcode_output_len(input_len)?;
322        let finish = self.max_finish_output_len()?;
323        reset
324            .checked_add(transcode)
325            .and_then(|len| len.checked_add(finish))
326            .ok_or(CapacityError::OutputLengthOverflow)
327    }
328
329    /// Resets codec encode state, hook-owned state, and stream-start output.
330    ///
331    /// # Parameters
332    ///
333    /// - `output`: Complete output unit slice visible to the encoder.
334    /// - `output_index`: Absolute output unit index where writing starts.
335    ///
336    /// # Returns
337    ///
338    /// Returns the number of reset units written.
339    ///
340    /// # Errors
341    ///
342    /// Returns framework errors when the caller provides invalid or
343    /// insufficient output capacity. Returns domain errors when codec reset or
344    /// hook reset handling fails. Capacity and index failures occur before any
345    /// reset state is changed. Once reset execution starts, an error poisons
346    /// the engine until a later reset succeeds.
347    ///
348    /// # Panics
349    ///
350    /// Panics if the codec reports reset output beyond its declared bound.
351    pub fn reset(&mut self, output: &mut [C::Unit], output_index: usize) -> Result<usize, TranscodeEncodeErrorOf<C>> {
352        let required = self.max_reset_output_len()?;
353        TranscodeFailure::ensure_output_capacity(output.len(), output_index, required)?;
354        self.lifecycle.on_reset_start();
355        self.hooks.reset_hooks(&mut self.codec);
356        let written = unsafe {
357            // SAFETY: The capacity check above reserves the codec's declared
358            // reset-output bound at `output_index`.
359            self.codec.encode_reset(output, output_index)
360        }
361        .map_err(TranscodeEncodeError::domain_reset)?;
362        assert!(written <= required, "Codec::encode_reset wrote beyond its reset bound",);
363        self.lifecycle.on_reset_success();
364        Ok(written)
365    }
366
367    /// Encodes values into a caller-provided output buffer.
368    ///
369    /// The engine stops before consuming the next input value when the current
370    /// output buffer does not satisfy that value's planned capacity bound.
371    ///
372    /// # Parameters
373    ///
374    /// - `input`: Complete input value slice visible to the encoder.
375    /// - `input_index`: Absolute input value index where encoding starts.
376    /// - `output`: Complete output unit slice visible to the encoder.
377    /// - `output_index`: Absolute output unit index where writing starts.
378    ///
379    /// # Returns
380    ///
381    /// Returns progress describing input values consumed, output units written,
382    /// and why encoding stopped.
383    ///
384    /// # Errors
385    ///
386    /// Returns framework errors when `input_index` is outside `input` or
387    /// `output_index` is outside `output`, and domain errors when codec or
388    /// hook processing fails or rejects a value. Returns
389    /// [`TranscodeFailure::TranscodeBeforeReset`] when the engine has not
390    /// completed its first reset. Returns
391    /// [`TranscodeFailure::TranscodeAfterFinish`] when the logical stream was
392    /// already finished and has not been reset, or
393    /// [`TranscodeFailure::LifecyclePoisoned`] when an earlier reset or finish
394    /// failed after execution started.
395    ///
396    /// # Panics
397    ///
398    /// Panics if the codec violates its declared encode length or hooks
399    /// return an unencodable replacement.
400    pub fn transcode(
401        &mut self,
402        input: &[C::Value],
403        input_index: usize,
404        output: &mut [C::Unit],
405        output_index: usize,
406    ) -> Result<TranscodeProgress, TranscodeEncodeErrorOf<C>> {
407        self.lifecycle.on_transcode()?;
408        TranscodeFailure::ensure_transcode_indices(input.len(), input_index, output.len(), output_index)?;
409        let mut state = EncodeState::new(input, input_index, output, output_index);
410
411        while state.has_input() {
412            // SAFETY: The loop condition proves that the current input cursor
413            // points at an available value.
414            let attempt = unsafe { state.context_unchecked() };
415            let outcome = self.encode_one(attempt)?;
416            if let Some(progress) = state.apply_encode_outcome(outcome) {
417                return Ok(progress);
418            }
419        }
420
421        Ok(state.complete_progress())
422    }
423
424    /// Finishes codec and hook-owned output after EOF.
425    ///
426    /// Finalization first finishes encode-side codec state through
427    /// [`Codec::encode_finish`], then lets hook implementations finish their
428    /// own retained state. The caller must provide enough output capacity for
429    /// [`TranscodeEncodeEngine::max_finish_output_len`], which includes both
430    /// the codec finish bound and the hook-owned finish bound.
431    ///
432    /// # Parameters
433    ///
434    /// - `output`: Complete output unit slice visible to the encoder.
435    /// - `output_index`: Absolute output unit index where writing starts.
436    ///
437    /// # Returns
438    ///
439    /// Returns the number of units written by finalization.
440    ///
441    /// # Errors
442    ///
443    /// Returns framework errors when the caller provides invalid or
444    /// insufficient output capacity. Returns domain errors when codec finish or
445    /// hook finalization fails. Returns
446    /// [`TranscodeFailure::FinishBeforeReset`] when the engine has not
447    /// completed its first reset. Returns
448    /// [`TranscodeFailure::FinishAfterFinish`] when the logical stream was
449    /// already finished and has not been reset, or
450    /// [`TranscodeFailure::LifecyclePoisoned`] when an earlier reset or finish
451    /// failed after execution started. Capacity and index failures occur before
452    /// finish execution and remain retryable; later failures poison the engine
453    /// until reset succeeds.
454    ///
455    /// # Panics
456    ///
457    /// Panics when the codec finish writes beyond
458    /// [`Codec::MAX_ENCODE_FINISH_UNITS`] or when the combined codec and hook
459    /// finalization writes beyond
460    /// [`TranscodeEncodeEngine::max_finish_output_len`].
461    pub fn finish(&mut self, output: &mut [C::Unit], output_index: usize) -> Result<usize, TranscodeEncodeErrorOf<C>> {
462        self.lifecycle.on_finish_attempt()?;
463        let required = self.max_finish_output_len()?;
464        TranscodeFailure::ensure_output_capacity(output.len(), output_index, required)?;
465        self.lifecycle.on_finish_start();
466        let finished = unsafe {
467            // SAFETY: The capacity check above reserves the codec's declared
468            // finish-output bound at `output_index`.
469            self.codec.encode_finish(output, output_index)
470        }
471        .map_err(TranscodeEncodeError::domain_finish)?;
472        assert!(
473            finished <= C::MAX_ENCODE_FINISH_UNITS,
474            "Codec::encode_finish wrote beyond its finish bound",
475        );
476        let written = self
477            .hooks
478            .finish_hooks(&mut self.codec, output, output_index + finished)?;
479        assert!(
480            finished + written <= required,
481            "TranscodeEncodeEngine hook wrote beyond its finish bound",
482        );
483        self.lifecycle.on_finish_success();
484        Ok(finished + written)
485    }
486
487    /// Runs a complete one-shot `reset -> transcode -> finish` encode stream.
488    ///
489    /// The complete input is supplied as `input`, and output starts at index
490    /// `0` in `output`. Callers that need subranges should slice their
491    /// buffers before calling this method. Downstream one-shot encoder
492    /// helpers should call this engine method instead of reproducing the
493    /// reset, transcode, and finish sequence themselves.
494    ///
495    /// # Parameters
496    ///
497    /// - `input`: Complete input values.
498    /// - `output`: Output unit slice for the whole encoded stream.
499    ///
500    /// # Returns
501    ///
502    /// Returns the number of output units written.
503    ///
504    /// # Errors
505    ///
506    /// Returns framework errors for insufficient output or capacity overflow,
507    /// and domain errors from reset, encode, or finish.
508    ///
509    /// # Panics
510    ///
511    /// Panics if codec or hook implementations violate the output or
512    /// replacement contracts of [`Self::reset`], [`Self::transcode`], or
513    /// [`Self::finish`].
514    #[inline]
515    pub fn transcode_complete_into(
516        &mut self,
517        input: &[C::Value],
518        output: &mut [C::Unit],
519    ) -> Result<usize, TranscodeEncodeErrorOf<C>> {
520        <Self as Transcoder>::transcode_complete_into(self, input, output)
521    }
522
523    /// Encodes one value through the codec and unencodable-value hooks.
524    ///
525    /// This is the single entry point for `TranscodeConvertEngine` to drive
526    /// the encode side without accessing `codec` and `hooks` directly.
527    ///
528    /// # Parameters
529    ///
530    /// - `attempt`: Current value, absolute indices, and writable output
531    ///   buffer.
532    ///
533    /// # Returns
534    ///
535    /// Returns whether the value was consumed or needs more output capacity.
536    ///
537    /// # Errors
538    ///
539    /// Returns an engine-domain error when the codec fails or when the hook
540    /// rejects an unencodable value.
541    ///
542    /// # Panics
543    ///
544    /// Panics if the codec violates its encode length contract or hooks
545    /// supply an unencodable replacement.
546    pub(in crate::transcode) fn encode_one(
547        &mut self,
548        attempt: EncodeAttempt<'_, C::Value, C::Unit>,
549    ) -> Result<EncodeOutcome, TranscodeEncodeErrorOf<C>> {
550        let encodable = {
551            let context = attempt.context();
552            self.codec.can_encode_value(context.input_value())
553        };
554        if encodable {
555            return self.encode_encodable_value(attempt);
556        }
557        let action = {
558            let context = attempt.context();
559            self.hooks.handle_unencodable_encode(&mut self.codec, &context)?
560        };
561        self.apply_unencodable_action(action, attempt)
562    }
563
564    /// Encodes an encodable input value.
565    ///
566    /// # Parameters
567    ///
568    /// - `attempt`: Current value, absolute indices, and writable output
569    ///   buffer.
570    ///
571    /// # Returns
572    ///
573    /// Returns the encode outcome for the current value.
574    ///
575    /// # Errors
576    ///
577    /// Returns a codec error when encoding fails.
578    ///
579    /// # Panics
580    ///
581    /// Panics when [`Codec::encode_len`] exceeds
582    /// [`Codec::MAX_ENCODE_UNITS_PER_VALUE`] or [`Codec::encode`] writes a
583    /// different number of units than planned.
584    fn encode_encodable_value(
585        &mut self,
586        attempt: EncodeAttempt<'_, C::Value, C::Unit>,
587    ) -> Result<EncodeOutcome, TranscodeEncodeErrorOf<C>> {
588        let required = self.codec.encode_len(attempt.value());
589        assert!(
590            required <= C::MAX_ENCODE_UNITS_PER_VALUE,
591            "Codec::encode_len exceeded Codec::MAX_ENCODE_UNITS_PER_VALUE",
592        );
593        if attempt.available_output() < required {
594            return Ok(EncodeOutcome::need_output(
595                NonZeroUsize::new(required).expect("required output is non-zero when capacity is insufficient"),
596            ));
597        }
598        let (value, input_index, output, output_index) = attempt.into_parts();
599        let written = unsafe {
600            // SAFETY: The capacity check above reserves the exact value width.
601            self.codec.encode(value, output, output_index)
602        }
603        .map_err(|error| TranscodeEncodeError::domain_main(error, input_index))?;
604        assert!(
605            written == required,
606            "Codec::encode wrote a different length than Codec::encode_len",
607        );
608        Ok(EncodeOutcome::consumed(written))
609    }
610
611    /// Applies the hook-selected unencodable-value action.
612    ///
613    /// # Parameters
614    ///
615    /// - `action`: Policy action selected by the encode hooks.
616    /// - `attempt`: Rejected value, absolute indices, and writable output
617    ///   buffer.
618    ///
619    /// # Returns
620    ///
621    /// Returns the encode outcome for the current input value.
622    ///
623    /// # Errors
624    ///
625    /// Returns an unencodable-value error for rejection, or a codec-domain
626    /// error when replacement encoding fails.
627    ///
628    /// # Panics
629    ///
630    /// Panics if replacement encoding violates the codec length contract or
631    /// the replacement cannot be encoded.
632    #[inline]
633    fn apply_unencodable_action(
634        &mut self,
635        action: EncodeUnencodableAction<C::Value>,
636        attempt: EncodeAttempt<'_, C::Value, C::Unit>,
637    ) -> Result<EncodeOutcome, TranscodeEncodeErrorOf<C>> {
638        match action {
639            EncodeUnencodableAction::Reject => {
640                // Keep the streaming encoder usable for non-Clone values.
641                // The failure still carries the absolute input index; callers
642                // that need value context can add it in a higher-level facade.
643                Err(TranscodeEncodeError::unencodable_without_context(attempt.input_index()))
644            }
645            EncodeUnencodableAction::Skip => Ok(EncodeOutcome::consumed(0)),
646            EncodeUnencodableAction::Replace { value } => self.encode_replacement_value(value, attempt),
647        }
648    }
649
650    /// Encodes a hook-provided replacement value.
651    ///
652    /// # Parameters
653    ///
654    /// - `value`: Replacement value selected by hooks.
655    /// - `attempt`: Original value, absolute indices, and writable output
656    ///   buffer.
657    ///
658    /// # Returns
659    ///
660    /// Returns the encode outcome for the replacement.
661    ///
662    /// # Errors
663    ///
664    /// Returns a codec error when replacement encoding fails.
665    ///
666    /// # Panics
667    ///
668    /// Panics when hooks return a replacement value that the codec cannot
669    /// encode, [`Codec::encode_len`] exceeds
670    /// [`Codec::MAX_ENCODE_UNITS_PER_VALUE`], or [`Codec::encode`] writes a
671    /// different number of units than planned.
672    fn encode_replacement_value(
673        &mut self,
674        value: C::Value,
675        attempt: EncodeAttempt<'_, C::Value, C::Unit>,
676    ) -> Result<EncodeOutcome, TranscodeEncodeErrorOf<C>> {
677        assert!(
678            self.codec.can_encode_value(&value),
679            "EncodeUnencodableAction::Replace returned an unencodable replacement value",
680        );
681        let required = self.codec.encode_len(&value);
682        assert!(
683            required <= C::MAX_ENCODE_UNITS_PER_VALUE,
684            "Codec::encode_len exceeded Codec::MAX_ENCODE_UNITS_PER_VALUE",
685        );
686        if attempt.available_output() < required {
687            return Ok(EncodeOutcome::need_output(
688                NonZeroUsize::new(required).expect("required output is non-zero when capacity is insufficient"),
689            ));
690        }
691        let (_, input_index, output, output_index) = attempt.into_parts();
692        let written = unsafe {
693            // SAFETY: The capacity check above reserves the exact replacement
694            // value width, and the hook contract requires encodability.
695            self.codec.encode(&value, output, output_index)
696        }
697        .map_err(|error| TranscodeEncodeError::domain_main(error, input_index))?;
698        assert!(
699            written == required,
700            "Codec::encode wrote a different length than Codec::encode_len",
701        );
702        Ok(EncodeOutcome::consumed(written))
703    }
704}
705
706impl<C, H> Default for TranscodeEncodeEngine<C, H>
707where
708    C: Codec + Default,
709    H: TranscodeEncodeHooks<C> + Default,
710{
711    /// Creates a default buffered encoder engine.
712    ///
713    /// # Returns
714    ///
715    /// Returns an engine with default codec and hooks.
716    #[inline]
717    fn default() -> Self {
718        Self::new(C::default(), H::default())
719    }
720}
721
722impl<C, H> Transcoder for TranscodeEncodeEngine<C, H>
723where
724    C: Codec,
725    H: TranscodeEncodeHooks<C>,
726{
727    /// Logical values borrowed from the caller during encoding.
728    type Input = C::Value;
729    /// Encoded storage units written into caller-owned buffers.
730    type Output = C::Unit;
731    /// Framework failures or codec-domain errors from encoding.
732    type Error = TranscodeEncodeErrorOf<C>;
733
734    /// Gets a conservative upper bound for output units needed for
735    /// `input_len` values.
736    ///
737    /// This bound covers only the streaming encode phase. It is delegated to
738    /// [`TranscodeEncodeHooks::max_transcode_output_len`], so it includes hook
739    /// policy and is valid for every reachable transient codec and hook state.
740    /// Downstream encoders must use this engine-level API for capacity planning
741    /// instead of recomputing the bound from [`Codec`] constants.
742    ///
743    /// # Parameters
744    ///
745    /// - `input_len`: Number of input values the caller plans to encode.
746    ///
747    /// # Returns
748    ///
749    /// Returns a conservative upper bound for output units.
750    ///
751    /// # Errors
752    ///
753    /// Returns a capacity error when hook planning overflows.
754    #[inline]
755    fn max_transcode_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
756        TranscodeEncodeEngine::max_transcode_output_len(self, input_len)
757    }
758
759    /// Gets the global maximum output units emitted by stream reset.
760    ///
761    /// # Returns
762    ///
763    /// Returns the codec's reset-output upper bound. This query never fails.
764    #[inline]
765    fn max_reset_output_len(&self) -> Result<usize, CapacityError> {
766        TranscodeEncodeEngine::max_reset_output_len(self)
767    }
768
769    /// Gets the global maximum output units emitted by finishing codec and hook
770    /// state.
771    ///
772    /// Returns the sum of [`Codec::MAX_ENCODE_FINISH_UNITS`] and the
773    /// hook-provided final-output bound. The codec finish portion covers units
774    /// written by [`Codec::encode_finish`]; hook implementations must not
775    /// include that portion in
776    /// [`TranscodeEncodeHooks::max_finish_output_len`]. Both component bounds
777    /// cover every reachable transient state.
778    ///
779    /// # Returns
780    ///
781    /// Returns the combined codec-finish and hook-finish output bound.
782    ///
783    /// # Errors
784    ///
785    /// Returns [`CapacityError::OutputLengthOverflow`] when the sum overflows.
786    #[inline]
787    fn max_finish_output_len(&self) -> Result<usize, CapacityError> {
788        TranscodeEncodeEngine::max_finish_output_len(self)
789    }
790
791    /// Resets codec encode state, hook-owned state, and stream-start output.
792    ///
793    /// # Parameters
794    ///
795    /// - `output`: Complete output unit slice visible to the encoder.
796    /// - `output_index`: Absolute output unit index where writing starts.
797    ///
798    /// # Returns
799    ///
800    /// Returns the number of reset units written.
801    ///
802    /// # Errors
803    ///
804    /// Returns framework errors when the caller provides invalid or
805    /// insufficient output capacity. Returns domain errors when codec reset or
806    /// hook reset handling fails. Capacity and index failures occur before any
807    /// reset state is changed. Once reset execution starts, an error poisons
808    /// the engine until a later reset succeeds.
809    ///
810    /// # Panics
811    ///
812    /// Panics if the codec reports reset output beyond its declared bound.
813    #[inline]
814    fn reset(&mut self, output: &mut [C::Unit], output_index: usize) -> Result<usize, TranscodeEncodeErrorOf<C>> {
815        TranscodeEncodeEngine::reset(self, output, output_index)
816    }
817
818    /// Encodes values into a caller-provided output buffer.
819    ///
820    /// The engine stops before consuming the next input value when the current
821    /// output buffer does not satisfy that value's planned capacity bound.
822    ///
823    /// # Parameters
824    ///
825    /// - `input`: Complete input value slice visible to the encoder.
826    /// - `input_index`: Absolute input value index where encoding starts.
827    /// - `output`: Complete output unit slice visible to the encoder.
828    /// - `output_index`: Absolute output unit index where writing starts.
829    ///
830    /// # Returns
831    ///
832    /// Returns progress describing input values consumed, output units written,
833    /// and why encoding stopped.
834    ///
835    /// # Errors
836    ///
837    /// Returns framework errors when `input_index` is outside `input` or
838    /// `output_index` is outside `output`, and domain errors when codec or
839    /// hook processing fails or rejects a value. Returns
840    /// [`TranscodeFailure::TranscodeBeforeReset`] when the engine has not
841    /// completed its first reset. Returns
842    /// [`TranscodeFailure::TranscodeAfterFinish`] when the logical stream was
843    /// already finished and has not been reset, or
844    /// [`TranscodeFailure::LifecyclePoisoned`] when an earlier reset or finish
845    /// failed after execution started.
846    ///
847    /// # Panics
848    ///
849    /// Panics if the codec violates its declared encode length or hooks
850    /// return an unencodable replacement.
851    #[inline]
852    fn transcode(
853        &mut self,
854        input: &[C::Value],
855        input_index: usize,
856        output: &mut [C::Unit],
857        output_index: usize,
858    ) -> Result<TranscodeProgress, TranscodeEncodeErrorOf<C>> {
859        TranscodeEncodeEngine::transcode(self, input, input_index, output, output_index)
860    }
861
862    /// Finishes codec and hook-owned output after EOF.
863    ///
864    /// Finalization first finishes encode-side codec state through
865    /// [`Codec::encode_finish`], then lets hook implementations finish their
866    /// own retained state. The caller must provide enough output capacity for
867    /// [`TranscodeEncodeEngine::max_finish_output_len`], which includes both
868    /// the codec finish bound and the hook-owned finish bound.
869    ///
870    /// # Parameters
871    ///
872    /// - `output`: Complete output unit slice visible to the encoder.
873    /// - `output_index`: Absolute output unit index where writing starts.
874    ///
875    /// # Returns
876    ///
877    /// Returns the number of units written by finalization.
878    ///
879    /// # Errors
880    ///
881    /// Returns framework errors when the caller provides invalid or
882    /// insufficient output capacity. Returns domain errors when codec finish or
883    /// hook finalization fails. Returns
884    /// [`TranscodeFailure::FinishBeforeReset`] when the engine has not
885    /// completed its first reset. Returns
886    /// [`TranscodeFailure::FinishAfterFinish`] when the logical stream was
887    /// already finished and has not been reset, or
888    /// [`TranscodeFailure::LifecyclePoisoned`] when an earlier reset or finish
889    /// failed after execution started. Capacity and index failures occur before
890    /// finish execution and remain retryable; later failures poison the engine
891    /// until reset succeeds.
892    ///
893    /// # Panics
894    ///
895    /// Panics when the codec finish writes beyond
896    /// [`Codec::MAX_ENCODE_FINISH_UNITS`] or when the combined codec and hook
897    /// finalization writes beyond
898    /// [`TranscodeEncodeEngine::max_finish_output_len`].
899    #[inline]
900    fn finish(&mut self, output: &mut [C::Unit], output_index: usize) -> Result<usize, TranscodeEncodeErrorOf<C>> {
901        TranscodeEncodeEngine::finish(self, output, output_index)
902    }
903}
904
905impl<C, H> TranscodeEncoder for TranscodeEncodeEngine<C, H>
906where
907    C: Codec,
908    H: TranscodeEncodeHooks<C>,
909{
910    /// Codec-specific source error retained by domain failures.
911    type EncodeError = C::EncodeError;
912}