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    pub fn max_transcode_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
257        self.hooks.max_transcode_output_len(&self.codec, input_len)
258    }
259
260    /// Gets the global maximum output units emitted by stream reset.
261    ///
262    /// # Returns
263    ///
264    /// Returns the codec's reset-output upper bound. This query never fails.
265    #[inline]
266    pub fn max_reset_output_len(&self) -> Result<usize, CapacityError> {
267        Ok(C::MAX_ENCODE_RESET_UNITS)
268    }
269
270    /// Gets the global maximum output units emitted by finishing codec and hook
271    /// state.
272    ///
273    /// Returns the sum of [`Codec::MAX_ENCODE_FINISH_UNITS`] and the
274    /// hook-provided final-output bound. The codec finish portion covers units
275    /// written by [`Codec::encode_finish`]; hook implementations must not
276    /// include that portion in
277    /// [`TranscodeEncodeHooks::max_finish_output_len`]. Both component bounds
278    /// cover every reachable transient state.
279    ///
280    /// # Returns
281    ///
282    /// Returns the combined codec-finish and hook-finish output bound.
283    ///
284    /// # Errors
285    ///
286    /// Returns [`CapacityError::OutputLengthOverflow`] when the sum overflows.
287    #[inline]
288    pub fn max_finish_output_len(&self) -> Result<usize, CapacityError> {
289        C::MAX_ENCODE_FINISH_UNITS
290            .checked_add(self.hooks.max_finish_output_len(&self.codec))
291            .ok_or(CapacityError::OutputLengthOverflow)
292    }
293
294    /// Gets the maximum output units needed by a complete one-shot encode
295    /// stream.
296    ///
297    /// The returned bound covers reset output, the streaming encode phase for
298    /// `input_len` values, and finish output. Its components are global across
299    /// transient state. Higher-level complete encode helpers should use this
300    /// engine-level bound instead of recomputing capacity from [`Codec`]
301    /// constants, because hook policy may change streaming or finish output.
302    ///
303    /// # Parameters
304    ///
305    /// - `input_len`: Number of input values in the complete stream.
306    ///
307    /// # Returns
308    ///
309    /// Returns the complete-stream output bound.
310    ///
311    /// # Errors
312    ///
313    /// Returns a capacity error if a component query or their sum overflows.
314    #[inline]
315    pub fn max_total_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
316        let reset = self.max_reset_output_len()?;
317        let transcode = self.max_transcode_output_len(input_len)?;
318        let finish = self.max_finish_output_len()?;
319        reset
320            .checked_add(transcode)
321            .and_then(|len| len.checked_add(finish))
322            .ok_or(CapacityError::OutputLengthOverflow)
323    }
324
325    /// Resets codec encode state, hook-owned state, and stream-start output.
326    ///
327    /// # Parameters
328    ///
329    /// - `output`: Complete output unit slice visible to the encoder.
330    /// - `output_index`: Absolute output unit index where writing starts.
331    ///
332    /// # Returns
333    ///
334    /// Returns the number of reset units written.
335    ///
336    /// # Errors
337    ///
338    /// Returns framework errors when the caller provides invalid or
339    /// insufficient output capacity. Returns domain errors when codec reset or
340    /// hook reset handling fails. Capacity and index failures occur before any
341    /// reset state is changed. Once reset execution starts, an error poisons
342    /// the engine until a later reset succeeds.
343    ///
344    /// # Panics
345    ///
346    /// Panics if the codec reports reset output beyond its declared bound.
347    pub fn reset(&mut self, output: &mut [C::Unit], output_index: usize) -> Result<usize, TranscodeEncodeErrorOf<C>> {
348        let required = self.max_reset_output_len()?;
349        TranscodeFailure::ensure_output_capacity(output.len(), output_index, required)?;
350        self.lifecycle.on_reset_start();
351        self.hooks.reset_hooks(&mut self.codec);
352        let written = unsafe {
353            // SAFETY: The capacity check above reserves the codec's declared
354            // reset-output bound at `output_index`.
355            self.codec.encode_reset(output, output_index)
356        }
357        .map_err(TranscodeEncodeError::domain_reset)?;
358        assert!(written <= required, "Codec::encode_reset wrote beyond its reset bound",);
359        self.lifecycle.on_reset_success();
360        Ok(written)
361    }
362
363    /// Encodes values into a caller-provided output buffer.
364    ///
365    /// The engine stops before consuming the next input value when the current
366    /// output buffer does not satisfy that value's planned capacity bound.
367    ///
368    /// # Parameters
369    ///
370    /// - `input`: Complete input value slice visible to the encoder.
371    /// - `input_index`: Absolute input value index where encoding starts.
372    /// - `output`: Complete output unit slice visible to the encoder.
373    /// - `output_index`: Absolute output unit index where writing starts.
374    ///
375    /// # Returns
376    ///
377    /// Returns progress describing input values consumed, output units written,
378    /// and why encoding stopped.
379    ///
380    /// # Errors
381    ///
382    /// Returns framework errors when `input_index` is outside `input` or
383    /// `output_index` is outside `output`, and domain errors when codec or
384    /// hook processing fails or rejects a value. Returns
385    /// [`TranscodeFailure::TranscodeBeforeReset`] when the engine has not
386    /// completed its first reset. Returns
387    /// [`TranscodeFailure::TranscodeAfterFinish`] when the logical stream was
388    /// already finished and has not been reset, or
389    /// [`TranscodeFailure::LifecyclePoisoned`] when an earlier reset or finish
390    /// failed after execution started.
391    ///
392    /// # Panics
393    ///
394    /// Panics if the codec violates its declared encode length or hooks
395    /// return an unencodable replacement.
396    pub fn transcode(
397        &mut self,
398        input: &[C::Value],
399        input_index: usize,
400        output: &mut [C::Unit],
401        output_index: usize,
402    ) -> Result<TranscodeProgress, TranscodeEncodeErrorOf<C>> {
403        self.lifecycle.on_transcode()?;
404        TranscodeFailure::ensure_transcode_indices(input.len(), input_index, output.len(), output_index)?;
405        let mut state = EncodeState::new(input, input_index, output, output_index);
406
407        while state.has_input() {
408            // SAFETY: The loop condition proves that the current input cursor
409            // points at an available value.
410            let attempt = unsafe { state.context_unchecked() };
411            let outcome = self.encode_one(attempt)?;
412            if let Some(progress) = state.apply_encode_outcome(outcome) {
413                return Ok(progress);
414            }
415        }
416
417        Ok(state.complete_progress())
418    }
419
420    /// Finishes codec and hook-owned output after EOF.
421    ///
422    /// Finalization first finishes encode-side codec state through
423    /// [`Codec::encode_finish`], then lets hook implementations finish their
424    /// own retained state. The caller must provide enough output capacity for
425    /// [`TranscodeEncodeEngine::max_finish_output_len`], which includes both
426    /// the codec finish bound and the hook-owned finish bound.
427    ///
428    /// # Parameters
429    ///
430    /// - `output`: Complete output unit slice visible to the encoder.
431    /// - `output_index`: Absolute output unit index where writing starts.
432    ///
433    /// # Returns
434    ///
435    /// Returns the number of units written by finalization.
436    ///
437    /// # Errors
438    ///
439    /// Returns framework errors when the caller provides invalid or
440    /// insufficient output capacity. Returns domain errors when codec finish or
441    /// hook finalization fails. Returns
442    /// [`TranscodeFailure::FinishBeforeReset`] when the engine has not
443    /// completed its first reset. Returns
444    /// [`TranscodeFailure::FinishAfterFinish`] when the logical stream was
445    /// already finished and has not been reset, or
446    /// [`TranscodeFailure::LifecyclePoisoned`] when an earlier reset or finish
447    /// failed after execution started. Capacity and index failures occur before
448    /// finish execution and remain retryable; later failures poison the engine
449    /// until reset succeeds.
450    ///
451    /// # Panics
452    ///
453    /// Panics when the codec finish or finish hooks write beyond their
454    /// independently declared output bounds.
455    pub fn finish(&mut self, output: &mut [C::Unit], output_index: usize) -> Result<usize, TranscodeEncodeErrorOf<C>> {
456        self.lifecycle.on_finish_attempt()?;
457        let hook_required = self.hooks.max_finish_output_len(&self.codec);
458        let required = C::MAX_ENCODE_FINISH_UNITS
459            .checked_add(hook_required)
460            .ok_or(CapacityError::OutputLengthOverflow)?;
461        TranscodeFailure::ensure_output_capacity(output.len(), output_index, required)?;
462        self.lifecycle.on_finish_start();
463        let finished = unsafe {
464            // SAFETY: The capacity check above reserves the codec's declared
465            // finish-output bound at `output_index`.
466            self.codec.encode_finish(output, output_index)
467        }
468        .map_err(TranscodeEncodeError::domain_finish)?;
469        assert!(
470            finished <= C::MAX_ENCODE_FINISH_UNITS,
471            "Codec::encode_finish wrote beyond its finish bound",
472        );
473        let written = self
474            .hooks
475            .finish_hooks(&mut self.codec, output, output_index + finished)?;
476        assert!(
477            written <= hook_required,
478            "TranscodeEncodeEngine hook wrote beyond its finish bound",
479        );
480        self.lifecycle.on_finish_success();
481        Ok(finished + written)
482    }
483
484    /// Runs a complete one-shot `reset -> transcode -> finish` encode stream.
485    ///
486    /// The complete input is supplied as `input`, and output starts at index
487    /// `0` in `output`. Callers that need subranges should slice their
488    /// buffers before calling this method. Downstream one-shot encoder
489    /// helpers should call this engine method instead of reproducing the
490    /// reset, transcode, and finish sequence themselves.
491    ///
492    /// # Parameters
493    ///
494    /// - `input`: Complete input values.
495    /// - `output`: Output unit slice for the whole encoded stream.
496    ///
497    /// # Returns
498    ///
499    /// Returns the number of output units written.
500    ///
501    /// # Errors
502    ///
503    /// Returns framework errors for insufficient output or capacity overflow,
504    /// and domain errors from reset, encode, or finish.
505    ///
506    /// # Panics
507    ///
508    /// Panics if codec or hook implementations violate the output or
509    /// replacement contracts of [`Self::reset`], [`Self::transcode`], or
510    /// [`Self::finish`].
511    #[inline]
512    pub fn transcode_complete_into(
513        &mut self,
514        input: &[C::Value],
515        output: &mut [C::Unit],
516    ) -> Result<usize, TranscodeEncodeErrorOf<C>> {
517        <Self as Transcoder>::transcode_complete_into(self, input, output)
518    }
519
520    /// Encodes one value through the codec and unencodable-value hooks.
521    ///
522    /// This is the single entry point for `TranscodeConvertEngine` to drive
523    /// the encode side without accessing `codec` and `hooks` directly.
524    ///
525    /// # Parameters
526    ///
527    /// - `attempt`: Current value, absolute indices, and writable output
528    ///   buffer.
529    ///
530    /// # Returns
531    ///
532    /// Returns whether the value was consumed or needs more output capacity.
533    ///
534    /// For an unencodable value, this checks capacity for the codec's maximum
535    /// value width before invoking policy hooks. A shortage returns
536    /// `NeedOutput` without changing hook state; this conservative requirement
537    /// also applies when the hook would skip or reject. Errors after a hook
538    /// runs do not roll back mutations made by that hook.
539    ///
540    /// # Errors
541    ///
542    /// Returns an engine-domain error when the codec fails or when the hook
543    /// rejects an unencodable value.
544    ///
545    /// # Panics
546    ///
547    /// Panics if the codec violates its encode length contract or hooks
548    /// supply an unencodable replacement.
549    pub(in crate::transcode) fn encode_one(
550        &mut self,
551        attempt: EncodeAttempt<'_, C::Value, C::Unit>,
552    ) -> Result<EncodeOutcome, TranscodeEncodeErrorOf<C>> {
553        let encodable = {
554            let context = attempt.context();
555            self.codec.can_encode_value(context.input_value())
556        };
557        if encodable {
558            return self.encode_encodable_value(attempt);
559        }
560        let required = C::MAX_ENCODE_UNITS_PER_VALUE;
561        if attempt.available_output() < required {
562            return Ok(EncodeOutcome::need_output(
563                NonZeroUsize::new(required).expect("required output is non-zero when capacity is insufficient"),
564            ));
565        }
566        let action = {
567            let context = attempt.context();
568            self.hooks.handle_unencodable_encode(&mut self.codec, &context)?
569        };
570        self.apply_unencodable_action(action, attempt)
571    }
572
573    /// Encodes an encodable input value.
574    ///
575    /// # Parameters
576    ///
577    /// - `attempt`: Current value, absolute indices, and writable output
578    ///   buffer.
579    ///
580    /// # Returns
581    ///
582    /// Returns the encode outcome for the current value.
583    ///
584    /// # Errors
585    ///
586    /// Returns a codec error when encoding fails.
587    ///
588    /// # Panics
589    ///
590    /// Panics when [`Codec::encode_len`] exceeds
591    /// [`Codec::MAX_ENCODE_UNITS_PER_VALUE`] or [`Codec::encode`] writes a
592    /// different number of units than planned.
593    fn encode_encodable_value(
594        &mut self,
595        attempt: EncodeAttempt<'_, C::Value, C::Unit>,
596    ) -> Result<EncodeOutcome, TranscodeEncodeErrorOf<C>> {
597        let required = self.codec.encode_len(attempt.value());
598        assert!(
599            required <= C::MAX_ENCODE_UNITS_PER_VALUE,
600            "Codec::encode_len exceeded Codec::MAX_ENCODE_UNITS_PER_VALUE",
601        );
602        if attempt.available_output() < required {
603            return Ok(EncodeOutcome::need_output(
604                NonZeroUsize::new(required).expect("required output is non-zero when capacity is insufficient"),
605            ));
606        }
607        let (value, input_index, output, output_index) = attempt.into_parts();
608        let written = unsafe {
609            // SAFETY: The capacity check above reserves the exact value width.
610            self.codec.encode(value, output, output_index)
611        }
612        .map_err(|error| TranscodeEncodeError::domain_main(error, input_index))?;
613        assert!(
614            written == required,
615            "Codec::encode wrote a different length than Codec::encode_len",
616        );
617        Ok(EncodeOutcome::consumed(written))
618    }
619
620    /// Applies the hook-selected unencodable-value action.
621    ///
622    /// # Parameters
623    ///
624    /// - `action`: Policy action selected by the encode hooks.
625    /// - `attempt`: Rejected value, absolute indices, and writable output
626    ///   buffer.
627    ///
628    /// # Returns
629    ///
630    /// Returns the encode outcome for the current input value.
631    ///
632    /// # Errors
633    ///
634    /// Returns an unencodable-value error for rejection, or a codec-domain
635    /// error when replacement encoding fails.
636    ///
637    /// # Panics
638    ///
639    /// Panics if replacement encoding violates the codec length contract or
640    /// the replacement cannot be encoded.
641    #[inline]
642    fn apply_unencodable_action(
643        &mut self,
644        action: EncodeUnencodableAction<C::Value>,
645        attempt: EncodeAttempt<'_, C::Value, C::Unit>,
646    ) -> Result<EncodeOutcome, TranscodeEncodeErrorOf<C>> {
647        match action {
648            EncodeUnencodableAction::Reject => {
649                // Keep the streaming encoder usable for non-Clone values.
650                // The failure still carries the absolute input index; callers
651                // that need value context can add it in a higher-level facade.
652                Err(TranscodeEncodeError::unencodable_without_context(attempt.input_index()))
653            }
654            EncodeUnencodableAction::Skip => Ok(EncodeOutcome::consumed(0)),
655            EncodeUnencodableAction::Replace { value } => self.encode_replacement_value(value, attempt),
656        }
657    }
658
659    /// Encodes a hook-provided replacement value.
660    ///
661    /// # Parameters
662    ///
663    /// - `value`: Replacement value selected by hooks.
664    /// - `attempt`: Original value, absolute indices, and writable output
665    ///   buffer.
666    ///
667    /// # Returns
668    ///
669    /// Returns the encode outcome for the replacement.
670    ///
671    /// # Errors
672    ///
673    /// Returns a codec error when replacement encoding fails.
674    ///
675    /// # Panics
676    ///
677    /// Panics when hooks return a replacement value that the codec cannot
678    /// encode, [`Codec::encode_len`] exceeds
679    /// [`Codec::MAX_ENCODE_UNITS_PER_VALUE`], or [`Codec::encode`] writes a
680    /// different number of units than planned.
681    fn encode_replacement_value(
682        &mut self,
683        value: C::Value,
684        attempt: EncodeAttempt<'_, C::Value, C::Unit>,
685    ) -> Result<EncodeOutcome, TranscodeEncodeErrorOf<C>> {
686        assert!(
687            self.codec.can_encode_value(&value),
688            "EncodeUnencodableAction::Replace returned an unencodable replacement value",
689        );
690        let required = self.codec.encode_len(&value);
691        assert!(
692            required <= C::MAX_ENCODE_UNITS_PER_VALUE,
693            "Codec::encode_len exceeded Codec::MAX_ENCODE_UNITS_PER_VALUE",
694        );
695        if attempt.available_output() < required {
696            return Ok(EncodeOutcome::need_output(
697                NonZeroUsize::new(required).expect("required output is non-zero when capacity is insufficient"),
698            ));
699        }
700        let (_, input_index, output, output_index) = attempt.into_parts();
701        let written = unsafe {
702            // SAFETY: The capacity check above reserves the exact replacement
703            // value width, and the hook contract requires encodability.
704            self.codec.encode(&value, output, output_index)
705        }
706        .map_err(|error| TranscodeEncodeError::domain_main(error, input_index))?;
707        assert!(
708            written == required,
709            "Codec::encode wrote a different length than Codec::encode_len",
710        );
711        Ok(EncodeOutcome::consumed(written))
712    }
713}
714
715impl<C, H> Default for TranscodeEncodeEngine<C, H>
716where
717    C: Codec + Default,
718    H: TranscodeEncodeHooks<C> + Default,
719{
720    /// Creates a default buffered encoder engine.
721    ///
722    /// # Returns
723    ///
724    /// Returns an engine with default codec and hooks.
725    #[inline]
726    fn default() -> Self {
727        Self::new(C::default(), H::default())
728    }
729}
730
731impl<C, H> Transcoder for TranscodeEncodeEngine<C, H>
732where
733    C: Codec,
734    H: TranscodeEncodeHooks<C>,
735{
736    /// Logical values borrowed from the caller during encoding.
737    type Input = C::Value;
738    /// Encoded storage units written into caller-owned buffers.
739    type Output = C::Unit;
740    /// Framework failures or codec-domain errors from encoding.
741    type Error = TranscodeEncodeErrorOf<C>;
742
743    /// Gets a conservative upper bound for output units needed for
744    /// `input_len` values.
745    ///
746    /// This bound covers only the streaming encode phase. It is delegated to
747    /// [`TranscodeEncodeHooks::max_transcode_output_len`], so it includes hook
748    /// policy and is valid for every reachable transient codec and hook state.
749    /// Downstream encoders must use this engine-level API for capacity planning
750    /// instead of recomputing the bound from [`Codec`] constants.
751    ///
752    /// # Parameters
753    ///
754    /// - `input_len`: Number of input values the caller plans to encode.
755    ///
756    /// # Returns
757    ///
758    /// Returns a conservative upper bound for output units.
759    ///
760    /// # Errors
761    ///
762    /// Returns a capacity error when hook planning overflows.
763    #[inline]
764    fn max_transcode_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
765        TranscodeEncodeEngine::max_transcode_output_len(self, input_len)
766    }
767
768    /// Gets the global maximum output units emitted by stream reset.
769    ///
770    /// # Returns
771    ///
772    /// Returns the codec's reset-output upper bound. This query never fails.
773    #[inline]
774    fn max_reset_output_len(&self) -> Result<usize, CapacityError> {
775        TranscodeEncodeEngine::max_reset_output_len(self)
776    }
777
778    /// Gets the global maximum output units emitted by finishing codec and hook
779    /// state.
780    ///
781    /// Returns the sum of [`Codec::MAX_ENCODE_FINISH_UNITS`] and the
782    /// hook-provided final-output bound. The codec finish portion covers units
783    /// written by [`Codec::encode_finish`]; hook implementations must not
784    /// include that portion in
785    /// [`TranscodeEncodeHooks::max_finish_output_len`]. Both component bounds
786    /// cover every reachable transient state.
787    ///
788    /// # Returns
789    ///
790    /// Returns the combined codec-finish and hook-finish output bound.
791    ///
792    /// # Errors
793    ///
794    /// Returns [`CapacityError::OutputLengthOverflow`] when the sum overflows.
795    #[inline]
796    fn max_finish_output_len(&self) -> Result<usize, CapacityError> {
797        TranscodeEncodeEngine::max_finish_output_len(self)
798    }
799
800    /// Resets codec encode state, hook-owned state, and stream-start output.
801    ///
802    /// # Parameters
803    ///
804    /// - `output`: Complete output unit slice visible to the encoder.
805    /// - `output_index`: Absolute output unit index where writing starts.
806    ///
807    /// # Returns
808    ///
809    /// Returns the number of reset units written.
810    ///
811    /// # Errors
812    ///
813    /// Returns framework errors when the caller provides invalid or
814    /// insufficient output capacity. Returns domain errors when codec reset or
815    /// hook reset handling fails. Capacity and index failures occur before any
816    /// reset state is changed. Once reset execution starts, an error poisons
817    /// the engine until a later reset succeeds.
818    ///
819    /// # Panics
820    ///
821    /// Panics if the codec reports reset output beyond its declared bound.
822    #[inline]
823    fn reset(&mut self, output: &mut [C::Unit], output_index: usize) -> Result<usize, TranscodeEncodeErrorOf<C>> {
824        TranscodeEncodeEngine::reset(self, output, output_index)
825    }
826
827    /// Encodes values into a caller-provided output buffer.
828    ///
829    /// The engine stops before consuming the next input value when the current
830    /// output buffer does not satisfy that value's planned capacity bound.
831    ///
832    /// # Parameters
833    ///
834    /// - `input`: Complete input value slice visible to the encoder.
835    /// - `input_index`: Absolute input value index where encoding starts.
836    /// - `output`: Complete output unit slice visible to the encoder.
837    /// - `output_index`: Absolute output unit index where writing starts.
838    ///
839    /// # Returns
840    ///
841    /// Returns progress describing input values consumed, output units written,
842    /// and why encoding stopped.
843    ///
844    /// # Errors
845    ///
846    /// Returns framework errors when `input_index` is outside `input` or
847    /// `output_index` is outside `output`, and domain errors when codec or
848    /// hook processing fails or rejects a value. Returns
849    /// [`TranscodeFailure::TranscodeBeforeReset`] when the engine has not
850    /// completed its first reset. Returns
851    /// [`TranscodeFailure::TranscodeAfterFinish`] when the logical stream was
852    /// already finished and has not been reset, or
853    /// [`TranscodeFailure::LifecyclePoisoned`] when an earlier reset or finish
854    /// failed after execution started.
855    ///
856    /// # Panics
857    ///
858    /// Panics if the codec violates its declared encode length or hooks
859    /// return an unencodable replacement.
860    #[inline]
861    fn transcode(
862        &mut self,
863        input: &[C::Value],
864        input_index: usize,
865        output: &mut [C::Unit],
866        output_index: usize,
867    ) -> Result<TranscodeProgress, TranscodeEncodeErrorOf<C>> {
868        TranscodeEncodeEngine::transcode(self, input, input_index, output, output_index)
869    }
870
871    /// Finishes codec and hook-owned output after EOF.
872    ///
873    /// Finalization first finishes encode-side codec state through
874    /// [`Codec::encode_finish`], then lets hook implementations finish their
875    /// own retained state. The caller must provide enough output capacity for
876    /// [`TranscodeEncodeEngine::max_finish_output_len`], which includes both
877    /// the codec finish bound and the hook-owned finish bound.
878    ///
879    /// # Parameters
880    ///
881    /// - `output`: Complete output unit slice visible to the encoder.
882    /// - `output_index`: Absolute output unit index where writing starts.
883    ///
884    /// # Returns
885    ///
886    /// Returns the number of units written by finalization.
887    ///
888    /// # Errors
889    ///
890    /// Returns framework errors when the caller provides invalid or
891    /// insufficient output capacity. Returns domain errors when codec finish or
892    /// hook finalization fails. Returns
893    /// [`TranscodeFailure::FinishBeforeReset`] when the engine has not
894    /// completed its first reset. Returns
895    /// [`TranscodeFailure::FinishAfterFinish`] when the logical stream was
896    /// already finished and has not been reset, or
897    /// [`TranscodeFailure::LifecyclePoisoned`] when an earlier reset or finish
898    /// failed after execution started. Capacity and index failures occur before
899    /// finish execution and remain retryable; later failures poison the engine
900    /// until reset succeeds.
901    ///
902    /// # Panics
903    ///
904    /// Panics when the codec finish writes beyond
905    /// [`Codec::MAX_ENCODE_FINISH_UNITS`] or when the combined codec and hook
906    /// finalization writes beyond
907    /// [`TranscodeEncodeEngine::max_finish_output_len`].
908    #[inline]
909    fn finish(&mut self, output: &mut [C::Unit], output_index: usize) -> Result<usize, TranscodeEncodeErrorOf<C>> {
910        TranscodeEncodeEngine::finish(self, output, output_index)
911    }
912}
913
914impl<C, H> TranscodeEncoder for TranscodeEncodeEngine<C, H>
915where
916    C: Codec,
917    H: TranscodeEncodeHooks<C>,
918{
919    /// Codec-specific source error retained by domain failures.
920    type EncodeError = C::EncodeError;
921}