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 = "use the codec reference to inspect the wrapped codec"]
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 = "use the codec reference to configure the wrapped codec"]
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 = "use the hook reference to inspect the encode policy"]
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 = "use the hook reference to configure the encode policy"]
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}