zencodec

zencodec, in code.

Start with a working call. Add one real requirement at a time. Expand imports, setup and continuation inline.

Review branch · PR 175 · eeb1a450 · examples are extracted from source, not pseudocode.

Borrowed Pulse arguments accept None for no extra per-call control or Some(pulse) for reporting, phases, sharing and cancellation. Retained job controls and mandatory policy checks still apply. The examples use the default policy first, then add callbacks for situational and later evidence. PNG, WebP, JPEG and bitmap optional-control changes are merged after focused native checks. Other native routes, progress reporting, workspace bounds and whole-container privacy need their own qualification. Optional control contract ↗ · Migration contract ↗

Build a photo gallery: open an upload, produce a derivative, then add display, metadata and transport requirements only as the product needs them.

Register → decode → transcode

1Choose the formats onceStart here

The gallery accepts JPEG and PNG and writes PNG. Register those implementations once; pass the set to each operation. with_send_encoder registers jobs that may move between threads for the later async path; with_encoder also exists for thread-affine jobs. Send does not mean writing to a destination.

Implemented · review API
Source ↗
Imports used by this source
use zencodec::CodecSet;
use zencodecs::{JpegDecoderConfig, PngDecoderConfig, PngEncoderConfig};
Show setup above ↑5 lines
use zencodec::CodecSet;
use zencodecs::{JpegDecoderConfig, PngDecoderConfig, PngEncoderConfig};

pub fn gallery_codecs() -> CodecSet {
    CodecSet::new()
        .with_decoder(JpegDecoderConfig::new())
        .with_decoder(PngDecoderConfig::new())
        .with_send_encoder(PngEncoderConfig::new())
Show continuation below ↓1 lines
}
Signatures & related contracts

Signatures from this revision · bodies omitted

Inherent methodszencodec::CodecSet
impl CodecSet {
    pub fn new() -> Self;

    pub fn with_decoder(mut self, config: impl DecoderConfig + 'static) -> Self;

    pub fn with_encoder<C>(mut self, config: C) -> Self
    where
        C: EncoderConfig + 'static,
        <C::Job as EncodeJob>::Enc: Encoder + Send,
        <C::Job as EncodeJob>::AnimationFrameEnc: AnimationFrameEncoder;

    pub fn with_send_encoder<C>(mut self, config: C) -> Self
    where
        C: EncoderConfig + 'static,
        C::Job: Send,
        <C::Job as EncodeJob>::Enc: Encoder + Send,
        <C::Job as EncodeJob>::AnimationFrameEnc: AnimationFrameEncoder;

    pub fn with_limits(mut self, limits: ResourceLimits) -> Self;

    pub fn with_pulse(mut self, pulse: how_far::SharedPulse) -> Self;

    pub fn pulse(&self) -> &dyn crate::Pulse;
}

Selected methods · 42 more in the full contract.

Read defaults, imports and full contract ↗
2Open one uploadAdd this need

The caller has encoded bytes and wants their native pixels. DecodeRequest detects the format and uses the set’s default control; the result retains pixel interpretation and the operation report.

Implemented · review API
Source ↗
Imports used by this source
use zencodec::{
    At, CodecSet,
    decode::DecodeOutput,
    operation::{DecodeRequest, OperationError, Outcome},
};
Show setup above ↑11 lines
use zencodec::{
    At, CodecSet,
    decode::DecodeOutput,
    operation::{DecodeRequest, OperationError, Outcome},
};

pub fn preview(
    codecs: &CodecSet,
    bytes: &[u8],
) -> Result<Outcome<DecodeOutput>, At<OperationError>> {
    let image = DecodeRequest::new().decode(codecs, bytes)?;
Show continuation below ↓3 lines
    // Keep image.pixels().descriptor() and image.info().source_color with the samples.
    Ok(image)
}
Signatures & related contracts

Signatures from this revision · bodies omitted

Inherent methodszencodec::operation::DecodeRequest
impl DecodeRequest {
    pub const fn new() -> Self;

    pub fn with_output(mut self, descriptor: PixelDescriptor) -> Self;

    pub fn preferring_output(mut self, descriptors: impl Into<Vec<PixelDescriptor>>) -> Self;

    pub const fn with_policy(mut self, policy: DecodePolicy) -> Self;

    pub fn decode(
        &self,
        codecs: &CodecSet,
        input: &[u8],
    ) -> Result<Outcome<DecodeOutput>, At<OperationError>>;

    pub fn start_controlled(
        &self,
        codecs: &CodecSet,
        storage: crate::storage::StorageBudget,
    ) -> Result<Box<crate::decode::DynControlledDecoder>, At<OperationError>>;
}

Selected methods · 1 more in the full contract.

Read defaults, imports and full contract ↗
Inherent methodszencodec::operation::Outcome
impl<T> Outcome<T> {
    pub const fn output(&self) -> &T;

    pub fn into_output(self) -> T;

    pub fn into_parts(self) -> (T, OperationReport);

    pub const fn report(&self) -> &OperationReport;
}

Selected methods · 1 more in the full contract.

Read defaults, imports and full contract ↗
Inherent methodszencodec::decode::DecodeOutput
impl DecodeOutput {
    pub fn pixels(&self) -> PixelSlice<'_>;

    pub fn into_buffer(self) -> PixelBuffer;

    pub fn into_parts(self) -> (PixelBuffer, DecodeDetails);

    pub fn from_parts(pixels: PixelBuffer, details: DecodeDetails) -> Self;

    pub fn info(&self) -> &ImageInfo;

    pub fn width(&self) -> u32;

    pub fn height(&self) -> u32;
}

Selected methods · 13 more in the full contract.

Read defaults, imports and full contract ↗
3Save a derivativeAdd this need

Now the gallery needs a PNG file. TranscodeRequest belongs to zencodecs, which composes the codecs and conversion. The default policy selects a permitted route; missing optional facts alone do not require refusal. Required preservation still applies.

Implemented · review API
Source ↗
Imports used by this source
use zencodec::{At, CodecSet, ImageFormat, encode::EncodeOutput, operation::Outcome};
use zencodecs::checked_transcode::{Error, TranscodeRequest};
Show setup above ↑5 lines
use zencodec::{At, CodecSet, ImageFormat, encode::EncodeOutput, operation::Outcome};
use zencodecs::checked_transcode::{Error, TranscodeRequest};

pub fn derivative(codecs: &CodecSet, bytes: &[u8]) -> Result<Outcome<EncodeOutput>, At<Error>> {
    let output = TranscodeRequest::new(ImageFormat::Png).transcode(codecs, bytes)?;
Show continuation below ↓5 lines
    // The built-in policy selects a permitted route without a custom callback.
    // Default retention can refuse when the target cannot preserve a reported field.
    // No file or HTTP body has been written by this operation.
    Ok(output)
}
Signatures & related contracts
Pinned dependency · imazen/zenpipe
use zencodec::{
    At, CodecSet, ImageFormat,
    encode::EncodeOutput,
    operation::{self, OperationReport, Outcome, OperationPolicy, PolicySet, MetadataTransfer},
    storage::StorageBudget,
};
use zencodec_io::{DriveOptions, tokio_io::AsyncSource};
use zenpixels::{ConvertOptions, PixelFormat};
use zenpixels_convert::{OutputProfile, cms::PluggableCms};
use zencodecs::checked_transcode::{Error, Stage, TranscodeRequest};

impl<'a> TranscodeRequest<'a> {
    pub fn new(format: ImageFormat) -> Self;

    pub fn preferring_fidelity(mut self, targets: Vec<zencodec::encode::Fidelity>) -> Self;

    pub fn transcode(
        &self,
        codecs: &CodecSet,
        input: &[u8],
    ) -> Result<Outcome<EncodeOutput>, At<Error>>;

    pub fn transcode_from_read_at<S: zencodec::source::ReadAt + ?Sized>(
        &self,
        codecs: &CodecSet,
        source: &mut S,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>;

    pub fn transcode_from_read_at_with<S, R>(
        &self,
        codecs: &CodecSet,
        source: &mut S,
        resolver: &mut R,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>
    where
        S: zencodec::source::ReadAt + ?Sized,
        R: OperationPolicy + ?Sized;

    pub fn transcode_with<R: OperationPolicy + ?Sized>(
        &self,
        codecs: &CodecSet,
        input: &[u8],
        resolver: &mut R,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>;

    pub fn with_cms(mut self, cms: &'a dyn PluggableCms) -> Self;

    pub fn with_decode(mut self, request: operation::DecodeRequest) -> Self;

    pub fn with_encode_policy(mut self, policy: zencodec::encode::EncodePolicy) -> Self;

    pub fn with_fidelity(mut self, target: zencodec::encode::Fidelity) -> Self;

    pub fn with_io_options(mut self, options: DriveOptions) -> Self;

    pub fn with_metadata(mut self, policy: MetadataTransfer) -> Self;

    pub fn with_output(
        mut self,
        profile: OutputProfile,
        format: PixelFormat,
        options: ConvertOptions,
    ) -> Self;

    pub fn with_output_limit(mut self, bytes: usize) -> Self;

    pub fn with_policy_set(mut self, policy: PolicySet) -> Self;

    pub fn with_storage(mut self, storage: StorageBudget) -> Self;

    pub fn with_strip_rows(mut self, rows: u32) -> Self;
}

impl TranscodeRequest<'_> {
    pub async fn transcode_async<S: AsyncSource + ?Sized>(
        &self,
        codecs: &CodecSet,
        source: &mut S,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>;

    pub async fn transcode_async_with<S, R>(
        &self,
        codecs: &CodecSet,
        source: &mut S,
        resolver: &mut R,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>
    where
        S: AsyncSource + ?Sized,
        R: OperationPolicy + ?Sized;
}

impl Error {
    pub fn stage(&self) -> Stage;

    pub fn report(&self) -> &OperationReport;
}
Source at f0322466 ↗
4Write the completed PNG fileAdd this need

Keep the PNG request and its mandatory policy from the preceding recipes. Encode successfully before opening the destination, then check the same operation Pulse and write the completed bytes synchronously. The successful operation report remains available. A stopped or refused encode must not create or truncate the destination; an I/O failure after writing starts can leave partial bytes.

Native JPEG/PNG cancellation and publication controls
Source ↗
Imports used by this source
use std::path::Path;
use zencodec::{
    At, CodecSet,
    encode::EncodeOutput,
    operation::{OperationError, OperationPolicy, Outcome},
};
use zencodecs::checked_transcode::TranscodeRequest;
Show setup above ↑30 lines
use std::path::Path;
use zencodec::{
    At, CodecSet,
    encode::EncodeOutput,
    operation::{OperationError, OperationPolicy, Outcome},
};
use zencodecs::checked_transcode::TranscodeRequest;

pub fn publish_completed(
    codecs: &CodecSet,
    output: &EncodeOutput,
    destination: &Path,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    // Cancellation observed here must not create or truncate the file.
    codecs
        .pulse()
        .check()
        .map_err(|reason| At::wrap(OperationError::Stopped(reason)))?;
    std::fs::write(destination, output.data())?;
    Ok(())
}

pub fn transcode_to_file<P: OperationPolicy + ?Sized>(
    request: &TranscodeRequest<'_>,
    codecs: &CodecSet,
    bytes: &[u8],
    destination: &Path,
    policy: &mut P,
) -> Result<Outcome<EncodeOutput>, Box<dyn std::error::Error + Send + Sync>> {
    codecs
        .pulse()
        .check()
        .map_err(|reason| At::wrap(OperationError::Stopped(reason)))?;
    let output = request.transcode_with(codecs, bytes, policy)?;
    publish_completed(codecs, output.output(), destination)?;
Show continuation below ↓7 lines
    // Pass the PNG request from the preceding recipes, along with its
    // mandatory PolicySet or BoundPolicy. No policy is replaced or relaxed.
    // The successful report remains available to the application.
    // fs::write is synchronous: cancellation during that call is not
    // preemptive, and an I/O failure may leave partial destination bytes.
    Ok(output)
}
Signatures & related contracts

Signatures from this revision · bodies omitted

Inherent methodszencodec::CodecSet
impl CodecSet {
    pub fn new() -> Self;

    pub fn with_decoder(mut self, config: impl DecoderConfig + 'static) -> Self;

    pub fn with_encoder<C>(mut self, config: C) -> Self
    where
        C: EncoderConfig + 'static,
        <C::Job as EncodeJob>::Enc: Encoder + Send,
        <C::Job as EncodeJob>::AnimationFrameEnc: AnimationFrameEncoder;

    pub fn with_send_encoder<C>(mut self, config: C) -> Self
    where
        C: EncoderConfig + 'static,
        C::Job: Send,
        <C::Job as EncodeJob>::Enc: Encoder + Send,
        <C::Job as EncodeJob>::AnimationFrameEnc: AnimationFrameEncoder;

    pub fn with_limits(mut self, limits: ResourceLimits) -> Self;

    pub fn with_pulse(mut self, pulse: how_far::SharedPulse) -> Self;

    pub fn pulse(&self) -> &dyn crate::Pulse;
}

Selected methods · 42 more in the full contract.

Read defaults, imports and full contract ↗
Inherent methodszencodec::operation::Outcome
impl<T> Outcome<T> {
    pub const fn output(&self) -> &T;

    pub fn into_output(self) -> T;

    pub fn into_parts(self) -> (T, OperationReport);

    pub const fn report(&self) -> &OperationReport;
}

Selected methods · 1 more in the full contract.

Read defaults, imports and full contract ↗
Inherent methodszencodec::encode::EncodeOutput
impl EncodeOutput {
    pub fn data(&self) -> &[u8];

    pub fn into_vec(self) -> Vec<u8>;

    pub fn format(&self) -> ImageFormat;
}

Selected methods · 12 more in the full contract.

Read defaults, imports and full contract ↗
Pinned dependency · imazen/zenpipe
use zencodec::{
    At, CodecSet, ImageFormat,
    encode::EncodeOutput,
    operation::{self, OperationReport, Outcome, OperationPolicy, PolicySet, MetadataTransfer},
    storage::StorageBudget,
};
use zencodec_io::{DriveOptions, tokio_io::AsyncSource};
use zenpixels::{ConvertOptions, PixelFormat};
use zenpixels_convert::{OutputProfile, cms::PluggableCms};
use zencodecs::checked_transcode::{Error, Stage, TranscodeRequest};

impl<'a> TranscodeRequest<'a> {
    pub fn new(format: ImageFormat) -> Self;

    pub fn preferring_fidelity(mut self, targets: Vec<zencodec::encode::Fidelity>) -> Self;

    pub fn transcode(
        &self,
        codecs: &CodecSet,
        input: &[u8],
    ) -> Result<Outcome<EncodeOutput>, At<Error>>;

    pub fn transcode_from_read_at<S: zencodec::source::ReadAt + ?Sized>(
        &self,
        codecs: &CodecSet,
        source: &mut S,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>;

    pub fn transcode_from_read_at_with<S, R>(
        &self,
        codecs: &CodecSet,
        source: &mut S,
        resolver: &mut R,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>
    where
        S: zencodec::source::ReadAt + ?Sized,
        R: OperationPolicy + ?Sized;

    pub fn transcode_with<R: OperationPolicy + ?Sized>(
        &self,
        codecs: &CodecSet,
        input: &[u8],
        resolver: &mut R,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>;

    pub fn with_cms(mut self, cms: &'a dyn PluggableCms) -> Self;

    pub fn with_decode(mut self, request: operation::DecodeRequest) -> Self;

    pub fn with_encode_policy(mut self, policy: zencodec::encode::EncodePolicy) -> Self;

    pub fn with_fidelity(mut self, target: zencodec::encode::Fidelity) -> Self;

    pub fn with_io_options(mut self, options: DriveOptions) -> Self;

    pub fn with_metadata(mut self, policy: MetadataTransfer) -> Self;

    pub fn with_output(
        mut self,
        profile: OutputProfile,
        format: PixelFormat,
        options: ConvertOptions,
    ) -> Self;

    pub fn with_output_limit(mut self, bytes: usize) -> Self;

    pub fn with_policy_set(mut self, policy: PolicySet) -> Self;

    pub fn with_storage(mut self, storage: StorageBudget) -> Self;

    pub fn with_strip_rows(mut self, rows: u32) -> Self;
}

impl TranscodeRequest<'_> {
    pub async fn transcode_async<S: AsyncSource + ?Sized>(
        &self,
        codecs: &CodecSet,
        source: &mut S,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>;

    pub async fn transcode_async_with<S, R>(
        &self,
        codecs: &CodecSet,
        source: &mut S,
        resolver: &mut R,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>
    where
        S: AsyncSource + ?Sized,
        R: OperationPolicy + ?Sized;
}

impl Error {
    pub fn stage(&self) -> Stage;

    pub fn report(&self) -> &OperationReport;
}
Source at f0322466 ↗

Owned result → rows → final metadata

1Separate pixels only when processing needs themStart here

The application first decodes an owned image. into_parts moves the raster and all result details without cloning them. Keep both owners: dropping codec extras can discard information needed by a later transcode.

Move pixels and details without cloning their owners
Source ↗
Imports used by this source
use crate::fixtures;
use std::borrow::Cow;
use zencodec::{
    CodecSet,
    decode::{Decode, DecodeJob, DecoderConfig},
    operation::{DecodeRequest, OperationError},
};
use zenjpeg::JpegDecoderConfig;
use zenpixels::PixelDescriptor;
Show preceding definitions ↑146 lines
//! Native counterexample to treating unconfigured output prediction as admission.
use crate::fixtures;
use std::borrow::Cow;
use zencodec::{
    CodecSet,
    decode::{Decode, DecodeJob, DecoderConfig},
    operation::{DecodeRequest, OperationError},
};
use zenjpeg::JpegDecoderConfig;
use zenpixels::PixelDescriptor;

#[test]
fn negotiated_output_must_not_be_rejected_by_native_prediction() {
    for progressive in [false, true] {
        let base = fixtures::base(progressive);
        let mut bytes = base[..2].to_vec();
        bytes.extend(fixtures::descriptive());
        bytes.extend_from_slice(&base[2..]);
        let config = JpegDecoderConfig::new();
        let predicted = config.clone().job().output_info(&bytes).unwrap();
        let requested = PixelDescriptor::RGBA8_SRGB;
        assert_ne!(predicted.native_format, requested);
        let expected = config
            .clone()
            .job()
            .decoder(Cow::Borrowed(&bytes), &[requested])
            .unwrap()
            .decode()
            .unwrap();
        let codecs = CodecSet::new().with_decoder(config);
        let actual = DecodeRequest::new()
            .with_output(requested)
            .decode(&codecs, &bytes)
            .unwrap();
        assert_eq!(actual.pixels().descriptor(), requested);
        assert_eq!(actual.metadata(), expected.metadata());
        assert_eq!(actual.info().orientation, expected.info().orientation);
        assert_eq!(actual.width(), expected.width());
        assert_eq!(actual.height(), expected.height());
        for row in 0..actual.height() {
            assert_eq!(actual.pixels().row(row), expected.pixels().row(row));
        }
    }
}

#[test]
fn required_output_keeps_final_check_when_native_falls_back() {
    let bytes = fixtures::base(false);
    let codecs = CodecSet::new().with_decoder(JpegDecoderConfig::new());
    let requested = PixelDescriptor::RGB16_SRGB;
    let error = DecodeRequest::new()
        .with_output(requested)
        .decode(&codecs, &bytes)
        .unwrap_err();
    assert!(matches!(
        zencodec::find_cause::<OperationError>(&error),
        Some(OperationError::OutputDescriptor { requested: got, actual })
            if *got == requested && *actual != requested
    ));
}

#[test]
fn required_native_layouts_preserve_samples_and_metadata_across_scan_modes() {
    for progressive in [false, true] {
        let base = fixtures::base(progressive);
        let mut bytes = base[..2].to_vec();
        bytes.extend(fixtures::descriptive());
        bytes.extend_from_slice(&base[2..]);
        for requested in [
            PixelDescriptor::RGB8_SRGB,
            PixelDescriptor::RGBA8_SRGB,
            PixelDescriptor::BGRA8_SRGB,
            PixelDescriptor::RGBX8_SRGB,
            PixelDescriptor::BGRX8_SRGB,
            PixelDescriptor::RGBF32_LINEAR,
            PixelDescriptor::RGBAF32_LINEAR,
        ] {
            let config = JpegDecoderConfig::new();
            let expected = config
                .clone()
                .job()
                .decoder(Cow::Borrowed(&bytes), &[requested])
                .unwrap()
                .decode()
                .unwrap();
            assert_eq!(
                expected.pixels().descriptor(),
                requested,
                "native output differs: progressive={progressive}, requested={requested:?}"
            );
            let codecs = CodecSet::new().with_decoder(config);
            let actual = DecodeRequest::new()
                .with_output(requested)
                .decode(&codecs, &bytes)
                .unwrap();
            assert_eq!(actual.pixels().descriptor(), requested);
            assert_eq!(actual.metadata(), expected.metadata());
            assert_eq!(actual.info().orientation, expected.info().orientation);
            assert_eq!(
                (actual.width(), actual.height()),
                (expected.width(), expected.height())
            );
            for row in 0..actual.height() {
                assert_eq!(actual.pixels().row(row), expected.pixels().row(row));
            }
        }
    }
}

#[test]
fn ranked_native_output_does_not_promote_a_later_float_preference() {
    for progressive in [false, true] {
        let bytes = fixtures::base(progressive);
        for preferred in [
            [PixelDescriptor::RGB8_SRGB, PixelDescriptor::RGBF32_LINEAR],
            [PixelDescriptor::RGBA8_SRGB, PixelDescriptor::RGBAF32_LINEAR],
            [PixelDescriptor::RGBF32_LINEAR, PixelDescriptor::RGB8_SRGB],
            [PixelDescriptor::RGBAF32_LINEAR, PixelDescriptor::RGBA8_SRGB],
        ] {
            let config = JpegDecoderConfig::new();
            let expected = config
                .clone()
                .job()
                .decoder(Cow::Borrowed(&bytes), &preferred[..1])
                .unwrap()
                .decode()
                .unwrap();
            let actual = DecodeRequest::new()
                .preferring_output(preferred.to_vec())
                .decode(&CodecSet::new().with_decoder(config), &bytes)
                .unwrap();
            assert_eq!(
                actual.pixels().descriptor(),
                expected.pixels().descriptor(),
                "progressive={progressive}, preferred={preferred:?}"
            );
            assert_eq!(actual.metadata(), expected.metadata());
            for row in 0..actual.height() {
                assert_eq!(actual.pixels().row(row), expected.pixels().row(row));
            }
        }
    }
}

#[test]
fn native_owned_result_split_preserves_pixels_metadata_and_all_extras() {
    for progressive in [false, true] {
        let base = fixtures::base(progressive);
        let mut bytes = base[..2].to_vec();
        bytes.extend(fixtures::descriptive());
        bytes.extend_from_slice(&base[2..]);
        let output = JpegDecoderConfig::new()
            .job()
            .decoder(Cow::Borrowed(&bytes), &[PixelDescriptor::RGBA8_SRGB])
            .unwrap()
            .decode()
            .unwrap();
        let metadata = output.metadata();
        let pixels = output.pixels().row(0).as_ptr();
        let source = output.source_encoding_details().unwrap() as *const _ as *const ();
        let extras = output.extras::<zenjpeg::decoder::DecodedExtras>().unwrap() as *const _;
        let count = output.extensions().len();
        let (buffer, details) = output.into_parts();
        assert_eq!(buffer.as_slice().row(0).as_ptr(), pixels);
        assert_eq!(details.info.metadata(), metadata);
        assert_eq!(
            details.source_encoding.as_deref().unwrap() as *const _ as *const (),
            source
        );
        assert_eq!(
            details
                .extensions
                .get::<zenjpeg::decoder::DecodedExtras>()
                .unwrap() as *const _,
            extras
        );
        assert_eq!(details.extensions.len(), count);
        let restored = zencodec::decode::DecodeOutput::from_parts(buffer, details);
        assert_eq!(restored.pixels().row(0).as_ptr(), pixels);
        assert_eq!(restored.metadata(), metadata);
        assert_eq!(
            restored
                .extras::<zenjpeg::decoder::DecodedExtras>()
                .unwrap() as *const _,
            extras
        );
    }
}
Show following definitions ↓142 lines


/// Private completion transport witness. The native one-shot decode has already
/// happened; this deliberately makes no early-admission or streaming-RAM claim.
struct OwnedResultRows {
    output: Option<zencodec::decode::DecodeOutput>,
    details: Option<zencodec::decode::DecodeDetails>,
}
impl zencodec::decode::IncrementalDecode for OwnedResultRows {
    type Error = std::io::Error;
    fn step(
        &mut self,
        _: zencodec::decode::Input<'_>,
        sink: &mut dyn zencodec::decode::DecodeRowSink,
        _: Option<&dyn zencodec::Pulse>,
    ) -> Result<zencodec::decode::Progress, zencodec::decode::StepError<Self::Error>> {
        use zencodec::decode::{Event, Progress, StepError};
        let output = self.output.take().ok_or(StepError::Finished)?;
        let info = zencodec::decode::OutputInfo::full_decode(
            output.width(),
            output.height(),
            output.descriptor(),
        );
        let (pixels, details) = output.into_parts();
        sink.begin(info.width, info.height, info.native_format)
            .map_err(StepError::Sink)?;
        for y in 0..info.height {
            sink.provide_next_buffer(y, 1, info.width, info.native_format)
                .map_err(StepError::Sink)?
                .row_mut(0)
                .copy_from_slice(pixels.as_slice().row(y));
        }
        sink.finish().map_err(StepError::Sink)?;
        self.details = Some(details);
        Ok(Progress::new(0, Event::Done(info)))
    }
    fn requested(&self) -> &[zencodec::decode::InputRequest] {
        &[]
    }
    fn info(&self) -> Option<&zencodec::ImageInfo> {
        None
    }
    fn take_details(&mut self) -> Option<zencodec::decode::DecodeDetails> {
        self.details.take()
    }
}

#[test]
fn native_owned_details_survive_terminal_erasure_and_row_delivery() {
    use zencodec::decode::{Input, erase_incremental};
    for progressive in [false, true] {
        let bytes = fixtures::base(progressive);
        let output = JpegDecoderConfig::new()
            .job()
            .decoder(Cow::Borrowed(&bytes), &[PixelDescriptor::RGBA8_SRGB])
            .unwrap()
            .decode()
            .unwrap();
        let metadata = output.metadata();
        let source = output.source_encoding_details().unwrap() as *const _ as *const ();
        let expected: Vec<_> = (0..output.height())
            .map(|y| output.pixels().row(y).to_vec())
            .collect();
        let mut decoder = erase_incremental(OwnedResultRows {
            output: Some(output),
            details: None,
        });
        assert!(decoder.take_details().is_none());
        let mut sink = zencodec_io::CollectSink::new();
        decoder.step(Input::empty(), &mut sink, None).unwrap();
        let details = decoder.take_details().unwrap();
        assert_eq!(details.info.metadata(), metadata);
        assert_eq!(
            details.source_encoding.as_deref().unwrap() as *const _ as *const (),
            source
        );
        for (y, row) in expected.iter().enumerate() {
            assert_eq!(sink.as_pixel_slice().unwrap().row(y as u32), row);
        }
        assert!(decoder.take_details().is_none());
    }
}

#[test]
fn native_controlled_completion_retains_descriptive_metadata_and_extras() {
    use zencodec::decode::IncrementalDecode;
    use zencodec::operation::PolicySet;
    use zencodec::storage::StorageBudget;
    use zencodec_io::{CollectSink, PolicyDecoder, drive_from_slice};
    for progressive in [false, true] {
        let base = fixtures::base(progressive);
        let mut bytes = base[..2].to_vec();
        bytes.extend(fixtures::descriptive());
        bytes.extend_from_slice(&base[2..]);
        let config = JpegDecoderConfig::new();
        let direct = config
            .clone()
            .job()
            .decoder(Cow::Borrowed(&bytes), &[PixelDescriptor::RGBA8_SRGB])
            .unwrap()
            .decode()
            .unwrap();
        let codecs = CodecSet::new().with_decoder(config);
        let native = DecodeRequest::new()
            .with_output(PixelDescriptor::RGBA8_SRGB)
            .start_controlled(&codecs, StorageBudget::new(1 << 20, 4))
            .unwrap();
        let mut session = PolicyDecoder::new(native, PolicySet::new());
        let mut sink = CollectSink::new();
        assert!(session.take_details().is_none());
        drive_from_slice(&mut session, &bytes, &mut sink, None, 7).unwrap();
        let details = session.take_details().expect("real native completion");
        assert!(session.take_details().is_none());
        assert_eq!(details.info.metadata(), direct.metadata());
        assert_eq!(details.info.orientation, direct.info().orientation);
        assert!(details.source_encoding.is_some());
        let actual = details
            .extensions
            .get::<zenjpeg::decoder::DecodedExtras>()
            .unwrap();
        let expected = direct.extras::<zenjpeg::decoder::DecodedExtras>().unwrap();
        assert_eq!(actual.segments().len(), expected.segments().len());
        for (a, b) in actual.segments().iter().zip(expected.segments()) {
            assert_eq!(
                (a.marker, a.segment_type, &a.data),
                (b.marker, b.segment_type, &b.data)
            );
        }
        let storage = sink.bytes().as_ptr();
        let output = zencodec::decode::DecodeOutput::from_parts(
            sink.into_buffer().expect("driver finished the sink"),
            details,
        );
        assert_eq!(output.pixels().as_strided_bytes().as_ptr(), storage);
        assert_eq!(output.metadata(), direct.metadata());
        assert_eq!(output.pixels().descriptor(), direct.pixels().descriptor());
        for y in 0..direct.height() {
            assert_eq!(output.pixels().row(y), direct.pixels().row(y));
        }
    }
}
Signatures & related contracts

Signatures from this revision · bodies omitted

Inherent methodszencodec::decode::DecodeOutput
impl DecodeOutput {
    pub fn pixels(&self) -> PixelSlice<'_>;

    pub fn into_buffer(self) -> PixelBuffer;

    pub fn into_parts(self) -> (PixelBuffer, DecodeDetails);

    pub fn from_parts(pixels: PixelBuffer, details: DecodeDetails) -> Self;

    pub fn info(&self) -> &ImageInfo;

    pub fn width(&self) -> u32;

    pub fn height(&self) -> u32;
}

Selected methods · 13 more in the full contract.

Read defaults, imports and full contract ↗
2Recover final details after consuming rowsAdd this need

The application now processes rows through a sink. Drive the real JPEG decoder to successful completion, then take_details once. The test compares native metadata and every row. None means that details are unavailable; it never certifies that the file contains no private data.

Real JPEG controlled producer, registry, row sink and one-time completion
Source ↗
Imports used by this source
use crate::fixtures;
use std::borrow::Cow;
use zencodec::{
    CodecSet,
    decode::{Decode, DecodeJob, DecoderConfig},
    operation::{DecodeRequest, OperationError},
};
use zenjpeg::JpegDecoderConfig;
use zenpixels::PixelDescriptor;
use zencodec::decode::{Event, Progress, StepError};
use zencodec::decode::{Input, erase_incremental};
Show preceding definitions ↑272 lines
//! Native counterexample to treating unconfigured output prediction as admission.
use crate::fixtures;
use std::borrow::Cow;
use zencodec::{
    CodecSet,
    decode::{Decode, DecodeJob, DecoderConfig},
    operation::{DecodeRequest, OperationError},
};
use zenjpeg::JpegDecoderConfig;
use zenpixels::PixelDescriptor;

#[test]
fn negotiated_output_must_not_be_rejected_by_native_prediction() {
    for progressive in [false, true] {
        let base = fixtures::base(progressive);
        let mut bytes = base[..2].to_vec();
        bytes.extend(fixtures::descriptive());
        bytes.extend_from_slice(&base[2..]);
        let config = JpegDecoderConfig::new();
        let predicted = config.clone().job().output_info(&bytes).unwrap();
        let requested = PixelDescriptor::RGBA8_SRGB;
        assert_ne!(predicted.native_format, requested);
        let expected = config
            .clone()
            .job()
            .decoder(Cow::Borrowed(&bytes), &[requested])
            .unwrap()
            .decode()
            .unwrap();
        let codecs = CodecSet::new().with_decoder(config);
        let actual = DecodeRequest::new()
            .with_output(requested)
            .decode(&codecs, &bytes)
            .unwrap();
        assert_eq!(actual.pixels().descriptor(), requested);
        assert_eq!(actual.metadata(), expected.metadata());
        assert_eq!(actual.info().orientation, expected.info().orientation);
        assert_eq!(actual.width(), expected.width());
        assert_eq!(actual.height(), expected.height());
        for row in 0..actual.height() {
            assert_eq!(actual.pixels().row(row), expected.pixels().row(row));
        }
    }
}

#[test]
fn required_output_keeps_final_check_when_native_falls_back() {
    let bytes = fixtures::base(false);
    let codecs = CodecSet::new().with_decoder(JpegDecoderConfig::new());
    let requested = PixelDescriptor::RGB16_SRGB;
    let error = DecodeRequest::new()
        .with_output(requested)
        .decode(&codecs, &bytes)
        .unwrap_err();
    assert!(matches!(
        zencodec::find_cause::<OperationError>(&error),
        Some(OperationError::OutputDescriptor { requested: got, actual })
            if *got == requested && *actual != requested
    ));
}

#[test]
fn required_native_layouts_preserve_samples_and_metadata_across_scan_modes() {
    for progressive in [false, true] {
        let base = fixtures::base(progressive);
        let mut bytes = base[..2].to_vec();
        bytes.extend(fixtures::descriptive());
        bytes.extend_from_slice(&base[2..]);
        for requested in [
            PixelDescriptor::RGB8_SRGB,
            PixelDescriptor::RGBA8_SRGB,
            PixelDescriptor::BGRA8_SRGB,
            PixelDescriptor::RGBX8_SRGB,
            PixelDescriptor::BGRX8_SRGB,
            PixelDescriptor::RGBF32_LINEAR,
            PixelDescriptor::RGBAF32_LINEAR,
        ] {
            let config = JpegDecoderConfig::new();
            let expected = config
                .clone()
                .job()
                .decoder(Cow::Borrowed(&bytes), &[requested])
                .unwrap()
                .decode()
                .unwrap();
            assert_eq!(
                expected.pixels().descriptor(),
                requested,
                "native output differs: progressive={progressive}, requested={requested:?}"
            );
            let codecs = CodecSet::new().with_decoder(config);
            let actual = DecodeRequest::new()
                .with_output(requested)
                .decode(&codecs, &bytes)
                .unwrap();
            assert_eq!(actual.pixels().descriptor(), requested);
            assert_eq!(actual.metadata(), expected.metadata());
            assert_eq!(actual.info().orientation, expected.info().orientation);
            assert_eq!(
                (actual.width(), actual.height()),
                (expected.width(), expected.height())
            );
            for row in 0..actual.height() {
                assert_eq!(actual.pixels().row(row), expected.pixels().row(row));
            }
        }
    }
}

#[test]
fn ranked_native_output_does_not_promote_a_later_float_preference() {
    for progressive in [false, true] {
        let bytes = fixtures::base(progressive);
        for preferred in [
            [PixelDescriptor::RGB8_SRGB, PixelDescriptor::RGBF32_LINEAR],
            [PixelDescriptor::RGBA8_SRGB, PixelDescriptor::RGBAF32_LINEAR],
            [PixelDescriptor::RGBF32_LINEAR, PixelDescriptor::RGB8_SRGB],
            [PixelDescriptor::RGBAF32_LINEAR, PixelDescriptor::RGBA8_SRGB],
        ] {
            let config = JpegDecoderConfig::new();
            let expected = config
                .clone()
                .job()
                .decoder(Cow::Borrowed(&bytes), &preferred[..1])
                .unwrap()
                .decode()
                .unwrap();
            let actual = DecodeRequest::new()
                .preferring_output(preferred.to_vec())
                .decode(&CodecSet::new().with_decoder(config), &bytes)
                .unwrap();
            assert_eq!(
                actual.pixels().descriptor(),
                expected.pixels().descriptor(),
                "progressive={progressive}, preferred={preferred:?}"
            );
            assert_eq!(actual.metadata(), expected.metadata());
            for row in 0..actual.height() {
                assert_eq!(actual.pixels().row(row), expected.pixels().row(row));
            }
        }
    }
}

#[test]
fn native_owned_result_split_preserves_pixels_metadata_and_all_extras() {
    for progressive in [false, true] {
        let base = fixtures::base(progressive);
        let mut bytes = base[..2].to_vec();
        bytes.extend(fixtures::descriptive());
        bytes.extend_from_slice(&base[2..]);
        let output = JpegDecoderConfig::new()
            .job()
            .decoder(Cow::Borrowed(&bytes), &[PixelDescriptor::RGBA8_SRGB])
            .unwrap()
            .decode()
            .unwrap();
        let metadata = output.metadata();
        let pixels = output.pixels().row(0).as_ptr();
        let source = output.source_encoding_details().unwrap() as *const _ as *const ();
        let extras = output.extras::<zenjpeg::decoder::DecodedExtras>().unwrap() as *const _;
        let count = output.extensions().len();
        let (buffer, details) = output.into_parts();
        assert_eq!(buffer.as_slice().row(0).as_ptr(), pixels);
        assert_eq!(details.info.metadata(), metadata);
        assert_eq!(
            details.source_encoding.as_deref().unwrap() as *const _ as *const (),
            source
        );
        assert_eq!(
            details
                .extensions
                .get::<zenjpeg::decoder::DecodedExtras>()
                .unwrap() as *const _,
            extras
        );
        assert_eq!(details.extensions.len(), count);
        let restored = zencodec::decode::DecodeOutput::from_parts(buffer, details);
        assert_eq!(restored.pixels().row(0).as_ptr(), pixels);
        assert_eq!(restored.metadata(), metadata);
        assert_eq!(
            restored
                .extras::<zenjpeg::decoder::DecodedExtras>()
                .unwrap() as *const _,
            extras
        );
    }
}

/// Private completion transport witness. The native one-shot decode has already
/// happened; this deliberately makes no early-admission or streaming-RAM claim.
struct OwnedResultRows {
    output: Option<zencodec::decode::DecodeOutput>,
    details: Option<zencodec::decode::DecodeDetails>,
}
impl zencodec::decode::IncrementalDecode for OwnedResultRows {
    type Error = std::io::Error;
    fn step(
        &mut self,
        _: zencodec::decode::Input<'_>,
        sink: &mut dyn zencodec::decode::DecodeRowSink,
        _: Option<&dyn zencodec::Pulse>,
    ) -> Result<zencodec::decode::Progress, zencodec::decode::StepError<Self::Error>> {
        use zencodec::decode::{Event, Progress, StepError};
        let output = self.output.take().ok_or(StepError::Finished)?;
        let info = zencodec::decode::OutputInfo::full_decode(
            output.width(),
            output.height(),
            output.descriptor(),
        );
        let (pixels, details) = output.into_parts();
        sink.begin(info.width, info.height, info.native_format)
            .map_err(StepError::Sink)?;
        for y in 0..info.height {
            sink.provide_next_buffer(y, 1, info.width, info.native_format)
                .map_err(StepError::Sink)?
                .row_mut(0)
                .copy_from_slice(pixels.as_slice().row(y));
        }
        sink.finish().map_err(StepError::Sink)?;
        self.details = Some(details);
        Ok(Progress::new(0, Event::Done(info)))
    }
    fn requested(&self) -> &[zencodec::decode::InputRequest] {
        &[]
    }
    fn info(&self) -> Option<&zencodec::ImageInfo> {
        None
    }
    fn take_details(&mut self) -> Option<zencodec::decode::DecodeDetails> {
        self.details.take()
    }
}

#[test]
fn native_owned_details_survive_terminal_erasure_and_row_delivery() {
    use zencodec::decode::{Input, erase_incremental};
    for progressive in [false, true] {
        let bytes = fixtures::base(progressive);
        let output = JpegDecoderConfig::new()
            .job()
            .decoder(Cow::Borrowed(&bytes), &[PixelDescriptor::RGBA8_SRGB])
            .unwrap()
            .decode()
            .unwrap();
        let metadata = output.metadata();
        let source = output.source_encoding_details().unwrap() as *const _ as *const ();
        let expected: Vec<_> = (0..output.height())
            .map(|y| output.pixels().row(y).to_vec())
            .collect();
        let mut decoder = erase_incremental(OwnedResultRows {
            output: Some(output),
            details: None,
        });
        assert!(decoder.take_details().is_none());
        let mut sink = zencodec_io::CollectSink::new();
        decoder.step(Input::empty(), &mut sink, None).unwrap();
        let details = decoder.take_details().unwrap();
        assert_eq!(details.info.metadata(), metadata);
        assert_eq!(
            details.source_encoding.as_deref().unwrap() as *const _ as *const (),
            source
        );
        for (y, row) in expected.iter().enumerate() {
            assert_eq!(sink.as_pixel_slice().unwrap().row(y as u32), row);
        }
        assert!(decoder.take_details().is_none());
    }
}

#[test]
fn native_controlled_completion_retains_descriptive_metadata_and_extras() {
    use zencodec::decode::IncrementalDecode;
    use zencodec::operation::PolicySet;
    use zencodec::storage::StorageBudget;
    use zencodec_io::{CollectSink, PolicyDecoder, drive_from_slice};
    for progressive in [false, true] {
        let base = fixtures::base(progressive);
        let mut bytes = base[..2].to_vec();
        bytes.extend(fixtures::descriptive());
        bytes.extend_from_slice(&base[2..]);
        let config = JpegDecoderConfig::new();
        let direct = config
            .clone()
            .job()
            .decoder(Cow::Borrowed(&bytes), &[PixelDescriptor::RGBA8_SRGB])
            .unwrap()
            .decode()
            .unwrap();
        let codecs = CodecSet::new().with_decoder(config);
        let native = DecodeRequest::new()
            .with_output(PixelDescriptor::RGBA8_SRGB)
            .start_controlled(&codecs, StorageBudget::new(1 << 20, 4))
            .unwrap();
        let mut session = PolicyDecoder::new(native, PolicySet::new());
        let mut sink = CollectSink::new();
        assert!(session.take_details().is_none());
        drive_from_slice(&mut session, &bytes, &mut sink, None, 7).unwrap();
        let details = session.take_details().expect("real native completion");
        assert!(session.take_details().is_none());
        assert_eq!(details.info.metadata(), direct.metadata());
        assert_eq!(details.info.orientation, direct.info().orientation);
        assert!(details.source_encoding.is_some());
        let actual = details
            .extensions
            .get::<zenjpeg::decoder::DecodedExtras>()
            .unwrap();
        let expected = direct.extras::<zenjpeg::decoder::DecodedExtras>().unwrap();
        assert_eq!(actual.segments().len(), expected.segments().len());
        for (a, b) in actual.segments().iter().zip(expected.segments()) {
            assert_eq!(
                (a.marker, a.segment_type, &a.data),
                (b.marker, b.segment_type, &b.data)
            );
        }
        let storage = sink.bytes().as_ptr();
        let output = zencodec::decode::DecodeOutput::from_parts(
            sink.into_buffer().expect("driver finished the sink"),
            details,
        );
        assert_eq!(output.pixels().as_strided_bytes().as_ptr(), storage);
        assert_eq!(output.metadata(), direct.metadata());
        assert_eq!(output.pixels().descriptor(), direct.pixels().descriptor());
        for y in 0..direct.height() {
            assert_eq!(output.pixels().row(y), direct.pixels().row(y));
        }
    }
}
Show following definitions ↓2 lines

Signatures & related contracts

Signatures from this revision · bodies omitted

Trait methodszencodec::decode::IncrementalDecode
pub trait IncrementalDecode {
    type Error: core::error::Error + Send + Sync + 'static;

    fn step(
        &mut self,
        input: Input<'_>,
        sink: &mut dyn DecodeRowSink,
        pulse: Option<&dyn crate::Pulse>,
    ) -> Result<Progress, StepError<Self::Error>>;

    fn requested(&self) -> &[InputRequest];

    fn info(&self) -> Option<&ImageInfo>;

    fn take_details(&mut self) -> Option<crate::decode::DecodeDetails>;

    fn access_pattern(&self) -> crate::source::AccessPattern;
}

Selected methods · 1 more in the full contract.

Required methods: step, requested, info. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Inherent methodszencodec_io::CollectSink
impl CollectSink {
    pub fn new() -> Self;

    pub fn as_pixel_slice(&self) -> Option<PixelSlice<'_>>;

    pub fn into_buffer(self) -> Option<PixelBuffer>;

    pub fn to_buffer(&self) -> Option<PixelBuffer>;
}

Selected methods · 6 more in the full contract.

Read defaults, imports and full contract ↗

Preset → situational callback → reusable callback object

1Start with a presetStart here

The gallery has a fixed output rule: do not wait on a destination without a qualified retained-memory bound. Pass PolicySet directly. This executable control checks that unknown evidence refuses and names OutputWait before work starts.

Direct preset: refuse before effects and name the unmet requirement
Source ↗
Imports used by this source
use std::{cell::Cell, convert::Infallible};
use zencodec::{
    At, NoPulse,
    decode::{DecodeBoundary, InterpretationFacts, InterpretationState},
    encode::OutputTiming,
    operation::{
        BoundPolicy, Decision, DecisionCode, DecisionRecord, OutputSchedule, OutputScheduleFacts,
        OutputScheduleOffer, OutputSource, PolicyFailure, PolicyRequirement, PolicyResolver,
        PolicySet, Question, RequirementEvidence, resolve_boundary, resolve_output_schedule,
    },
};
use zenpixels::PixelDescriptor;
Show preceding definitions ↑206 lines
//! Production operation binding: requirements stay outside callback wrappers.
//!
//! Requirements belong to the operation binding, outside callback wrappers.
//! The binding delegates admission to the real shared resolver. It does not
//! solve deferred obligations across decode/processing/encode boundaries.
use std::{cell::Cell, convert::Infallible};
use zencodec::{
    At, NoPulse,
    decode::{DecodeBoundary, InterpretationFacts, InterpretationState},
    encode::OutputTiming,
    operation::{
        BoundPolicy, Decision, DecisionCode, DecisionRecord, OutputSchedule, OutputScheduleFacts,
        OutputScheduleOffer, OutputSource, PolicyFailure, PolicyRequirement, PolicyResolver,
        PolicySet, Question, RequirementEvidence, resolve_boundary, resolve_output_schedule,
    },
};
use zenpixels::PixelDescriptor;

trait ExerciseBinding {
    fn schedule(
        &mut self,
        offers: &[OutputScheduleOffer],
    ) -> Result<(usize, DecisionRecord), At<PolicyFailure>>;
    fn boundary(
        &mut self,
        state: InterpretationState,
    ) -> Result<(zencodec::decode::BoundaryAction, DecisionRecord), At<PolicyFailure>>;
}
impl<R: PolicyResolver> ExerciseBinding for BoundPolicy<R> {
    fn schedule(
        &mut self,
        offers: &[OutputScheduleOffer],
    ) -> Result<(usize, DecisionRecord), At<PolicyFailure>> {
        resolve_output_schedule(
            OutputScheduleFacts::new(OutputTiming::streaming(0), OutputSource::PullRows),
            offers,
            self,
            PolicySet::new(),
            Some(&NoPulse),
        )
    }
    fn boundary(
        &mut self,
        state: InterpretationState,
    ) -> Result<(zencodec::decode::BoundaryAction, DecisionRecord), At<PolicyFailure>> {
        resolve_boundary(
            DecodeBoundary::new(InterpretationFacts::new(
                3,
                2,
                (state == InterpretationState::Final).then_some(PixelDescriptor::RGB8_SRGB),
                state,
            ))
            .unwrap(),
            self,
            PolicySet::new(),
            Some(&NoPulse),
        )
    }
}

fn preset(_: Question<'_>) -> Result<Decision, At<Infallible>> {
    Ok(Decision::UsePreset)
}

struct Logged<R> {
    inner: R,
    calls: Cell<usize>,
}
impl<R: PolicyResolver> PolicyResolver for Logged<R> {
    type Error = R::Error;
    fn resolve(&mut self, question: Question<'_>) -> Result<Decision, At<Self::Error>> {
        self.calls.set(self.calls.get() + 1);
        self.inner.resolve(question)
    }
}

#[test]
fn bound_requirements_survive_a_callback_only_logging_wrapper() {
    let mut policy = BoundPolicy::new(
        PolicySet::new().with_output_wait_limit(8),
        Logged {
            inner: preset,
            calls: Cell::new(0),
        },
    );
    let error = policy
        .schedule(&[OutputScheduleOffer::new(OutputSchedule::FinishFirst)])
        .unwrap_err();
    assert_eq!(policy.resolver_mut().calls.get(), 0);
    let record = error.error().record();
    assert_eq!(record.code, DecisionCode::Declined);
    assert_eq!((record.offered, record.admitted), (1, 0));
    assert_eq!(
        record.unmet_requirements.iter().collect::<Vec<_>>(),
        [(PolicyRequirement::OutputWait, RequirementEvidence::Unknown)]
    );
}

#[test]
fn bound_direct_choice_sees_only_compliant_original_indices() {
    let mut offers = [OutputScheduleOffer::new(OutputSchedule::Live); 3];
    offers[1].wait_retained_upper_bound = Some(9);
    offers[2].wait_retained_upper_bound = Some(8);
    let mut policy = BoundPolicy::new(
        PolicySet::new().with_output_wait_limit(8),
        |q: Question<'_>| -> Result<Decision, At<Infallible>> {
            let Question::OutputSchedule { choices, .. } = q else {
                panic!("wrong domain")
            };
            assert_eq!(choices.len(), 1);
            assert_eq!(
                choices.first().unwrap().value().wait_retained_upper_bound,
                Some(8)
            );
            Ok(Decision::choose(choices.first().unwrap()))
        },
    );
    let (index, record) = policy.schedule(&offers).unwrap();
    assert_eq!(index, 2);
    assert_eq!((record.offered, record.admitted), (3, 1));
    assert_eq!(
        record.unmet_requirements.iter().collect::<Vec<_>>(),
        [
            (PolicyRequirement::OutputWait, RequirementEvidence::Unknown),
            (
                PolicyRequirement::OutputWait,
                RequirementEvidence::Contradicted
            ),
        ]
    );
}

#[test]
fn bound_schedule_refusal_names_the_contradiction() {
    let mut policy = BoundPolicy::new(
        PolicySet::new().require_output_schedule(OutputSchedule::Live),
        preset,
    );
    let error = policy
        .schedule(&[OutputScheduleOffer::new(OutputSchedule::FinishFirst)])
        .unwrap_err();
    assert_eq!(
        error
            .error()
            .record()
            .unmet_requirements
            .iter()
            .collect::<Vec<_>>(),
        [(
            PolicyRequirement::OutputSchedule,
            RequirementEvidence::Contradicted
        )]
    );
}

#[test]
fn binding_does_not_turn_discovery_into_discharge_of_output_obligations() {
    let mut policy = BoundPolicy::new(
        PolicySet::new().require_output_schedule(OutputSchedule::FinishFirst),
        preset,
    );
    policy.boundary(InterpretationState::Pending).unwrap();
    let error = policy.boundary(InterpretationState::Final).unwrap_err();
    assert_eq!(
        error
            .error()
            .record()
            .unmet_requirements
            .iter()
            .collect::<Vec<_>>(),
        [(
            PolicyRequirement::OutputSchedule,
            RequirementEvidence::Unknown
        )]
    );
    // A decoder adoption boundary may publish rows. Sharing this binding with
    // an encoder is not permission to postpone an unproven output obligation.
    // A future pipeline contract must prove intermediate ownership first.
}

#[test]
fn callback_failure_preserves_its_typed_cause_through_the_binding() {
    #[derive(Debug)]
    struct TenantFailure;
    impl std::fmt::Display for TenantFailure {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            f.write_str("private tenant detail")
        }
    }
    impl std::error::Error for TenantFailure {}
    let mut policy = BoundPolicy::new(
        PolicySet::new(),
        |_: Question<'_>| -> Result<Decision, At<TenantFailure>> {
            Err(At::wrap(TenantFailure).at())
        },
    );
    let error = policy
        .schedule(&[OutputScheduleOffer::new(OutputSchedule::Live)])
        .unwrap_err();
    assert_eq!(error.error().record().code, DecisionCode::CallbackFailed);
    assert!(!error.to_string().contains("private tenant detail"));
    assert!(zencodec::find_cause::<TenantFailure>(&error).is_some());
}

#[test]
fn direct_preset_names_unknown_wait_and_contradictory_schedule() {
    for (mut policy, requirement, evidence) in [
        (
            PolicySet::new().with_output_wait_limit(8),
            PolicyRequirement::OutputWait,
            RequirementEvidence::Unknown,
        ),
        (
            PolicySet::new().require_output_schedule(OutputSchedule::Live),
            PolicyRequirement::OutputSchedule,
            RequirementEvidence::Contradicted,
        ),
    ] {
        let error = resolve_output_schedule(
            OutputScheduleFacts::new(OutputTiming::AT_FINISH, OutputSource::PullRows),
            &[OutputScheduleOffer::new(OutputSchedule::FinishFirst)],
            &mut policy,
            PolicySet::new(),
            Some(&NoPulse),
        )
        .unwrap_err();
        let record = error.error().record();
        assert_eq!((record.offered, record.admitted), (1, 0));
        assert_eq!(
            record.unmet_requirements.iter().collect::<Vec<_>>(),
            [(requirement, evidence)]
        );
        assert!(!record.used_preset, "admission precedes built-in selection");
    }
}
Show following definitions ↓74 lines


#[test]
fn legacy_and_bound_requirements_intersect_without_replacing_each_other() {
    for (legacy, bound) in [
        (
            PolicySet::new().require_output_schedule(OutputSchedule::Live),
            PolicySet::new().with_output_wait_limit(8),
        ),
        (
            PolicySet::new().with_output_wait_limit(8),
            PolicySet::new().require_output_schedule(OutputSchedule::Live),
        ),
    ] {
        let mut calls = 0;
        let mut policy = BoundPolicy::new(
            bound,
            |q: Question<'_>| -> Result<Decision, At<Infallible>> {
                calls += 1;
                let Question::OutputSchedule { choices, .. } = q else {
                    panic!("wrong domain")
                };
                assert_eq!(choices.len(), 1);
                Ok(Decision::choose(choices.first().unwrap()))
            },
        );
        let mut offers = [
            OutputScheduleOffer::new(OutputSchedule::FinishFirst),
            OutputScheduleOffer::new(OutputSchedule::Live),
            OutputScheduleOffer::new(OutputSchedule::Live),
        ];
        offers[0].wait_retained_upper_bound = Some(8);
        offers[1].wait_retained_upper_bound = Some(9);
        offers[2].wait_retained_upper_bound = Some(8);
        let (index, record) = resolve_output_schedule(
            OutputScheduleFacts::new(OutputTiming::streaming(0), OutputSource::PullRows),
            &offers,
            &mut policy,
            legacy,
            Some(&NoPulse),
        )
        .unwrap();
        assert_eq!(index, 2);
        assert_eq!((record.offered, record.admitted), (3, 1));
        assert_eq!(calls, 1);
    }
}

#[test]
fn erased_callback_and_reborrowed_operation_keep_the_same_admission() {
    use zencodec::operation::OperationPolicy;
    let mut binding = BoundPolicy::new(PolicySet::new().with_output_wait_limit(8), preset);
    let erased: &mut BoundPolicy<dyn PolicyResolver<Error = Infallible>> = &mut binding;
    let mut borrowed = &mut *erased;
    let operation: &mut dyn OperationPolicy<Error = Infallible> = &mut borrowed;
    let error = resolve_output_schedule(
        OutputScheduleFacts::new(OutputTiming::streaming(0), OutputSource::PullRows),
        &[OutputScheduleOffer::new(OutputSchedule::Live)],
        operation,
        PolicySet::new(),
        Some(&NoPulse),
    )
    .unwrap_err();
    assert_eq!(
        error
            .error()
            .record()
            .unmet_requirements
            .iter()
            .collect::<Vec<_>>(),
        [(PolicyRequirement::OutputWait, RequirementEvidence::Unknown)]
    );
}
Signatures & related contracts

Signatures from this revision · bodies omitted

Inherent methodszencodec::operation::PolicySet
impl PolicySet {
    pub const fn new() -> Self;

    pub const fn prefer_lossless(mut self, enabled: bool) -> Self;

    pub const fn require_metadata(mut self, treatment: MetadataTransfer) -> Self;

    pub const fn allow_opaque_profiles(mut self, allowed: bool) -> Self;

    pub const fn with_workspace_limit(mut self, bytes: u64) -> Self;

    pub const fn allow_assumed_chroma_siting(mut self, allowed: bool) -> Self;

    pub const fn require_extended_range(mut self, required: bool) -> Self;

    pub const fn require_output_schedule(mut self, schedule: super::OutputSchedule) -> Self;

    pub const fn with_output_wait_limit(mut self, bytes: u64) -> Self;
}

Selected methods · 6 more in the full contract.

Read defaults, imports and full contract ↗
Trait methodszencodec::operation::OperationPolicy
pub trait OperationPolicy: sealed::Sealed {
    type Error: Error + Send + Sync + 'static;

    fn requirements(&self) -> Option<PolicySet>;

    fn decide(&mut self, question: Question<'_>) -> Result<Decision, At<Self::Error>>;
}

Required methods: requirements, decide. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
2Choose among admitted routesAdd this need

The gallery later needs a situational choice. Add BoundPolicy::new(requirements, callback); required limits still filter the offers first. The callback selects using the current facts without repeating those checks.

Callback: select only among compliant routes
Source ↗
Imports used by this source
use std::{cell::Cell, convert::Infallible};
use zencodec::{
    At, NoPulse,
    decode::{DecodeBoundary, InterpretationFacts, InterpretationState},
    encode::OutputTiming,
    operation::{
        BoundPolicy, Decision, DecisionCode, DecisionRecord, OutputSchedule, OutputScheduleFacts,
        OutputScheduleOffer, OutputSource, PolicyFailure, PolicyRequirement, PolicyResolver,
        PolicySet, Question, RequirementEvidence, resolve_boundary, resolve_output_schedule,
    },
};
use zenpixels::PixelDescriptor;
Show preceding definitions ↑100 lines
//! Production operation binding: requirements stay outside callback wrappers.
//!
//! Requirements belong to the operation binding, outside callback wrappers.
//! The binding delegates admission to the real shared resolver. It does not
//! solve deferred obligations across decode/processing/encode boundaries.
use std::{cell::Cell, convert::Infallible};
use zencodec::{
    At, NoPulse,
    decode::{DecodeBoundary, InterpretationFacts, InterpretationState},
    encode::OutputTiming,
    operation::{
        BoundPolicy, Decision, DecisionCode, DecisionRecord, OutputSchedule, OutputScheduleFacts,
        OutputScheduleOffer, OutputSource, PolicyFailure, PolicyRequirement, PolicyResolver,
        PolicySet, Question, RequirementEvidence, resolve_boundary, resolve_output_schedule,
    },
};
use zenpixels::PixelDescriptor;

trait ExerciseBinding {
    fn schedule(
        &mut self,
        offers: &[OutputScheduleOffer],
    ) -> Result<(usize, DecisionRecord), At<PolicyFailure>>;
    fn boundary(
        &mut self,
        state: InterpretationState,
    ) -> Result<(zencodec::decode::BoundaryAction, DecisionRecord), At<PolicyFailure>>;
}
impl<R: PolicyResolver> ExerciseBinding for BoundPolicy<R> {
    fn schedule(
        &mut self,
        offers: &[OutputScheduleOffer],
    ) -> Result<(usize, DecisionRecord), At<PolicyFailure>> {
        resolve_output_schedule(
            OutputScheduleFacts::new(OutputTiming::streaming(0), OutputSource::PullRows),
            offers,
            self,
            PolicySet::new(),
            Some(&NoPulse),
        )
    }
    fn boundary(
        &mut self,
        state: InterpretationState,
    ) -> Result<(zencodec::decode::BoundaryAction, DecisionRecord), At<PolicyFailure>> {
        resolve_boundary(
            DecodeBoundary::new(InterpretationFacts::new(
                3,
                2,
                (state == InterpretationState::Final).then_some(PixelDescriptor::RGB8_SRGB),
                state,
            ))
            .unwrap(),
            self,
            PolicySet::new(),
            Some(&NoPulse),
        )
    }
}

fn preset(_: Question<'_>) -> Result<Decision, At<Infallible>> {
    Ok(Decision::UsePreset)
}

struct Logged<R> {
    inner: R,
    calls: Cell<usize>,
}
impl<R: PolicyResolver> PolicyResolver for Logged<R> {
    type Error = R::Error;
    fn resolve(&mut self, question: Question<'_>) -> Result<Decision, At<Self::Error>> {
        self.calls.set(self.calls.get() + 1);
        self.inner.resolve(question)
    }
}

#[test]
fn bound_requirements_survive_a_callback_only_logging_wrapper() {
    let mut policy = BoundPolicy::new(
        PolicySet::new().with_output_wait_limit(8),
        Logged {
            inner: preset,
            calls: Cell::new(0),
        },
    );
    let error = policy
        .schedule(&[OutputScheduleOffer::new(OutputSchedule::FinishFirst)])
        .unwrap_err();
    assert_eq!(policy.resolver_mut().calls.get(), 0);
    let record = error.error().record();
    assert_eq!(record.code, DecisionCode::Declined);
    assert_eq!((record.offered, record.admitted), (1, 0));
    assert_eq!(
        record.unmet_requirements.iter().collect::<Vec<_>>(),
        [(PolicyRequirement::OutputWait, RequirementEvidence::Unknown)]
    );
}

#[test]
fn bound_direct_choice_sees_only_compliant_original_indices() {
    let mut offers = [OutputScheduleOffer::new(OutputSchedule::Live); 3];
    offers[1].wait_retained_upper_bound = Some(9);
    offers[2].wait_retained_upper_bound = Some(8);
    let mut policy = BoundPolicy::new(
        PolicySet::new().with_output_wait_limit(8),
        |q: Question<'_>| -> Result<Decision, At<Infallible>> {
            let Question::OutputSchedule { choices, .. } = q else {
                panic!("wrong domain")
            };
            assert_eq!(choices.len(), 1);
            assert_eq!(
                choices.first().unwrap().value().wait_retained_upper_bound,
                Some(8)
            );
            Ok(Decision::choose(choices.first().unwrap()))
        },
    );
    let (index, record) = policy.schedule(&offers).unwrap();
    assert_eq!(index, 2);
    assert_eq!((record.offered, record.admitted), (3, 1));
    assert_eq!(
        record.unmet_requirements.iter().collect::<Vec<_>>(),
        [
            (PolicyRequirement::OutputWait, RequirementEvidence::Unknown),
            (
                PolicyRequirement::OutputWait,
                RequirementEvidence::Contradicted
            ),
        ]
    );
}
Show following definitions ↓178 lines


#[test]
fn bound_schedule_refusal_names_the_contradiction() {
    let mut policy = BoundPolicy::new(
        PolicySet::new().require_output_schedule(OutputSchedule::Live),
        preset,
    );
    let error = policy
        .schedule(&[OutputScheduleOffer::new(OutputSchedule::FinishFirst)])
        .unwrap_err();
    assert_eq!(
        error
            .error()
            .record()
            .unmet_requirements
            .iter()
            .collect::<Vec<_>>(),
        [(
            PolicyRequirement::OutputSchedule,
            RequirementEvidence::Contradicted
        )]
    );
}

#[test]
fn binding_does_not_turn_discovery_into_discharge_of_output_obligations() {
    let mut policy = BoundPolicy::new(
        PolicySet::new().require_output_schedule(OutputSchedule::FinishFirst),
        preset,
    );
    policy.boundary(InterpretationState::Pending).unwrap();
    let error = policy.boundary(InterpretationState::Final).unwrap_err();
    assert_eq!(
        error
            .error()
            .record()
            .unmet_requirements
            .iter()
            .collect::<Vec<_>>(),
        [(
            PolicyRequirement::OutputSchedule,
            RequirementEvidence::Unknown
        )]
    );
    // A decoder adoption boundary may publish rows. Sharing this binding with
    // an encoder is not permission to postpone an unproven output obligation.
    // A future pipeline contract must prove intermediate ownership first.
}

#[test]
fn callback_failure_preserves_its_typed_cause_through_the_binding() {
    #[derive(Debug)]
    struct TenantFailure;
    impl std::fmt::Display for TenantFailure {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            f.write_str("private tenant detail")
        }
    }
    impl std::error::Error for TenantFailure {}
    let mut policy = BoundPolicy::new(
        PolicySet::new(),
        |_: Question<'_>| -> Result<Decision, At<TenantFailure>> {
            Err(At::wrap(TenantFailure).at())
        },
    );
    let error = policy
        .schedule(&[OutputScheduleOffer::new(OutputSchedule::Live)])
        .unwrap_err();
    assert_eq!(error.error().record().code, DecisionCode::CallbackFailed);
    assert!(!error.to_string().contains("private tenant detail"));
    assert!(zencodec::find_cause::<TenantFailure>(&error).is_some());
}

#[test]
fn direct_preset_names_unknown_wait_and_contradictory_schedule() {
    for (mut policy, requirement, evidence) in [
        (
            PolicySet::new().with_output_wait_limit(8),
            PolicyRequirement::OutputWait,
            RequirementEvidence::Unknown,
        ),
        (
            PolicySet::new().require_output_schedule(OutputSchedule::Live),
            PolicyRequirement::OutputSchedule,
            RequirementEvidence::Contradicted,
        ),
    ] {
        let error = resolve_output_schedule(
            OutputScheduleFacts::new(OutputTiming::AT_FINISH, OutputSource::PullRows),
            &[OutputScheduleOffer::new(OutputSchedule::FinishFirst)],
            &mut policy,
            PolicySet::new(),
            Some(&NoPulse),
        )
        .unwrap_err();
        let record = error.error().record();
        assert_eq!((record.offered, record.admitted), (1, 0));
        assert_eq!(
            record.unmet_requirements.iter().collect::<Vec<_>>(),
            [(requirement, evidence)]
        );
        assert!(!record.used_preset, "admission precedes built-in selection");
    }
}

#[test]
fn legacy_and_bound_requirements_intersect_without_replacing_each_other() {
    for (legacy, bound) in [
        (
            PolicySet::new().require_output_schedule(OutputSchedule::Live),
            PolicySet::new().with_output_wait_limit(8),
        ),
        (
            PolicySet::new().with_output_wait_limit(8),
            PolicySet::new().require_output_schedule(OutputSchedule::Live),
        ),
    ] {
        let mut calls = 0;
        let mut policy = BoundPolicy::new(
            bound,
            |q: Question<'_>| -> Result<Decision, At<Infallible>> {
                calls += 1;
                let Question::OutputSchedule { choices, .. } = q else {
                    panic!("wrong domain")
                };
                assert_eq!(choices.len(), 1);
                Ok(Decision::choose(choices.first().unwrap()))
            },
        );
        let mut offers = [
            OutputScheduleOffer::new(OutputSchedule::FinishFirst),
            OutputScheduleOffer::new(OutputSchedule::Live),
            OutputScheduleOffer::new(OutputSchedule::Live),
        ];
        offers[0].wait_retained_upper_bound = Some(8);
        offers[1].wait_retained_upper_bound = Some(9);
        offers[2].wait_retained_upper_bound = Some(8);
        let (index, record) = resolve_output_schedule(
            OutputScheduleFacts::new(OutputTiming::streaming(0), OutputSource::PullRows),
            &offers,
            &mut policy,
            legacy,
            Some(&NoPulse),
        )
        .unwrap();
        assert_eq!(index, 2);
        assert_eq!((record.offered, record.admitted), (3, 1));
        assert_eq!(calls, 1);
    }
}

#[test]
fn erased_callback_and_reborrowed_operation_keep_the_same_admission() {
    use zencodec::operation::OperationPolicy;
    let mut binding = BoundPolicy::new(PolicySet::new().with_output_wait_limit(8), preset);
    let erased: &mut BoundPolicy<dyn PolicyResolver<Error = Infallible>> = &mut binding;
    let mut borrowed = &mut *erased;
    let operation: &mut dyn OperationPolicy<Error = Infallible> = &mut borrowed;
    let error = resolve_output_schedule(
        OutputScheduleFacts::new(OutputTiming::streaming(0), OutputSource::PullRows),
        &[OutputScheduleOffer::new(OutputSchedule::Live)],
        operation,
        PolicySet::new(),
        Some(&NoPulse),
    )
    .unwrap_err();
    assert_eq!(
        error
            .error()
            .record()
            .unmet_requirements
            .iter()
            .collect::<Vec<_>>(),
        [(PolicyRequirement::OutputWait, RequirementEvidence::Unknown)]
    );
}
Signatures & related contracts

Signatures from this revision · bodies omitted

Inherent methodszencodec::operation::BoundPolicy
impl<R: PolicyResolver> BoundPolicy<R> {
    pub const fn new(requirements: PolicySet, resolver: R) -> Self;

    pub fn into_parts(self) -> (PolicySet, R);
}
Read defaults, imports and full contract ↗
Trait methodszencodec::operation::PolicyResolver
pub trait PolicyResolver {
    type Error: Error + Send + Sync + 'static;

    fn resolve(&mut self, question: Question<'_>) -> Result<Decision, At<Self::Error>>;
}

Required methods: resolve. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
3Keep tracing outside the requirementsAdd this need

The application now wraps its callback to count or log decisions. Put that wrapper inside BoundPolicy so forwarding the callback cannot forget mandatory requirements. Reuse the binding through a mutable borrow; operation progress stays with each operation.

Custom callback wrapper: requirements remain outside the wrapper
Source ↗
Imports used by this source
use std::{cell::Cell, convert::Infallible};
use zencodec::{
    At, NoPulse,
    decode::{DecodeBoundary, InterpretationFacts, InterpretationState},
    encode::OutputTiming,
    operation::{
        BoundPolicy, Decision, DecisionCode, DecisionRecord, OutputSchedule, OutputScheduleFacts,
        OutputScheduleOffer, OutputSource, PolicyFailure, PolicyRequirement, PolicyResolver,
        PolicySet, Question, RequirementEvidence, resolve_boundary, resolve_output_schedule,
    },
};
use zenpixels::PixelDescriptor;
Show preceding definitions ↑78 lines
//! Production operation binding: requirements stay outside callback wrappers.
//!
//! Requirements belong to the operation binding, outside callback wrappers.
//! The binding delegates admission to the real shared resolver. It does not
//! solve deferred obligations across decode/processing/encode boundaries.
use std::{cell::Cell, convert::Infallible};
use zencodec::{
    At, NoPulse,
    decode::{DecodeBoundary, InterpretationFacts, InterpretationState},
    encode::OutputTiming,
    operation::{
        BoundPolicy, Decision, DecisionCode, DecisionRecord, OutputSchedule, OutputScheduleFacts,
        OutputScheduleOffer, OutputSource, PolicyFailure, PolicyRequirement, PolicyResolver,
        PolicySet, Question, RequirementEvidence, resolve_boundary, resolve_output_schedule,
    },
};
use zenpixels::PixelDescriptor;

trait ExerciseBinding {
    fn schedule(
        &mut self,
        offers: &[OutputScheduleOffer],
    ) -> Result<(usize, DecisionRecord), At<PolicyFailure>>;
    fn boundary(
        &mut self,
        state: InterpretationState,
    ) -> Result<(zencodec::decode::BoundaryAction, DecisionRecord), At<PolicyFailure>>;
}
impl<R: PolicyResolver> ExerciseBinding for BoundPolicy<R> {
    fn schedule(
        &mut self,
        offers: &[OutputScheduleOffer],
    ) -> Result<(usize, DecisionRecord), At<PolicyFailure>> {
        resolve_output_schedule(
            OutputScheduleFacts::new(OutputTiming::streaming(0), OutputSource::PullRows),
            offers,
            self,
            PolicySet::new(),
            Some(&NoPulse),
        )
    }
    fn boundary(
        &mut self,
        state: InterpretationState,
    ) -> Result<(zencodec::decode::BoundaryAction, DecisionRecord), At<PolicyFailure>> {
        resolve_boundary(
            DecodeBoundary::new(InterpretationFacts::new(
                3,
                2,
                (state == InterpretationState::Final).then_some(PixelDescriptor::RGB8_SRGB),
                state,
            ))
            .unwrap(),
            self,
            PolicySet::new(),
            Some(&NoPulse),
        )
    }
}

fn preset(_: Question<'_>) -> Result<Decision, At<Infallible>> {
    Ok(Decision::UsePreset)
}

struct Logged<R> {
    inner: R,
    calls: Cell<usize>,
}
impl<R: PolicyResolver> PolicyResolver for Logged<R> {
    type Error = R::Error;
    fn resolve(&mut self, question: Question<'_>) -> Result<Decision, At<Self::Error>> {
        self.calls.set(self.calls.get() + 1);
        self.inner.resolve(question)
    }
}

#[test]
fn bound_requirements_survive_a_callback_only_logging_wrapper() {
    let mut policy = BoundPolicy::new(
        PolicySet::new().with_output_wait_limit(8),
        Logged {
            inner: preset,
            calls: Cell::new(0),
        },
    );
    let error = policy
        .schedule(&[OutputScheduleOffer::new(OutputSchedule::FinishFirst)])
        .unwrap_err();
    assert_eq!(policy.resolver_mut().calls.get(), 0);
    let record = error.error().record();
    assert_eq!(record.code, DecisionCode::Declined);
    assert_eq!((record.offered, record.admitted), (1, 0));
    assert_eq!(
        record.unmet_requirements.iter().collect::<Vec<_>>(),
        [(PolicyRequirement::OutputWait, RequirementEvidence::Unknown)]
    );
}
Show following definitions ↓212 lines


#[test]
fn bound_direct_choice_sees_only_compliant_original_indices() {
    let mut offers = [OutputScheduleOffer::new(OutputSchedule::Live); 3];
    offers[1].wait_retained_upper_bound = Some(9);
    offers[2].wait_retained_upper_bound = Some(8);
    let mut policy = BoundPolicy::new(
        PolicySet::new().with_output_wait_limit(8),
        |q: Question<'_>| -> Result<Decision, At<Infallible>> {
            let Question::OutputSchedule { choices, .. } = q else {
                panic!("wrong domain")
            };
            assert_eq!(choices.len(), 1);
            assert_eq!(
                choices.first().unwrap().value().wait_retained_upper_bound,
                Some(8)
            );
            Ok(Decision::choose(choices.first().unwrap()))
        },
    );
    let (index, record) = policy.schedule(&offers).unwrap();
    assert_eq!(index, 2);
    assert_eq!((record.offered, record.admitted), (3, 1));
    assert_eq!(
        record.unmet_requirements.iter().collect::<Vec<_>>(),
        [
            (PolicyRequirement::OutputWait, RequirementEvidence::Unknown),
            (
                PolicyRequirement::OutputWait,
                RequirementEvidence::Contradicted
            ),
        ]
    );
}

#[test]
fn bound_schedule_refusal_names_the_contradiction() {
    let mut policy = BoundPolicy::new(
        PolicySet::new().require_output_schedule(OutputSchedule::Live),
        preset,
    );
    let error = policy
        .schedule(&[OutputScheduleOffer::new(OutputSchedule::FinishFirst)])
        .unwrap_err();
    assert_eq!(
        error
            .error()
            .record()
            .unmet_requirements
            .iter()
            .collect::<Vec<_>>(),
        [(
            PolicyRequirement::OutputSchedule,
            RequirementEvidence::Contradicted
        )]
    );
}

#[test]
fn binding_does_not_turn_discovery_into_discharge_of_output_obligations() {
    let mut policy = BoundPolicy::new(
        PolicySet::new().require_output_schedule(OutputSchedule::FinishFirst),
        preset,
    );
    policy.boundary(InterpretationState::Pending).unwrap();
    let error = policy.boundary(InterpretationState::Final).unwrap_err();
    assert_eq!(
        error
            .error()
            .record()
            .unmet_requirements
            .iter()
            .collect::<Vec<_>>(),
        [(
            PolicyRequirement::OutputSchedule,
            RequirementEvidence::Unknown
        )]
    );
    // A decoder adoption boundary may publish rows. Sharing this binding with
    // an encoder is not permission to postpone an unproven output obligation.
    // A future pipeline contract must prove intermediate ownership first.
}

#[test]
fn callback_failure_preserves_its_typed_cause_through_the_binding() {
    #[derive(Debug)]
    struct TenantFailure;
    impl std::fmt::Display for TenantFailure {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            f.write_str("private tenant detail")
        }
    }
    impl std::error::Error for TenantFailure {}
    let mut policy = BoundPolicy::new(
        PolicySet::new(),
        |_: Question<'_>| -> Result<Decision, At<TenantFailure>> {
            Err(At::wrap(TenantFailure).at())
        },
    );
    let error = policy
        .schedule(&[OutputScheduleOffer::new(OutputSchedule::Live)])
        .unwrap_err();
    assert_eq!(error.error().record().code, DecisionCode::CallbackFailed);
    assert!(!error.to_string().contains("private tenant detail"));
    assert!(zencodec::find_cause::<TenantFailure>(&error).is_some());
}

#[test]
fn direct_preset_names_unknown_wait_and_contradictory_schedule() {
    for (mut policy, requirement, evidence) in [
        (
            PolicySet::new().with_output_wait_limit(8),
            PolicyRequirement::OutputWait,
            RequirementEvidence::Unknown,
        ),
        (
            PolicySet::new().require_output_schedule(OutputSchedule::Live),
            PolicyRequirement::OutputSchedule,
            RequirementEvidence::Contradicted,
        ),
    ] {
        let error = resolve_output_schedule(
            OutputScheduleFacts::new(OutputTiming::AT_FINISH, OutputSource::PullRows),
            &[OutputScheduleOffer::new(OutputSchedule::FinishFirst)],
            &mut policy,
            PolicySet::new(),
            Some(&NoPulse),
        )
        .unwrap_err();
        let record = error.error().record();
        assert_eq!((record.offered, record.admitted), (1, 0));
        assert_eq!(
            record.unmet_requirements.iter().collect::<Vec<_>>(),
            [(requirement, evidence)]
        );
        assert!(!record.used_preset, "admission precedes built-in selection");
    }
}

#[test]
fn legacy_and_bound_requirements_intersect_without_replacing_each_other() {
    for (legacy, bound) in [
        (
            PolicySet::new().require_output_schedule(OutputSchedule::Live),
            PolicySet::new().with_output_wait_limit(8),
        ),
        (
            PolicySet::new().with_output_wait_limit(8),
            PolicySet::new().require_output_schedule(OutputSchedule::Live),
        ),
    ] {
        let mut calls = 0;
        let mut policy = BoundPolicy::new(
            bound,
            |q: Question<'_>| -> Result<Decision, At<Infallible>> {
                calls += 1;
                let Question::OutputSchedule { choices, .. } = q else {
                    panic!("wrong domain")
                };
                assert_eq!(choices.len(), 1);
                Ok(Decision::choose(choices.first().unwrap()))
            },
        );
        let mut offers = [
            OutputScheduleOffer::new(OutputSchedule::FinishFirst),
            OutputScheduleOffer::new(OutputSchedule::Live),
            OutputScheduleOffer::new(OutputSchedule::Live),
        ];
        offers[0].wait_retained_upper_bound = Some(8);
        offers[1].wait_retained_upper_bound = Some(9);
        offers[2].wait_retained_upper_bound = Some(8);
        let (index, record) = resolve_output_schedule(
            OutputScheduleFacts::new(OutputTiming::streaming(0), OutputSource::PullRows),
            &offers,
            &mut policy,
            legacy,
            Some(&NoPulse),
        )
        .unwrap();
        assert_eq!(index, 2);
        assert_eq!((record.offered, record.admitted), (3, 1));
        assert_eq!(calls, 1);
    }
}

#[test]
fn erased_callback_and_reborrowed_operation_keep_the_same_admission() {
    use zencodec::operation::OperationPolicy;
    let mut binding = BoundPolicy::new(PolicySet::new().with_output_wait_limit(8), preset);
    let erased: &mut BoundPolicy<dyn PolicyResolver<Error = Infallible>> = &mut binding;
    let mut borrowed = &mut *erased;
    let operation: &mut dyn OperationPolicy<Error = Infallible> = &mut borrowed;
    let error = resolve_output_schedule(
        OutputScheduleFacts::new(OutputTiming::streaming(0), OutputSource::PullRows),
        &[OutputScheduleOffer::new(OutputSchedule::Live)],
        operation,
        PolicySet::new(),
        Some(&NoPulse),
    )
    .unwrap_err();
    assert_eq!(
        error
            .error()
            .record()
            .unmet_requirements
            .iter()
            .collect::<Vec<_>>(),
        [(PolicyRequirement::OutputWait, RequirementEvidence::Unknown)]
    );
}
Signatures & related contracts

Signatures from this revision · bodies omitted

Inherent methodszencodec::operation::BoundPolicy
impl<R: PolicyResolver> BoundPolicy<R> {
    pub const fn new(requirements: PolicySet, resolver: R) -> Self;

    pub fn into_parts(self) -> (PolicySet, R);
}
Read defaults, imports and full contract ↗
Type definitionzencodec::operation::DecisionRecord
pub struct DecisionRecord {
    /// Globally unique question identity (zero only if identity allocation failed).
    pub question: u64,
    /// What prompted this decision, including on refusal or callback failure.
    pub context: DecisionContext,
    /// Number of executable alternatives supplied before mandatory admission.
    pub offered: u8,
    /// Number of alternatives that passed mandatory admission.
    pub admitted: u8,
    /// Union of missing/contradicted evidence across excluded alternatives.
    /// A selected route satisfies these requirements; rejected alternatives may not.
    pub unmet_requirements: UnmetRequirements,
    /// Selected contract, including its original request preference index.
    pub selected: Option<Selection>,
    /// True when a callback returned `UsePreset`. A `PolicySet` passed directly
    /// as the operation policy selects without delegation and records false.
    pub used_preset: bool,
    /// Selection or refusal category.
    pub code: DecisionCode,
}
Read defaults, imports and full contract ↗

Select → crop canvas → render

1Open the first display imageStart here

A collection can contain pages, children or layers. Select through the common collection interface before assuming that the first stored raster is a complete canvas.

Implemented · review API
Source ↗
Imports used by this source
use crate::{collection_factories::TiffFactory, tiff_region::Allowances};
use zencodec::{
    At, CodecSet, ImageFormat, SharedPulse,
    collection::{ImageRequest, OpenRequest},
    image::ImageOutput,
    operation::{CollectionOperationError, Outcome, OperationPolicy},
    source::DynReadAt,
};
use zenjxl::collection::JxlCollectionConfig;
Show setup above ↑32 lines
use crate::{collection_factories::TiffFactory, tiff_region::Allowances};
use zencodec::{
    At, CodecSet, ImageFormat, SharedPulse,
    collection::{ImageRequest, OpenRequest},
    image::ImageOutput,
    operation::{CollectionOperationError, Outcome, OperationPolicy},
    source::DynReadAt,
};

use zenjxl::collection::JxlCollectionConfig;

// For example: JxlCollectionConfig::new(64, 256) bounds image identities
// and processed frame headers separately. Configure these once.
// JXL uses its real codec package; TIFF
// remains a private adapter candidate. Both use the shared checked operation.
pub fn registered_factories(tiff: Allowances, jxl: JxlCollectionConfig) -> CodecSet {
    CodecSet::new()
        .with_collection(TiffFactory(tiff))
        .with_collection(jxl)
}

// The caller supplies a stable source: immutable bytes, a conditional HTTP
// object, or a file held unchanged. Choose one view and discovery allowance.
pub fn checked_registered_first<R: OperationPolicy>(
    factories: &CodecSet,
    format: ImageFormat,
    source: &mut dyn DynReadAt,
    request: OpenRequest,
    resolver: R,
    stop: SharedPulse,
) -> Result<Option<Outcome<ImageOutput>>, At<CollectionOperationError>> {
    let mut images = factories.open_collection(format, source, request, resolver, stop)?;
    let image = images.first_image(&ImageRequest::new())?;
Show continuation below ↓4 lines
    // The image owns its samples after this operation ends. Applications
    // that retain IDs instead keep the opened operation for later requests.
    Ok(image)
}
Signatures & related contracts

Signatures from this revision · bodies omitted

Inherent methodszencodec::CodecSet
impl CodecSet {
    pub fn with_collection(mut self, factory: impl CollectionFactory + 'static) -> Self;

    pub fn open_collection<'a, R: OperationPolicy>(
        &self,
        format: ImageFormat,
        source: &'a mut dyn DynReadAt,
        request: OpenRequest,
        resolver: R,
        pulse: SharedPulse,
    ) -> Result<CheckedCollection<Discovery<ErasedCollection<'a>>, R>, At<CollectionOperationError>>;
}
Read defaults, imports and full contract ↗
Inherent methodszencodec::operation::CheckedCollection<C, R>
impl<C: Collection, R: OperationPolicy> CheckedCollection<C, R> {
    pub fn open(
        pulse: SharedPulse,
        resolver: R,
        open: impl FnOnce(SharedPulse) -> Result<C, Cause>,
    ) -> Result<Self, At<CollectionOperationError>>;

    pub fn with_policy_set(mut self, preset: PolicySet) -> Self;

    pub fn images(&mut self) -> Result<C::Cursor, At<CollectionOperationError>>;

    pub fn children(&mut self, image: &ImageId) -> Result<C::Cursor, At<CollectionOperationError>>;

    pub fn next(
        &mut self,
        cursor: &mut C::Cursor,
    ) -> Result<Option<ImageId>, At<CollectionOperationError>>;

    pub fn describe(
        &mut self,
        image: &ImageId,
    ) -> Result<ImageDescription, At<CollectionOperationError>>;

    pub fn first_image(
        &mut self,
        request: &ImageRequest,
    ) -> Result<Option<Outcome<ImageOutput>>, At<CollectionOperationError>>;

    pub fn decode(
        &mut self,
        image: &ImageId,
        request: &ImageRequest,
    ) -> Result<Outcome<ImageOutput>, At<CollectionOperationError>>;

    pub fn decode_with<E: Into<Cause>>(
        &mut self,
        image: &ImageId,
        request: &ImageRequest,
        transform: impl FnOnce(ImageOutput, &dyn crate::Pulse) -> Result<ImageOutput, E>,
    ) -> Result<Outcome<ImageOutput>, At<CollectionOperationError>>;

    pub fn report(&self) -> &OperationReport;

    pub fn is_finished(&self) -> bool;
}

Selected methods · 1 more in the full contract.

Read defaults, imports and full contract ↗
2Render from a remote snapshotAdd this need

The viewer needs a display result from positional reads. This native test checks the full read → select → render path and rejects changed or faulty sources.

Native TIFF integration test
Source ↗
Imports used by this source
use super::*;
use crate::http_source::{Failure, FailureKind, HttpSource};
use crate::collection_factories::TiffFactory;
use zencodec::{CodecSet, ImageFormat, LimitExceeded, ResourceLimits, SharedPulse, NoPulse,
        collection::OpenRequest, operation::PolicySet, source::ReadAtShim};
use crate::collection_factories::TiffFactory;
use zencodec::{CodecSet, collection::OpenRequest};
use zencodec::{ImageFormat, SharedPulse, NoPulse, operation::PolicySet, source::ReadAtShim};
Show preceding definitions ↑80 lines
use super::*;
use crate::http_source::{Failure, FailureKind, HttpSource};

#[test]
fn http_snapshot_collection_limits_keep_inspection_and_smaller_children_available() {
    use crate::collection_factories::TiffFactory;
    use zencodec::{CodecSet, ImageFormat, LimitExceeded, ResourceLimits, SharedPulse, NoPulse,
        collection::OpenRequest, operation::PolicySet, source::ReadAtShim};
    let (bytes, refs) = nested_fixture(true, true, false);
    let name = "collection-limits";
    write_fixture(name, &bytes);
    let mut source = ReadAtShim(HttpSource::open(url("stable", name, 0), 65536).unwrap());
    let codecs = CodecSet::new().with_collection(TiffFactory(Allowances::default()))
        .with_limits(ResourceLimits::none().with_max_width(refs[1].width));
    let mut images = codecs.open_collection(
        ImageFormat::Tiff, &mut source, OpenRequest::new(Composition::Composited, 32),
        PolicySet::new(), SharedPulse::default(),
    ).unwrap();
    let mut roots = images.images().unwrap();
    let root = images.next(&mut roots).unwrap().unwrap();
    assert_eq!(images.describe(&root).unwrap().stored_size, (refs[0].width, refs[0].height));
    let error = images.decode(&root, &ImageRequest::new()).unwrap_err();
    assert_eq!(zencodec::find_cause::<LimitExceeded>(&error), Some(&LimitExceeded::Width {
        actual: refs[0].width, max: refs[1].width,
    }));
    assert_eq!(images.report().decisions().count(), 0);
    assert!(!images.is_finished());
    let mut children = images.children(&root).unwrap();
    let child = images.next(&mut children).unwrap().unwrap();
    let output = images.decode(&child, &ImageRequest::new()).unwrap();
    assert_eq!(output.with_pixels(|p| p.as_contiguous_bytes().unwrap().to_vec()).unwrap(), refs[1].bytes);
    assert_eq!(images.report().decisions().count(), 1);
}

#[test]
fn http_snapshot_collection_factory_preserves_positional_selection() {
    use crate::collection_factories::TiffFactory;
    use zencodec::{CodecSet, collection::OpenRequest};
    use zencodec::{ImageFormat, SharedPulse, NoPulse, operation::PolicySet, source::ReadAtShim};
    let (bytes, refs) = nested_fixture(true, true, false);
    let name = "collection-factory";
    write_fixture(name, &bytes);
    let mut final_read = 0;
    for mode in ["stable", "change", "changed_tag", "short_body"] {
        let source = HttpSource::open(url(mode, name, final_read), 65536).unwrap();
        let attempts = source.attempts.clone();
        let mut source = ReadAtShim(source);
        let factories = CodecSet::new().with_collection(TiffFactory(Allowances::default()));
        let mut images = factories.open_collection(
            ImageFormat::Tiff, &mut source,
            OpenRequest::new(Composition::Composited, 32),
            PolicySet::new(), SharedPulse::default(),
        ).unwrap();
        let mut roots = images.images().unwrap();
        let root = images.next(&mut roots).unwrap().unwrap();
        let description = images.describe(&root).unwrap();
        assert_eq!(description.stored_size, (refs[0].width, refs[0].height));
        let output = images.decode(&root, &ImageRequest::new());
        if mode == "stable" {
            final_read = attempts.get();
            assert_eq!(output.unwrap().with_pixels(|p| p.as_strided_bytes().to_vec()).unwrap(), refs[0].bytes);
        } else {
            let expected = match mode {
                "change" => FailureKind::Status(412),
                "changed_tag" => FailureKind::Validator,
                _ => FailureKind::Body,
            };
            let error = output.err().unwrap();
            assert_cause(&error, expected);
            assert_eq!(error.error().stop_reason(), None);
            let before = attempts.get();
            assert_cause(&images.decode(&root, &ImageRequest::new()).err().unwrap(), expected);
            assert_eq!(attempts.get(), before, "terminal source retried transport I/O");
            assert_eq!(images.report().decisions().count(), 0, "failed native output reached publication policy");
        }
    }
}

#[test]
fn http_snapshot_render_first_matches_slice_and_rejects_late_source_faults() {
    use crate::collection_factories::TiffFactory;
    use zencodec::{CodecSet, ImageFormat, SharedPulse, NoPulse,
        collection::OpenRequest, operation::PolicySet,
        source::{ReadAtShim, SliceReadAt}, storage::StorageBudget};
    use zencodec_render::Renderer;
    use zenpixels_convert::cms_moxcms::MoxCms;

    let bytes = crate::canvas_render::tests::native_tiff_bytes(6, true, 8);
    let name = "render-first";
    write_fixture(name, &bytes);
    let codecs = CodecSet::new().with_collection(TiffFactory(Allowances::default()));
    let budget = StorageBudget::new(1024 * 1024, 32);
    let renderer = Renderer::new(budget.clone()).with_cms(&MoxCms);
    // The returned owner must survive both the collection and its source.
    let reference = {
        let mut source = ReadAtShim(SliceReadAt::new(&bytes));
        let mut images = codecs.open_collection(
            ImageFormat::Tiff, &mut source, OpenRequest::new(Composition::Composited, 32),
            PolicySet::new(), SharedPulse::default(),
        ).unwrap();
        let image = renderer.render_first(&mut images).unwrap().unwrap();
        assert_eq!(images.report().decisions().count(), 1);
        image
    };
    let expected = reference.with_pixels(|p| {
        assert_eq!((p.width(), p.rows()), (5, 7));
        assert_eq!(p.descriptor(), zenpixels::PixelDescriptor::RGBA8_SRGB);
        p.as_strided_bytes().to_vec()
    }).unwrap();
    drop(reference);
    assert_eq!(budget.reserved_bytes(), 0);
    let mut final_read = 0;
    for mode in ["stable", "change", "changed_tag", "short_body"] {
        let source = HttpSource::open(url(mode, name, final_read), 65536).unwrap();
        let attempts = source.attempts.clone();
        let mut source = ReadAtShim(source);
        let mut images = codecs.open_collection(
            ImageFormat::Tiff, &mut source, OpenRequest::new(Composition::Composited, 32),
            PolicySet::new(), SharedPulse::default(),
        ).unwrap();
        let output = renderer.render_first(&mut images);
        if mode == "stable" {
            final_read = attempts.get();
            let output = output.unwrap().unwrap();
            assert_eq!(images.report().decisions().count(), 1);
            drop(images);
            drop(source);
            assert_eq!(output.with_pixels(|p| p.as_strided_bytes().to_vec()).unwrap(), expected);
            drop(output);
        } else {
            let kind = match mode {
                "change" => FailureKind::Status(412),
                "changed_tag" => FailureKind::Validator,
                _ => FailureKind::Body,
            };
            let error = output.expect_err("fault must not become an empty collection or an image");
            assert_cause(&error, kind);
            assert_eq!(error.error().stop_reason(), None);
            assert_eq!(images.report().decisions().count(), 0);
            let before = attempts.get();
            assert_cause(&renderer.render_first(&mut images).err().unwrap(), kind);
            assert_eq!(attempts.get(), before, "terminal source retried transport I/O");
        }
        assert_eq!(budget.reserved_bytes(), 0);
        assert_eq!(budget.reserved_objects(), 0);
    }
}
Show following definitions ↓339 lines


fn write_fixture(name: &str, bytes: &[u8]) {
    use std::io::Write;
    let root =
        std::env::var_os("SPATIAL_HTTP_ARTIFACTS").expect("HTTP harness must supply artifacts");
    let path = std::path::Path::new(&root).join(format!("{name}.tif"));
    let mut file = std::fs::OpenOptions::new()
        .write(true)
        .create_new(true)
        .open(path)
        .unwrap();
    file.write_all(bytes).unwrap();
}
fn url(mode: &str, name: &str, fault_at: u64) -> String {
    let base = std::env::var("SPATIAL_HTTP_URL").expect("HTTP harness must supply its server URL");
    format!("{base}/{mode}/{name}.tif?at={fault_at}")
}
fn child<R: std::io::Read + std::io::Seek>(
    collection: &mut TiffCollection<R>,
) -> crate::image_items::ImageId {
    let mut roots = collection.images().unwrap();
    let root = collection.next(&mut roots).unwrap().unwrap();
    let mut children = collection.children(&root).unwrap();
    collection.next(&mut children).unwrap().unwrap()
}
fn assert_cause(error: &(dyn std::error::Error + 'static), kind: FailureKind) {
    assert_eq!(
        zencodec::find_cause::<Failure>(error).unwrap().kind,
        kind,
        "{error:?}"
    );
}

#[test]
fn http_snapshot_description_and_later_decode_keep_the_source_revision() {
        for big in [false, true] {
        let (bytes, refs) = nested_fixture(big, true, false);
        let name = format!("selected-description-{big}");
        write_fixture(&name, &bytes);
        let mut final_description_read = 0;
        for (mode, expected) in [
            ("stable", None),
            ("change", Some(FailureKind::Status(412))),
            ("changed_tag", Some(FailureKind::Validator)),
            ("short_body", Some(FailureKind::Body)),
        ] {
            let source = HttpSource::open(url(mode, &name, final_description_read), 65536).unwrap();
            let attempts = source.attempts.clone();
            let mut collection = TiffCollection::open(ReadAtReader::new(source), Composition::Composited, Allowances::default()).unwrap();
            let id = child(&mut collection);
            let discovered = attempts.get();
            match expected {
                None => {
                    let description = collection.describe(&id).unwrap();
                    assert_eq!(description.stored_size, (refs[1].width, refs[1].height));
                    final_description_read = attempts.get();
                    assert!(final_description_read > discovered);
                }
                Some(kind) => {
                    let error = collection.describe(&id).expect_err("partial or replaced description escaped");
                    assert_cause(&error, kind);
                    assert_eq!(attempts.get(), final_description_read);
                    let error = collection.describe(&id).expect_err("failed source reopened");
                    assert_cause(&error, kind);
                    assert_eq!(attempts.get(), final_description_read);
                }
            }
        }
        // A successful description is a fact about the retained representation,
        // not permission to decode a different revision under the same ID.
        let source = HttpSource::open(url("change", &name, final_description_read + 1), 65536).unwrap();
        let attempts = source.attempts.clone();
        let mut collection = TiffCollection::open(ReadAtReader::new(source), Composition::Composited, Allowances::default()).unwrap();
        let id = child(&mut collection);
        collection.describe(&id).unwrap();
        assert_eq!(attempts.get(), final_description_read);
        let error = collection.decode(&id, &ImageRequest::new()).expect_err("new revision decoded after old description");
        assert_cause(&error, FailureKind::Status(412));
        assert_eq!(attempts.get(), final_description_read + 1);
    }
}

#[test]
fn http_snapshot_checked_collection_publishes_only_after_last_range_and_policy() {
    use crate::checked_collection::{Checked, Stage};
    use std::{cell::Cell, convert::Infallible};
    use zencodec::{
        At, SharedPulse, NoPulse,
        operation::{Decision, Question},
    };
    for big in [false, true] {
        let (bytes, refs) = nested_fixture(big, true, false);
        let name = format!("checked-publication-{big}");
        write_fixture(&name, &bytes);
        let mut last_read = 0;
        for (mode, expected_error) in [
            ("stable", None),
            ("change", Some(FailureKind::Status(412))),
            ("changed_tag", Some(FailureKind::Validator)),
            ("short_body", Some(FailureKind::Body)),
        ] {
            let seen = Cell::new(0);
            let resolver = |question: Question<'_>| -> Result<Decision, At<Infallible>> {
                let Question::Image { image, .. } = question else {
                    panic!("wrong domain")
                };
                image
                    .with_pixels(|p| assert_eq!(p.as_contiguous_bytes().unwrap(), refs[1].bytes))
                    .unwrap();
                seen.set(seen.get() + 1);
                Ok(Decision::UsePreset)
            };
            let mut attempts = None;
            let mut collection = Checked::open(SharedPulse::default(), resolver, |stop| {
                let source = HttpSource::open(url(mode, &name, last_read), 65536)?;
                attempts = Some(source.attempts.clone());
                TiffCollection::open_with_stop(
                    ReadAtReader::new(source),
                    Composition::Composited,
                    Allowances::default(),
                    stop,
                )
                .map_err(Into::into)
            })
            .unwrap();
            let attempts = attempts.unwrap();
            let mut roots = collection.images().unwrap();
            let parent = collection.next(&mut roots).unwrap().unwrap();
            let mut children = collection.children(&parent).unwrap();
            let id = collection.next(&mut children).unwrap().unwrap();
            let discovered = attempts.get();
            if let Some(kind) = expected_error {
                assert!(last_read > discovered);
                let error = collection
                    .decode(&id, &ImageRequest::default())
                    .unwrap_err();
                assert_eq!(error.error().stage, Stage::Decode);
                assert_cause(&error, kind);
                assert_eq!(attempts.get(), last_read);
                assert_eq!(seen.get(), 0, "policy saw a partial or replaced image");
                assert_eq!(error.error().report.decisions().count(), 0);
                let retry = collection
                    .decode(&id, &ImageRequest::default())
                    .unwrap_err();
                assert_cause(&retry, kind);
                assert_eq!(attempts.get(), last_read, "retry reopened a failed source");
                assert_eq!(seen.get(), 0);
            } else {
                let image = collection.decode(&id, &ImageRequest::default()).unwrap();
                last_read = attempts.get();
                assert!(last_read > discovered);
                assert_eq!(seen.get(), 1);
                assert_eq!(image.report().decisions().count(), 1);
                drop(collection);
                image
                    .with_pixels(|p| assert_eq!(p.as_contiguous_bytes().unwrap(), refs[1].bytes))
                    .unwrap();
            }
        }
    }
}

#[test]
fn http_snapshot_native_tiff_matches_slice_with_direct_and_buffered_io() {
    for big in [false, true] {
        for planar in [false, true] {
            let (bytes, refs) = nested_fixture(big, planar, false);
            let check = |output: crate::image_output::ImageOutput| {
                output
                    .with_pixels(|pixels| {
                        assert_eq!(pixels.as_contiguous_bytes().unwrap(), refs[1].bytes);
                    })
                    .unwrap();
            };
            for buffered in [false, true] {
                let name = format!("control-{big}-{planar}-{buffered}");
                write_fixture(&name, &bytes);
                let source = HttpSource::open(url("stable", &name, 0), 65536).unwrap();
                let input = ReadAtReader::new(source);
                if buffered {
                    let mut collection = TiffCollection::open(
                        std::io::BufReader::with_capacity(4096, input),
                        Composition::Composited,
                        Allowances::default(),
                    )
                    .unwrap();
                    check(
                        first_child(&mut collection, &ImageRequest::default())
                            .unwrap()
                            .unwrap(),
                    );
                } else {
                    let mut collection =
                        TiffCollection::open(input, Composition::Composited, Allowances::default())
                            .unwrap();
                    check(
                        first_child(&mut collection, &ImageRequest::default())
                            .unwrap()
                            .unwrap(),
                    );
                }
            }
        }
    }
}

#[test]
fn http_snapshot_change_after_native_discovery_refuses_selected_pixels_and_retry() {
    for big in [false, true] {
        let (bytes, refs) = nested_fixture(big, true, false);
        let name = format!("after-discovery-{big}");
        write_fixture(&name, &bytes);
        let source = HttpSource::open(url("stable", &name, 0), 65536).unwrap();
        let attempts = source.attempts.clone();
        let mut collection = TiffCollection::open(
            ReadAtReader::new(source),
            Composition::Composited,
            Allowances::default(),
        )
        .unwrap();
        let id = child(&mut collection);
        let fault_at = attempts.get() + 1;
        collection
            .decode(&id, &ImageRequest::default())
            .unwrap()
            .with_pixels(|p| {
                assert_eq!(p.as_contiguous_bytes().unwrap(), refs[1].bytes);
            })
            .unwrap();
        assert!(
            attempts.get() >= fault_at,
            "decode must perform another range request"
        );
        let last_read = attempts.get();
        assert!(
            last_read > fault_at,
            "fixture must distinguish first and last decode read"
        );
        for (mode, at, kind) in [
            ("change", fault_at, FailureKind::Status(412)),
            ("change", last_read, FailureKind::Status(412)),
            ("changed_tag", last_read, FailureKind::Validator),
            ("wrong_total", last_read, FailureKind::ContentRange),
            ("short_body", last_read, FailureKind::Body),
        ] {
            let source = HttpSource::open(url(mode, &name, at), 65536).unwrap();
            let attempts = source.attempts.clone();
            let mut collection = TiffCollection::open(
                ReadAtReader::new(source),
                Composition::Composited,
                Allowances::default(),
            )
            .unwrap();
            let id = child(&mut collection);
            assert_eq!(attempts.get(), fault_at - 1);
            let error = collection
                .decode(&id, &ImageRequest::default())
                .unwrap_err();
            assert_cause(&error, kind);
            assert_eq!(attempts.get(), at);
            let error = collection
                .decode(&id, &ImageRequest::default())
                .unwrap_err();
            assert_cause(&error, kind);
            assert_eq!(attempts.get(), at, "failed source silently reopened");
        }
    }
}

#[test]
fn http_snapshot_malformed_responses_keep_typed_causes_through_native_open() {
    let (bytes, _) = nested_fixture(false, false, false);
    write_fixture("faults", &bytes);
    for (mode, kind) in [
        ("change", FailureKind::Status(412)),
        ("changed_tag", FailureKind::Validator),
        ("weak", FailureKind::Validator),
        ("missing_tag", FailureKind::Validator),
        ("bad_tag", FailureKind::Validator),
        ("duplicate_tag", FailureKind::Validator),
        ("wrong_range", FailureKind::ContentRange),
        ("wrong_total", FailureKind::ContentRange),
        ("wrong_length", FailureKind::ContentLength),
        ("missing_length", FailureKind::ContentLength),
        ("encoding", FailureKind::Encoding),
        ("full_body", FailureKind::Status(200)),
        ("redirect", FailureKind::Status(302)),
        ("short_body", FailureKind::Body),
    ] {
        let source = HttpSource::open(url(mode, "faults", 2), 65536).unwrap();
        let attempts = source.attempts.clone();
        let error = TiffCollection::open(
            ReadAtReader::new(source),
            Composition::Composited,
            Allowances::default(),
        )
        .err()
        .expect("malformed response opened native collection");
        assert_cause(&error, kind);
        assert_eq!(attempts.get(), 2, "unexpected HTTP retry for {mode}");
        if kind == FailureKind::Body {
            assert!(zencodec::find_cause::<std::io::Error>(&error).is_some());
        }
    }
}

#[test]
fn http_snapshot_initial_validator_and_range_limit_are_required() {
    let (bytes, _) = nested_fixture(false, false, false);
    write_fixture("admission", &bytes);
    for mode in ["weak", "missing_tag", "bad_tag", "duplicate_tag"] {
        let error = HttpSource::open(url(mode, "admission", 1), 65536)
            .err()
            .unwrap();
        assert_eq!(error.kind, FailureKind::Validator);
    }
    // An empty strong opaque tag is legal; it is not a weak/missing validator.
    let mut source = HttpSource::open(url("empty_tag", "admission", 1), 65536).unwrap();
    let mut out = [0; 4];
    source.read_exact_at(0, &mut out).unwrap();
    assert_eq!(out, bytes[..4]);

    let mut source = HttpSource::open(url("stable", "admission", 0), 1).unwrap();
    let error = source.read_exact_at(0, &mut [0; 2]).unwrap_err();
    assert_eq!(error.kind, FailureKind::RangeLimit);
    assert_eq!(
        source.attempts.get(),
        1,
        "oversized request reached network"
    );
    assert_eq!(source.len_hint(), None);
    assert_eq!(source.len().unwrap_err().kind, error.kind);
    assert_eq!(
        source.read_exact_at(0, &mut []).unwrap_err().kind,
        error.kind
    );
}
Signatures & related contracts

Signatures from this revision · bodies omitted

Type definitionzencodec_render::RenderRequest
pub struct RenderRequest {
    /// Required output color space.
    pub target: ColorTarget,
    /// Required packed pixel descriptor, including alpha interpretation.
    pub required: PixelDescriptor,
    /// Oriented canvas coordinates. None requests the full oriented canvas.
    pub region: Option<Rect>,
    /// Explicit conversion behavior, including clipping and channel loss.
    pub conversion: ConvertOptions,
}
Read defaults, imports and full contract ↗
Inherent methodszencodec_render::Renderer
impl<'a> Renderer<'a> {
    pub fn new(storage: StorageBudget) -> Self;

    pub fn with_request(mut self, request: RenderRequest) -> Self;

    pub fn with_cms(mut self, cms: &'a dyn PluggableCms) -> Self;

    pub fn render_first<C: Collection, R: OperationPolicy>(
        &self,
        images: &mut CheckedCollection<C, R>,
    ) -> Result<Option<Outcome<ImageOutput>>, At<CollectionOperationError>>;

    pub fn render_selected<C: Collection, R: OperationPolicy>(
        &self,
        images: &mut CheckedCollection<C, R>,
        image: &ImageId,
    ) -> Result<Outcome<ImageOutput>, At<CollectionOperationError>>;

    pub fn render<R: OperationPolicy + ?Sized>(
        &self,
        image: ImageOutput,
        policy: &mut R,
        pulse: Option<&dyn zencodec::Pulse>,
    ) -> Result<Outcome<ImageOutput>, At<RenderFailure>>;
}

Selected methods · 3 more in the full contract.

Read defaults, imports and full contract ↗

Convert color → map a framebuffer

1Choose the display colorStart here

Native pixels need not match the display. Keep their current color context and let zenpixels-convert prepare the requested output interpretation.

Implemented · review API
Source ↗
Imports used by this source
use zencodec::{CodecSet, Orientation, ResourceLimits, decode::BoxedError};
use zenjpeg::JpegDecoderConfig;
use zenpixels::{PixelBuffer, PixelDescriptor};
use zenpixels_convert::PixelBufferConvertExt;
Show setup above ↑12 lines
use zencodec::{CodecSet, Orientation, ResourceLimits, decode::BoxedError};
use zenjpeg::JpegDecoderConfig;
use zenpixels::{PixelBuffer, PixelDescriptor};
use zenpixels_convert::PixelBufferConvertExt;

pub(crate) fn decode_for_display(
    bytes: &[u8],
) -> Result<(PixelBuffer, Orientation), BoxedError> {
    let codecs = CodecSet::new()
        .with_limits(ResourceLimits::for_untrusted_input().with_max_pixels(4_000_000))
        .with_decoder(JpegDecoderConfig::new());
    let decoded = codecs.decode(bytes)?;
    let orientation = decoded.info().orientation;
    let bgra = decoded
        .into_buffer()
        .convert_to(PixelDescriptor::BGRA8_SRGB)?;
Show continuation below ↓4 lines
    // Storage conversion does not apply orientation. The display owns that step.
    // An unsupported color transform is an error, never a request to relabel.
    Ok((bgra, orientation))
}
Signatures & related contracts

Signatures from this revision · bodies omitted

Inherent methodszencodec::CodecSet
impl CodecSet {
    pub fn probe<'a>(&'a self, data: &'a [u8]) -> Result<ImageInfo, CodecSetError>;

    pub fn decode<'a>(&'a self, data: &'a [u8]) -> Result<DecodeOutput, CodecSetError>;

    pub fn decode_preferring<'a>(
        &'a self,
        data: &'a [u8],
        preferred: &[PixelDescriptor],
    ) -> Result<DecodeOutput, CodecSetError>;

    pub fn encode(
        &self,
        format: ImageFormat,
        pixels: PixelSlice<'_>,
    ) -> Result<EncodeOutput, CodecSetError>;

    pub fn encode_with(
        &self,
        format: ImageFormat,
        fidelity: Fidelity,
        pixels: PixelSlice<'_>,
    ) -> Result<EncodeOutput, CodecSetError>;

    pub fn transcode(
        &self,
        input: &[u8],
        target: ImageFormat,
        fidelity: Fidelity,
        metadata: MetadataPolicy,
        color: ColorEmitPolicy,
    ) -> Result<EncodeOutput, CodecSetError>;
}

Selected methods · 43 more in the full contract.

Read defaults, imports and full contract ↗
2Map the framebufferAdd this need

The display needs rows in a specific layout. Borrow the returned owner and honor its stride; do not infer packed storage from width.

Implemented · review API
Source ↗
Imports used by this source
use zencodec::{
    ResourceLimits,
    decode::BoxedError,
    encode::{EncodeJob, EncoderConfig},
};
use zenjpeg::JpegEncoderConfig;
use zenpixels::{AlphaPolicy, ConvertOptions, PixelBuffer, PixelDescriptor, PixelSlice};
use zenpixels_convert::{RowConverter, adapt::check_opaque};
Show setup above ↑28 lines
use zencodec::{
    ResourceLimits,
    decode::BoxedError,
    encode::{EncodeJob, EncoderConfig},
};
use zenjpeg::JpegEncoderConfig;
use zenpixels::{AlphaPolicy, ConvertOptions, PixelBuffer, PixelDescriptor, PixelSlice};
use zenpixels_convert::{RowConverter, adapt::check_opaque};

#[derive(Debug)]
pub(crate) struct ExpectedSrgbFramebuffer;
impl std::fmt::Display for ExpectedSrgbFramebuffer {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str("expected caller-rendered BGRA8 sRGB without an attached color profile")
    }
}
impl std::error::Error for ExpectedSrgbFramebuffer {}

pub(crate) fn rgb_for_jpeg(pixels: PixelSlice<'_>) -> Result<PixelBuffer, BoxedError> {
    // This adapter's input is an already rendered framebuffer, not arbitrary
    // decoded ICC/HDR/unknown-color pixels. Those need a color-aware path.
    if pixels.descriptor() != PixelDescriptor::BGRA8_SRGB || pixels.color_context().is_some() {
        return Err(ExpectedSrgbFramebuffer.into());
    }
    ResourceLimits::for_untrusted_input()
        .with_max_pixels(4_000_000)
        .check_dimensions(pixels.width(), pixels.rows())?;
    check_opaque(pixels.clone())?; // Verify every row before authorizing alpha removal.
    let options =
        ConvertOptions::retain_channels().with_alpha_policy(AlphaPolicy::DiscardUnchecked); // Proven above.
    let mut converter =
        RowConverter::new_explicit(pixels.descriptor(), PixelDescriptor::RGB8_SRGB, &options)?;
    converter.prepare(pixels.width())?;
    let mut rgb =
        PixelBuffer::try_new(pixels.width(), pixels.rows(), PixelDescriptor::RGB8_SRGB)?;
    {
        let mut destination = rgb.as_slice_mut();
        for y in 0..pixels.rows() {
            converter.try_convert_row(pixels.row(y), destination.row_mut(y), pixels.width())?;
        }
    }
Show continuation below ↓13 lines
    Ok(rgb)
}

pub(crate) fn encode_framebuffer(pixels: PixelSlice<'_>) -> Result<Vec<u8>, BoxedError> {
    let rgb = rgb_for_jpeg(pixels)?;
    // Conversion preserves these RGB samples. JPEG encoding is intentionally lossy.
    let output = JpegEncoderConfig::new()
        .with_generic_quality(85.0)
        .job()
        .with_limits(ResourceLimits::for_untrusted_input())
        .encode(rgb.as_slice())?;
    Ok(output.into_vec())
}
Signatures & related contracts

Signatures from this revision · bodies omitted

Inherent methodszencodec::image::ImageOutput
impl ImageOutput {
    pub fn interpretation(&self) -> &ImageInterpretation;

    pub fn source_frame(&self) -> Option<&Arc<FrameInterpretation>>;

    pub fn map(&self) -> Result<Box<dyn MappedImage + '_>, At<OutputError>>;

    pub fn with_pixels<T>(
        &self,
        consume: impl FnOnce(PixelSlice<'_>) -> T,
    ) -> Result<T, At<OutputError>>;
}

Selected methods · 4 more in the full contract.

Read defaults, imports and full contract ↗

Transcode → metadata → custom policy

1Publish a web derivativeStart here

The output now needs named sRGB and descriptive-metadata removal. These are explicit requirements; alpha or depth loss must not be silently substituted.

Implemented · review API
Source ↗
Imports used by this source
use zencodec::{
    ImageFormat,
    operation::{MetadataTransfer, PolicySet},
};
use zencodecs::checked_transcode::TranscodeRequest;
use zenpixels::{Cicp, ConvertOptions, PixelFormat};
use zenpixels_convert::{OutputProfile, cms_moxcms::MoxCms};
Show setup above ↑10 lines
use zencodec::{
    ImageFormat,
    operation::{MetadataTransfer, PolicySet},
};
use zencodecs::checked_transcode::TranscodeRequest;
use zenpixels::{Cicp, ConvertOptions, PixelFormat};
use zenpixels_convert::{OutputProfile, cms_moxcms::MoxCms};

pub fn web_request() -> (TranscodeRequest<'static>, PolicySet) {
    let request = TranscodeRequest::new(ImageFormat::Png)
        .with_cms(&MoxCms)
        .with_output(
            OutputProfile::Named(Cicp::SRGB),
            PixelFormat::Rgb8,
            ConvertOptions::retain_channels(),
        )
        .with_metadata(MetadataTransfer::DiscardDescriptive);
    let policy = PolicySet::new()
        .require_metadata(MetadataTransfer::DiscardDescriptive)
        .allow_opaque_profiles(false);
    (request, policy)
Show continuation below ↓3 lines
    // Accept JPEG's RGB samples here. Alpha removal and depth loss are not permitted.
    // Current color is converted; orientation needed for rendering is retained.
}
Signatures & related contracts

Signatures from this revision · bodies omitted

Inherent methodszencodec::operation::PolicySet
impl PolicySet {
    pub const fn new() -> Self;

    pub const fn prefer_lossless(mut self, enabled: bool) -> Self;

    pub const fn require_metadata(mut self, treatment: MetadataTransfer) -> Self;

    pub const fn allow_opaque_profiles(mut self, allowed: bool) -> Self;

    pub const fn with_workspace_limit(mut self, bytes: u64) -> Self;

    pub const fn allow_assumed_chroma_siting(mut self, allowed: bool) -> Self;

    pub const fn require_extended_range(mut self, required: bool) -> Self;

    pub const fn require_output_schedule(mut self, schedule: super::OutputSchedule) -> Self;

    pub const fn with_output_wait_limit(mut self, bytes: u64) -> Self;
}

Selected methods · 6 more in the full contract.

Read defaults, imports and full contract ↗
Pinned dependency · imazen/zenpipe
use zencodec::{
    At, CodecSet, ImageFormat,
    encode::EncodeOutput,
    operation::{self, OperationReport, Outcome, OperationPolicy, PolicySet, MetadataTransfer},
    storage::StorageBudget,
};
use zencodec_io::{DriveOptions, tokio_io::AsyncSource};
use zenpixels::{ConvertOptions, PixelFormat};
use zenpixels_convert::{OutputProfile, cms::PluggableCms};
use zencodecs::checked_transcode::{Error, Stage, TranscodeRequest};

impl<'a> TranscodeRequest<'a> {
    pub fn new(format: ImageFormat) -> Self;

    pub fn preferring_fidelity(mut self, targets: Vec<zencodec::encode::Fidelity>) -> Self;

    pub fn transcode(
        &self,
        codecs: &CodecSet,
        input: &[u8],
    ) -> Result<Outcome<EncodeOutput>, At<Error>>;

    pub fn transcode_from_read_at<S: zencodec::source::ReadAt + ?Sized>(
        &self,
        codecs: &CodecSet,
        source: &mut S,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>;

    pub fn transcode_from_read_at_with<S, R>(
        &self,
        codecs: &CodecSet,
        source: &mut S,
        resolver: &mut R,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>
    where
        S: zencodec::source::ReadAt + ?Sized,
        R: OperationPolicy + ?Sized;

    pub fn transcode_with<R: OperationPolicy + ?Sized>(
        &self,
        codecs: &CodecSet,
        input: &[u8],
        resolver: &mut R,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>;

    pub fn with_cms(mut self, cms: &'a dyn PluggableCms) -> Self;

    pub fn with_decode(mut self, request: operation::DecodeRequest) -> Self;

    pub fn with_encode_policy(mut self, policy: zencodec::encode::EncodePolicy) -> Self;

    pub fn with_fidelity(mut self, target: zencodec::encode::Fidelity) -> Self;

    pub fn with_io_options(mut self, options: DriveOptions) -> Self;

    pub fn with_metadata(mut self, policy: MetadataTransfer) -> Self;

    pub fn with_output(
        mut self,
        profile: OutputProfile,
        format: PixelFormat,
        options: ConvertOptions,
    ) -> Self;

    pub fn with_output_limit(mut self, bytes: usize) -> Self;

    pub fn with_policy_set(mut self, policy: PolicySet) -> Self;

    pub fn with_storage(mut self, storage: StorageBudget) -> Self;

    pub fn with_strip_rows(mut self, rows: u32) -> Self;
}

impl TranscodeRequest<'_> {
    pub async fn transcode_async<S: AsyncSource + ?Sized>(
        &self,
        codecs: &CodecSet,
        source: &mut S,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>;

    pub async fn transcode_async_with<S, R>(
        &self,
        codecs: &CodecSet,
        source: &mut S,
        resolver: &mut R,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>
    where
        S: AsyncSource + ?Sized,
        R: OperationPolicy + ?Sized;
}

impl Error {
    pub fn stage(&self) -> Stage;

    pub fn report(&self) -> &OperationReport;
}
Source at f0322466 ↗
2Add a tenant decisionAdd this need

Only this tenant requires native workspace evidence. The callback chooses among admitted routes or returns a located application error; unknown is not zero. A later fact can require another decision before its affected effects; an earlier choice cannot discharge an unsatisfied requirement.

Implemented · review API
Source ↗
Imports used by this source
use zencodec::{
    At, CodecSet,
    encode::EncodeOutput,
    operation::{BoundPolicy, Decision, Outcome, PolicySet, Question},
};
use zencodecs::checked_transcode::{Error, TranscodeRequest};
Show setup above ↑24 lines
use zencodec::{
    At, CodecSet,
    encode::EncodeOutput,
    operation::{BoundPolicy, Decision, Outcome, PolicySet, Question},
};
use zencodecs::checked_transcode::{Error, TranscodeRequest};

#[derive(Debug)]
pub struct WorkspaceEvidenceRequired;
impl std::fmt::Display for WorkspaceEvidenceRequired {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str("tenant requires a qualified native workspace bound")
    }
}
impl std::error::Error for WorkspaceEvidenceRequired {}

pub fn for_tenant(
    request: &TranscodeRequest<'_>,
    codecs: &CodecSet,
    bytes: &[u8],
    requirements: PolicySet,
    require_workspace_evidence: bool,
) -> Result<Outcome<EncodeOutput>, At<Error>> {
    let mut policy = BoundPolicy::new(
        requirements,
        |question: Question<'_>| -> Result<Decision, At<WorkspaceEvidenceRequired>> {
            if let Question::Transcode { choices, .. } = question
                && require_workspace_evidence
            {
                let choice = choices
                    .iter()
                    .find(|choice| choice.value().workspace_upper_bound.is_some())
                    .ok_or_else(|| At::wrap(WorkspaceEvidenceRequired))?;
                return Ok(Decision::choose(choice));
            }
            Ok(Decision::UsePreset)
        },
    );
    request.transcode_with(codecs, bytes, &mut policy)
Show continuation below ↓3 lines
    // The current native route has an unknown workspace bound, so the strict
    // tenant gets this traced refusal. Managed row storage is not that bound.
}
Signatures & related contracts

Signatures from this revision · bodies omitted

Trait methodszencodec::operation::PolicyResolver
pub trait PolicyResolver {
    type Error: Error + Send + Sync + 'static;

    fn resolve(&mut self, question: Question<'_>) -> Result<Decision, At<Self::Error>>;
}

Required methods: resolve. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Pinned dependency · imazen/zenpipe
use zencodec::{
    At, CodecSet, ImageFormat,
    encode::EncodeOutput,
    operation::{self, OperationReport, Outcome, OperationPolicy, PolicySet, MetadataTransfer},
    storage::StorageBudget,
};
use zencodec_io::{DriveOptions, tokio_io::AsyncSource};
use zenpixels::{ConvertOptions, PixelFormat};
use zenpixels_convert::{OutputProfile, cms::PluggableCms};
use zencodecs::checked_transcode::{Error, Stage, TranscodeRequest};

impl<'a> TranscodeRequest<'a> {
    pub fn new(format: ImageFormat) -> Self;

    pub fn preferring_fidelity(mut self, targets: Vec<zencodec::encode::Fidelity>) -> Self;

    pub fn transcode(
        &self,
        codecs: &CodecSet,
        input: &[u8],
    ) -> Result<Outcome<EncodeOutput>, At<Error>>;

    pub fn transcode_from_read_at<S: zencodec::source::ReadAt + ?Sized>(
        &self,
        codecs: &CodecSet,
        source: &mut S,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>;

    pub fn transcode_from_read_at_with<S, R>(
        &self,
        codecs: &CodecSet,
        source: &mut S,
        resolver: &mut R,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>
    where
        S: zencodec::source::ReadAt + ?Sized,
        R: OperationPolicy + ?Sized;

    pub fn transcode_with<R: OperationPolicy + ?Sized>(
        &self,
        codecs: &CodecSet,
        input: &[u8],
        resolver: &mut R,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>;

    pub fn with_cms(mut self, cms: &'a dyn PluggableCms) -> Self;

    pub fn with_decode(mut self, request: operation::DecodeRequest) -> Self;

    pub fn with_encode_policy(mut self, policy: zencodec::encode::EncodePolicy) -> Self;

    pub fn with_fidelity(mut self, target: zencodec::encode::Fidelity) -> Self;

    pub fn with_io_options(mut self, options: DriveOptions) -> Self;

    pub fn with_metadata(mut self, policy: MetadataTransfer) -> Self;

    pub fn with_output(
        mut self,
        profile: OutputProfile,
        format: PixelFormat,
        options: ConvertOptions,
    ) -> Self;

    pub fn with_output_limit(mut self, bytes: usize) -> Self;

    pub fn with_policy_set(mut self, policy: PolicySet) -> Self;

    pub fn with_storage(mut self, storage: StorageBudget) -> Self;

    pub fn with_strip_rows(mut self, rows: u32) -> Self;
}

impl TranscodeRequest<'_> {
    pub async fn transcode_async<S: AsyncSource + ?Sized>(
        &self,
        codecs: &CodecSet,
        source: &mut S,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>;

    pub async fn transcode_async_with<S, R>(
        &self,
        codecs: &CodecSet,
        source: &mut S,
        resolver: &mut R,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>
    where
        S: AsyncSource + ?Sized,
        R: OperationPolicy + ?Sized;
}

impl Error {
    pub fn stage(&self) -> Stage;

    pub fn report(&self) -> &OperationReport;
}
Source at f0322466 ↗

Memory → disk → async source

1Read a file positionallyStart here

The source is on disk instead of in a byte slice. Keep the same transcode request and supply FileReadAt.

Implemented · review API
Source ↗
Imports used by this source
use zencodec::{
    CodecSet,
    encode::EncodeOutput,
    operation::{OperationPolicy, Outcome},
};
use zencodecs::checked_transcode::TranscodeRequest;
Show setup above ↑14 lines
use zencodec::{
    CodecSet,
    encode::EncodeOutput,
    operation::{OperationPolicy, Outcome},
};
use zencodecs::checked_transcode::TranscodeRequest;

pub fn from_file<P: OperationPolicy + ?Sized>(
    request: &TranscodeRequest<'_>,
    codecs: &CodecSet,
    path: &std::path::Path,
    policy: &mut P,
) -> Result<Outcome<EncodeOutput>, Box<dyn std::error::Error + Send + Sync>> {
    let mut source = zencodec_io::FileReadAt::open(path)?;
    let output = request.transcode_from_read_at_with(codecs, &mut source, policy)?;
Show continuation below ↓3 lines
    // FileReadAt keeps positional reads below the codec; the request is unchanged.
    Ok(output)
}
Signatures & related contracts

Signatures from this revision · bodies omitted

Inherent methods · zencodec-io feature: stdzencodec_io::FileReadAt
impl FileReadAt {
    pub fn new(file: std::fs::File) -> Result<Self, StdSourceError>;

    pub fn open(path: impl AsRef<std::path::Path>) -> Result<Self, StdSourceError>;
}
Read defaults, imports and full contract ↗
Trait methodszencodec::source::ReadAt
pub trait ReadAt {
    type Error: core::error::Error + Send + Sync + 'static;

    fn len(&mut self) -> Result<u64, Self::Error>;

    fn len_hint(&self) -> Option<u64>;

    fn read_exact_at(&mut self, offset: u64, out: &mut [u8]) -> Result<(), Self::Error>;

    fn read_at(&mut self, offset: u64, out: &mut [u8]) -> Result<usize, Self::Error>;

    fn read_many_at(&mut self, reqs: &mut [(u64, &mut [u8])]) -> Result<(), Self::Error>;

    fn as_slice(&self) -> Option<&[u8]>;
}

Selected methods · 1 more in the full contract.

Required methods: len, read_exact_at. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Pinned dependency · imazen/zenpipe
use zencodec::{
    At, CodecSet, ImageFormat,
    encode::EncodeOutput,
    operation::{self, OperationReport, Outcome, OperationPolicy, PolicySet, MetadataTransfer},
    storage::StorageBudget,
};
use zencodec_io::{DriveOptions, tokio_io::AsyncSource};
use zenpixels::{ConvertOptions, PixelFormat};
use zenpixels_convert::{OutputProfile, cms::PluggableCms};
use zencodecs::checked_transcode::{Error, Stage, TranscodeRequest};

impl<'a> TranscodeRequest<'a> {
    pub fn new(format: ImageFormat) -> Self;

    pub fn preferring_fidelity(mut self, targets: Vec<zencodec::encode::Fidelity>) -> Self;

    pub fn transcode(
        &self,
        codecs: &CodecSet,
        input: &[u8],
    ) -> Result<Outcome<EncodeOutput>, At<Error>>;

    pub fn transcode_from_read_at<S: zencodec::source::ReadAt + ?Sized>(
        &self,
        codecs: &CodecSet,
        source: &mut S,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>;

    pub fn transcode_from_read_at_with<S, R>(
        &self,
        codecs: &CodecSet,
        source: &mut S,
        resolver: &mut R,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>
    where
        S: zencodec::source::ReadAt + ?Sized,
        R: OperationPolicy + ?Sized;

    pub fn transcode_with<R: OperationPolicy + ?Sized>(
        &self,
        codecs: &CodecSet,
        input: &[u8],
        resolver: &mut R,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>;

    pub fn with_cms(mut self, cms: &'a dyn PluggableCms) -> Self;

    pub fn with_decode(mut self, request: operation::DecodeRequest) -> Self;

    pub fn with_encode_policy(mut self, policy: zencodec::encode::EncodePolicy) -> Self;

    pub fn with_fidelity(mut self, target: zencodec::encode::Fidelity) -> Self;

    pub fn with_io_options(mut self, options: DriveOptions) -> Self;

    pub fn with_metadata(mut self, policy: MetadataTransfer) -> Self;

    pub fn with_output(
        mut self,
        profile: OutputProfile,
        format: PixelFormat,
        options: ConvertOptions,
    ) -> Self;

    pub fn with_output_limit(mut self, bytes: usize) -> Self;

    pub fn with_policy_set(mut self, policy: PolicySet) -> Self;

    pub fn with_storage(mut self, storage: StorageBudget) -> Self;

    pub fn with_strip_rows(mut self, rows: u32) -> Self;
}

impl TranscodeRequest<'_> {
    pub async fn transcode_async<S: AsyncSource + ?Sized>(
        &self,
        codecs: &CodecSet,
        source: &mut S,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>;

    pub async fn transcode_async_with<S, R>(
        &self,
        codecs: &CodecSet,
        source: &mut S,
        resolver: &mut R,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>
    where
        S: AsyncSource + ?Sized,
        R: OperationPolicy + ?Sized;
}

impl Error {
    pub fn stage(&self) -> Stage;

    pub fn report(&self) -> &OperationReport;
}
Source at f0322466 ↗
2Await a network sourceAdd this need

The source can suspend for I/O. AsyncSource drives acquisition; this example still returns completed encoded bytes before the application sends them.

Implemented · review API
Source ↗
Imports used by this source
use zencodec::{
    At, CodecSet,
    encode::EncodeOutput,
    operation::{OperationPolicy, Outcome},
};
use zencodec_io::tokio_io::AsyncSource;
use zencodecs::checked_transcode::Error;
use zencodecs::checked_transcode::TranscodeRequest;
Show setup above ↑16 lines
use zencodec::{
    At, CodecSet,
    encode::EncodeOutput,
    operation::{OperationPolicy, Outcome},
};
use zencodec_io::tokio_io::AsyncSource;
use zencodecs::checked_transcode::Error;
use zencodecs::checked_transcode::TranscodeRequest;

pub async fn from_network<S: AsyncSource + ?Sized, P: OperationPolicy + ?Sized>(
    request: &TranscodeRequest<'_>,
    codecs: &CodecSet,
    source: &mut S,
    policy: &mut P,
) -> Result<Outcome<EncodeOutput>, At<Error>> {
    let output = request.transcode_async_with(codecs, source, policy).await?;
Show continuation below ↓4 lines
    // Send the finished, checked bytes afterwards. Codec CPU work runs on
    // this task: use a suitable worker runtime for expensive configurations.
    Ok(output)
}
Signatures & related contracts

Signatures from this revision · bodies omitted

Trait methods · zencodec-io feature: tokiozencodec_io::tokio_io::AsyncSource
pub trait AsyncSource: Send {
    fn read_at(
        &mut self,
        offset: u64,
        out: &mut [u8],
    ) -> impl Future<Output = Result<usize, BoxedError>> + Send;

    fn read_ranges(
        &mut self,
        reqs: &mut [(u64, &mut [u8])],
    ) -> impl Future<Output = Result<(), BoxedError>> + Send;

    fn len_hint(&self) -> Option<u64>;

    fn len(&mut self) -> impl Future<Output = Result<u64, BoxedError>> + Send;
}

Required methods: read_at, len. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Pinned dependency · imazen/zenpipe
use zencodec::{
    At, CodecSet, ImageFormat,
    encode::EncodeOutput,
    operation::{self, OperationReport, Outcome, OperationPolicy, PolicySet, MetadataTransfer},
    storage::StorageBudget,
};
use zencodec_io::{DriveOptions, tokio_io::AsyncSource};
use zenpixels::{ConvertOptions, PixelFormat};
use zenpixels_convert::{OutputProfile, cms::PluggableCms};
use zencodecs::checked_transcode::{Error, Stage, TranscodeRequest};

impl<'a> TranscodeRequest<'a> {
    pub fn new(format: ImageFormat) -> Self;

    pub fn preferring_fidelity(mut self, targets: Vec<zencodec::encode::Fidelity>) -> Self;

    pub fn transcode(
        &self,
        codecs: &CodecSet,
        input: &[u8],
    ) -> Result<Outcome<EncodeOutput>, At<Error>>;

    pub fn transcode_from_read_at<S: zencodec::source::ReadAt + ?Sized>(
        &self,
        codecs: &CodecSet,
        source: &mut S,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>;

    pub fn transcode_from_read_at_with<S, R>(
        &self,
        codecs: &CodecSet,
        source: &mut S,
        resolver: &mut R,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>
    where
        S: zencodec::source::ReadAt + ?Sized,
        R: OperationPolicy + ?Sized;

    pub fn transcode_with<R: OperationPolicy + ?Sized>(
        &self,
        codecs: &CodecSet,
        input: &[u8],
        resolver: &mut R,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>;

    pub fn with_cms(mut self, cms: &'a dyn PluggableCms) -> Self;

    pub fn with_decode(mut self, request: operation::DecodeRequest) -> Self;

    pub fn with_encode_policy(mut self, policy: zencodec::encode::EncodePolicy) -> Self;

    pub fn with_fidelity(mut self, target: zencodec::encode::Fidelity) -> Self;

    pub fn with_io_options(mut self, options: DriveOptions) -> Self;

    pub fn with_metadata(mut self, policy: MetadataTransfer) -> Self;

    pub fn with_output(
        mut self,
        profile: OutputProfile,
        format: PixelFormat,
        options: ConvertOptions,
    ) -> Self;

    pub fn with_output_limit(mut self, bytes: usize) -> Self;

    pub fn with_policy_set(mut self, policy: PolicySet) -> Self;

    pub fn with_storage(mut self, storage: StorageBudget) -> Self;

    pub fn with_strip_rows(mut self, rows: u32) -> Self;
}

impl TranscodeRequest<'_> {
    pub async fn transcode_async<S: AsyncSource + ?Sized>(
        &self,
        codecs: &CodecSet,
        source: &mut S,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>;

    pub async fn transcode_async_with<S, R>(
        &self,
        codecs: &CodecSet,
        source: &mut S,
        resolver: &mut R,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>
    where
        S: AsyncSource + ?Sized,
        R: OperationPolicy + ?Sized;
}

impl Error {
    pub fn stage(&self) -> Stage;

    pub fn report(&self) -> &OperationReport;
}
Source at f0322466 ↗

Rewrite metadata → refuse opaque ICC

1Change metadata without pixel reconstructionStart here

The product needs removal, not a new image encode. RewriteRequest retains the codestream and applies the selected metadata treatment.

Implemented · review API
Source ↗
Imports used by this source
use zencodec::{MetadataPolicy, decode::BoxedError, metadata_rewrite::RewriteRequest};
Show setup above ↑5 lines
use zencodec::{MetadataPolicy, decode::BoxedError, metadata_rewrite::RewriteRequest};

pub(crate) fn derivative(source: &[u8]) -> Result<Vec<u8>, BoxedError> {
    // Actual JPEG rewrite; scan data is borrowed without pixel decoding.
    let request = RewriteRequest::new(source).with_policy(MetadataPolicy::ColorAndRotation);
    let plan = zenjpeg::metadata::rewrite(&request)?;
Show continuation below ↓3 lines
    // This preserves opaque ICC. Success does not certify its privacy.
    Ok(plan.to_vec()?)
}
Signatures & related contracts

Signatures from this revision · bodies omitted

Inherent methodszencodec::metadata_rewrite::RewriteRequest
impl<'a> RewriteRequest<'a> {
    pub fn new(input: &'a [u8]) -> Self;

    pub fn with_policy(mut self, policy: MetadataPolicy) -> Self;
}

Selected methods · 20 more in the full contract.

Read defaults, imports and full contract ↗
2Handle an uninspectable profileAdd this need

Opaque ICC bytes may contain fields the privacy rule cannot inspect. Refuse rather than treating an unparsed payload as proven clean.

Implemented · review API
Source ↗
Imports used by this source
use std::io::Write;
use zencodec::{
    MetadataPolicy,
    decode::BoxedError,
    metadata_rewrite::{Action, RewriteRequest},
};
Show setup above ↑23 lines
use std::io::Write;
use zencodec::{
    MetadataPolicy,
    decode::BoxedError,
    metadata_rewrite::{Action, RewriteRequest},
};

#[derive(Debug)]
pub(crate) struct UninspectedIcc;
impl std::fmt::Display for UninspectedIcc {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str("retained ICC has not been inspected")
    }
}
impl std::error::Error for UninspectedIcc {}

pub(crate) fn checked_derivative(
    source: &[u8],
    writer: &mut dyn Write,
) -> Result<(), BoxedError> {
    let request = RewriteRequest::new(source).with_policy(MetadataPolicy::ColorAndRotation);
    let plan = zenjpeg::metadata::rewrite(&request)?;
    if plan
        .report()
        .iter()
        .any(|entry| entry.action() == Action::OpaqueIcc)
    {
        return Err(UninspectedIcc.into());
    }
    // All checks precede the first write. Writer errors can leave partial output.
    for chunk in plan.chunks() {
        writer.write_all(chunk)?;
    }
Show continuation below ↓2 lines
    Ok(())
}
Signatures & related contracts

Signatures from this revision · bodies omitted

Inherent methodszencodec::metadata_rewrite::Plan
impl<'a> Plan<'a> {
    pub fn report(&self) -> &[Entry];

    pub fn chunks(&self) -> impl Iterator<Item = &[u8]>;

    pub fn output_len(&self) -> usize;

    pub fn to_vec(&self) -> Result<Vec<u8>, Error>;
}

Selected methods · 1 more in the full contract.

Read defaults, imports and full contract ↗

Packets → owned frames → policy

1Gate decoded frame publicationStart here

The source is video. Wrap the native decoder once so every returned frame passes through the shared policy.

Implemented · review API
Source ↗
Imports used by this source
use zenavif::media::Av1VideoDecoder;
use zencodec::operation::PolicySet;
use zencodec_media::policy::{Error, PolicyDecoder};
Show setup above ↑6 lines
use zenavif::media::Av1VideoDecoder;
use zencodec::operation::PolicySet;
use zencodec_media::policy::{Error, PolicyDecoder};

pub fn open_video() -> Result<PolicyDecoder<Av1VideoDecoder, PolicySet>, Error> {
    let decoder = PolicyDecoder::try_new(Av1VideoDecoder::new, PolicySet::new(), None)?;
Show continuation below ↓4 lines
    // Configure and drive it through VideoDecoder; None adds no per-call Pulse.
    // Every receive checks policy before returning a native Frame.
    Ok(decoder)
}
Signatures & related contracts

Signatures from this revision · bodies omitted

Trait methodszencodec_media::session::VideoDecoder
pub trait VideoDecoder {
    type Error: core::error::Error + Send + Sync + 'static;

    fn configure(
        &mut self,
        config: &CodecConfig,
        pulse: Option<&dyn zencodec::Pulse>,
    ) -> Result<(), SessionError<Self::Error>>;

    fn send(
        &mut self,
        packet: &mut Packet,
        pulse: Option<&dyn zencodec::Pulse>,
    ) -> Result<SendStatus, SessionError<Self::Error>>;

    fn receive(
        &mut self,
        pulse: Option<&dyn zencodec::Pulse>,
    ) -> Result<Receive<Frame>, SessionError<Self::Error>>;

    fn end_input(&mut self) -> Result<(), SessionError<Self::Error>>;

    fn reset(&mut self) -> Result<(), SessionError<Self::Error>>;
}

Selected methods · 1 more in the full contract.

Required methods: configure, send, receive, end_input, reset, stream_info. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Inherent methodszencodec_media::policy::PolicyDecoder::try_new
impl<D: VideoDecoder, R: OperationPolicy> PolicyDecoder<D, R> {
    pub fn try_new<E: StdError + Send + Sync + 'static>(
        factory: impl FnOnce() -> Result<D, E>,
        resolver: R,
        pulse: Option<&dyn zencodec::Pulse>,
    ) -> Result<Self, Error>;
}

Selected methods · 1 more in the full contract.

Read defaults, imports and full contract ↗
Inherent methodszencodec_media::policy::PolicyDecoder
impl<D, R> PolicyDecoder<D, R> {
    pub fn new(decoder: D, resolver: R) -> Self;

    pub fn with_policy_set(mut self, preset: PolicySet) -> Self;

    pub fn report(&self) -> &OperationReport;

    pub const fn is_failed(&self) -> bool;
}
Read defaults, imports and full contract ↗
2Bound native picture allocationsAdd this need

A publication decision happens after reconstruction. This native setting adds picture-plane admission before allocation; it is not a total-RAM bound.

Implemented · review API
Source ↗
Imports used by this source
use zenavif::media::{Av1SessionError, Av1VideoDecoder};
use zencodec::operation::PolicySet;
use zencodec_media::policy::PolicyDecoder;
Show setup above ↑8 lines
use zenavif::media::{Av1SessionError, Av1VideoDecoder};
use zencodec::operation::PolicySet;
use zencodec_media::policy::PolicyDecoder;

pub fn open_video_with_picture_limit(
    picture_bytes: usize,
) -> Result<PolicyDecoder<Av1VideoDecoder, PolicySet>, Av1SessionError> {
    let mut settings = rav1d_safe::Settings::default();
    settings.max_picture_bytes = Some(picture_bytes);
    let native = Av1VideoDecoder::with_settings(settings)?;
    let decoder = PolicyDecoder::new(native, PolicySet::new());
Show continuation below ↓5 lines
    // Native admission precedes reconstruction. Policy precedes publication.
    // The allowance follows retained frames, including after decoder drop.
    // This bounds requested native plane bytes, not workspace or total RAM.
    Ok(decoder)
}
Signatures & related contracts

Signatures from this revision · bodies omitted

Inherent methodszencodec::storage::StorageBudget
impl StorageBudget {
    pub fn new(max_bytes: usize, max_objects: usize) -> Self;

    pub fn bytes(&self, len: usize) -> Result<OwnedStorage<u8>, StorageError>;

    pub fn reserved_bytes(&self) -> usize;

    pub fn reserved_objects(&self) -> usize;
}

Selected methods · 7 more in the full contract.

Read defaults, imports and full contract ↗
3Check each frame interpretationAdd this need

A later frame can carry different color facts. This callback refuses an unsupported transfer before publication; it does not perform tone mapping.

Implemented · review API
Source ↗
Imports used by this source
use zenavif::media::{Av1SessionError, Av1VideoDecoder};
use zencodec::{
    At,
    operation::{Decision, OperationPolicy, Question},
};
use zencodec_media::policy::PolicyDecoder;
Show setup above ↑22 lines
use zenavif::media::{Av1SessionError, Av1VideoDecoder};
use zencodec::{
    At,
    operation::{Decision, OperationPolicy, Question},
};
use zencodec_media::policy::PolicyDecoder;

#[derive(Debug)]
pub struct PreviewTransfer;
impl core::fmt::Display for PreviewTransfer {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.write_str("this preview route requires declared sRGB transfer")
    }
}
impl core::error::Error for PreviewTransfer {}

pub fn open_preview() -> Result<
    PolicyDecoder<Av1VideoDecoder, impl OperationPolicy<Error = PreviewTransfer>>,
    Av1SessionError,
> {
    let native = Av1VideoDecoder::new()?;
    let policy = |question: Question<'_>| -> Result<Decision, At<PreviewTransfer>> {
        if let Question::Frame { interpretation, .. } = question {
            let supported = interpretation
                .color
                .resolved
                .is_some_and(|color| color.transfer_characteristics == 13);
            if !supported {
                return Err(At::wrap(PreviewTransfer).at_named("preview transfer requirement"));
            }
        }
        Ok(Decision::UsePreset)
    };
    let decoder = PolicyDecoder::new(native, policy);
Show continuation below ↓4 lines
    // This gate does not convert YUV/GBR, tone-map or prove a RAM bound.
    // The renderer must still validate its complete conversion route.
    Ok(decoder)
}
Signatures & related contracts

Signatures from this revision · bodies omitted

Functionzencodec::operation::resolve_frame
pub fn resolve_frame<R: OperationPolicy + ?Sized>(
    interpretation: &FrameInterpretation,
    timing: FrameTiming,
    resolver: &mut R,
    preset: PolicySet,
    pulse: Option<&dyn crate::Pulse>,
) -> Result<DecisionRecord, At<PolicyFailure>>;
Read defaults, imports and full contract ↗

Require resolution; handle refusal

1Require a physical resolutionStart here

A print consumer relies on exact density. Ask for it on EncodeRequest and handle refusal when the format cannot represent it exactly.

Implemented · review API
Source ↗
Imports used by this source
use zencodec::{
    At, CodecSet, ImageFormat, Resolution,
    encode::EncodeOutput,
    operation::{EncodeRequest, OperationError, Outcome},
};
use zenpixels::PixelSlice;
Show setup above ↑13 lines
use zencodec::{
    At, CodecSet, ImageFormat, Resolution,
    encode::EncodeOutput,
    operation::{EncodeRequest, OperationError, Outcome},
};
use zenpixels::PixelSlice;

pub fn with_density(
    codecs: &CodecSet,
    pixels: PixelSlice<'_>,
    resolution: Resolution,
) -> Result<Outcome<EncodeOutput>, At<OperationError>> {
    let output = EncodeRequest::new(ImageFormat::Png)
        .with_resolution(resolution)
        .encode(codecs, pixels)?;
Show continuation below ↓4 lines
    // Required means exact: 254 DPI fits PNG; 300 DPI refuses.
    // No implicit rounding or unit change may alter the represented density.
    Ok(output)
}
Signatures & related contracts

Signatures from this revision · bodies omitted

Inherent methodszencodec::operation::EncodeRequest
impl EncodeRequest {
    pub const fn new(format: ImageFormat) -> Self;

    pub fn with_fidelity(mut self, fidelity: Fidelity) -> Self;

    pub fn preferring_fidelity(mut self, targets: impl Into<Vec<Fidelity>>) -> Self;

    pub const fn with_resolution(mut self, resolution: crate::Resolution) -> Self;

    pub const fn with_policy(mut self, policy: EncodePolicy) -> Self;

    pub const fn with_policy_set(mut self, preset: PolicySet) -> Self;

    pub fn encode(
        &self,
        codecs: &CodecSet,
        pixels: PixelSlice<'_>,
    ) -> Result<Outcome<EncodeOutput>, At<OperationError>>;

    pub fn encode_with<R: OperationPolicy + ?Sized>(
        &self,
        codecs: &CodecSet,
        pixels: PixelSlice<'_>,
        resolver: &mut R,
    ) -> Result<Outcome<EncodeOutput>, At<OperationError>>;
}

Selected methods · 5 more in the full contract.

Read defaults, imports and full contract ↗
Trait methodszencodec::encode::EncodeJob
pub trait EncodeJob: Sized {
    fn try_set_resolution(
        &mut self,
        resolution: crate::Resolution,
    ) -> Result<(), crate::ResolutionError>;

    fn try_with_resolution(
        mut self,
        resolution: crate::Resolution,
    ) -> Result<Self, crate::ResolutionError>;
}

Selected methods · 21 more in the full contract.

Required methods: with_pulse, with_limits, with_metadata, encoder, animation_frame_encoder. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Inherent methodszencodec::Resolution
impl Resolution {
    pub fn validate(&self) -> Result<(), ResolutionError>;

    pub fn is_equivalent_to(&self, other: &Self) -> bool;

    pub fn try_integer_density(
        &self,
        unit: ResolutionUnit,
        max: u32,
    ) -> Result<(u32, u32), ResolutionError>;
}
Read defaults, imports and full contract ↗

Inspect images → select JXL layers

1Inspect before decodingStart here

A large collection may contain images the caller never wants. Read the selected description before allocating its pixels.

Implemented · review API
Source ↗
Imports used by this source
use zencodec::{
    At,
    collection::{Collection, ImageDescription, ImageId},
    operation::{CheckedCollection, CollectionOperationError, OperationPolicy},
};
Show setup above ↑16 lines
use zencodec::{
    At,
    collection::{Collection, ImageDescription, ImageId},
    operation::{CheckedCollection, CollectionOperationError, OperationPolicy},
};

// The caller keeps this opened operation, so the returned ID can be used
// for later region requests. No pixel-format capability query is needed.
pub fn checked_first_description<C: Collection, R: OperationPolicy>(
    images: &mut CheckedCollection<C, R>,
) -> Result<Option<(ImageId, ImageDescription)>, At<CollectionOperationError>> {
    let mut roots = images.images()?;
    let Some(id) = images.next(&mut roots)? else {
        return Ok(None);
    };
    let description = images.describe(&id)?;
Show continuation below ↓4 lines
    // Stored geometry can guide layout/region choices. A later decode still
    // checks source consistency and must pass publication policy.
    Ok(Some((id, description)))
}
Signatures & related contracts

Signatures from this revision · bodies omitted

Inherent methodszencodec::operation::CheckedCollection<C, R>
impl<C: Collection, R: OperationPolicy> CheckedCollection<C, R> {
    pub fn open(
        pulse: SharedPulse,
        resolver: R,
        open: impl FnOnce(SharedPulse) -> Result<C, Cause>,
    ) -> Result<Self, At<CollectionOperationError>>;

    pub fn with_policy_set(mut self, preset: PolicySet) -> Self;

    pub fn images(&mut self) -> Result<C::Cursor, At<CollectionOperationError>>;

    pub fn children(&mut self, image: &ImageId) -> Result<C::Cursor, At<CollectionOperationError>>;

    pub fn next(
        &mut self,
        cursor: &mut C::Cursor,
    ) -> Result<Option<ImageId>, At<CollectionOperationError>>;

    pub fn describe(
        &mut self,
        image: &ImageId,
    ) -> Result<ImageDescription, At<CollectionOperationError>>;

    pub fn first_image(
        &mut self,
        request: &ImageRequest,
    ) -> Result<Option<Outcome<ImageOutput>>, At<CollectionOperationError>>;

    pub fn decode(
        &mut self,
        image: &ImageId,
        request: &ImageRequest,
    ) -> Result<Outcome<ImageOutput>, At<CollectionOperationError>>;

    pub fn decode_with<E: Into<Cause>>(
        &mut self,
        image: &ImageId,
        request: &ImageRequest,
        transform: impl FnOnce(ImageOutput, &dyn crate::Pulse) -> Result<ImageOutput, E>,
    ) -> Result<Outcome<ImageOutput>, At<CollectionOperationError>>;

    pub fn report(&self) -> &OperationReport;

    pub fn is_finished(&self) -> bool;
}

Selected methods · 1 more in the full contract.

Read defaults, imports and full contract ↗
Inherent methodszencodec::collection::ImageDescription
impl ImageDescription {
    pub const fn new(stored_size: (u32, u32)) -> Self;

    pub const fn with_orientation(mut self, orientation: Orientation) -> Self;

    pub const fn with_placement(mut self, placement: Placement) -> Self;

    pub fn validate(&self) -> Result<(), At<CollectionError>>;
}
Read defaults, imports and full contract ↗
2Select a JXL layer deliberatelyAdd this need

The application now edits layers. Preserve layer placement and distinguish a layer raster from a composited presentation.

Native JXL adapter qualification
Source ↗
Imports used by this source
use crate::{jxl_items::Limits, jxl_layers::JxlLayers};
use zencodec::{
    At, SharedPulse,
    collection::ImageRequest,
    image::ImageOutput,
    operation::{CheckedCollection, CollectionOperationError, Outcome, OperationPolicy},
};
Show setup above ↑19 lines
use crate::{jxl_items::Limits, jxl_layers::JxlLayers};
use zencodec::{
    At, SharedPulse,
    collection::ImageRequest,
    image::ImageOutput,
    operation::{CheckedCollection, CollectionOperationError, Outcome, OperationPolicy},
};

// The adapter fixes the uncomposited view before IDs are discovered.
// This qualified F32 path retains stored orientation and unrendered spots.
pub fn checked_jxl_layer_first<R: OperationPolicy>(
    bytes: &[u8],
    limits: Limits,
    resolver: R,
    stop: SharedPulse,
) -> Result<Option<Outcome<ImageOutput>>, At<CollectionOperationError>> {
    let mut images =
        CheckedCollection::open(stop, resolver, |stop| JxlLayers::open(bytes, limits, stop))?;
    let layer = images.first_image(&ImageRequest::default())?;
Show continuation below ↓4 lines
    // The result retains the full local samples, source blend facts, signed
    // canvas placement and explicit CMYK polarity. It is not a flat preview.
    Ok(layer)
}
Signatures & related contracts

Signatures from this revision · bodies omitted

Inherent methodscandidate::JxlLayers (private native F32 adapter)
impl<'a> JxlLayers<'a> {
    pub fn open(bytes: &'a [u8], limits: Limits, stop: SharedPulse) -> Result<Self, Failure>;
}

Selected methods · 3 more in the full contract.

Read defaults, imports and full contract ↗
Inherent methodszencodec::image::Placement
impl Placement {
    pub fn visible(
        self,
        geometry: ImageGeometry,
    ) -> Result<Option<PlacementOverlap>, PlacementError>;
}
Read defaults, imports and full contract ↗

Bound input → cancel → inspect errors

1Bound and cancel an operationStart here

An upload is untrusted or the user closes the viewer. Add resource limits and attach a SharedPulse to the set or job when the application needs cancellation or reporting. Borrowed per-call arguments accept None for no extra control; retained job controls and required limits still apply.

Lower-level configured decode
Source ↗
Imports used by this source
use std::borrow::Cow;
use zenbitmaps::QoiDecoderConfig;
use zencodec::ResourceLimits;
use zencodec::decode::{Decode, DecodeJob, DecodeOutput, DecoderConfig};
Show setup above ↑9 lines
use std::borrow::Cow;
use zenbitmaps::QoiDecoderConfig;
use zencodec::ResourceLimits;
use zencodec::decode::{Decode, DecodeJob, DecodeOutput, DecoderConfig};
type Error = Box<dyn std::error::Error + Send + Sync>;

pub fn limited(bytes: &[u8], max_pixels: u64) -> Result<DecodeOutput, Error> {
    let config = QoiDecoderConfig::new();
    let limits = ResourceLimits::none().with_max_pixels(max_pixels);
    let image = config
        .job()
        .with_limits(limits)
        .decoder(Cow::Borrowed(bytes), &[])?
        .decode()?;
Show continuation below ↓2 lines
    Ok(image)
}
Signatures & related contracts

Signatures from this revision · bodies omitted

Trait methodszencodec::decode::DecodeJob<'a>
pub trait DecodeJob<'a>: Sized {
    type Error: core::error::Error + Send + Sync + 'static;

    type Dec: Decode<Error = Self::Error>;

    type StreamDec: StreamingDecode<Error = Self::Error> + Send;

    type AnimationFrameDec: AnimationFrameDecoder<Error = Self::Error> + Send + 'static;

    fn with_limits(self, limits: ResourceLimits) -> Self;

    fn with_pulse(self, pulse: how_far::SharedPulse) -> Self;

    fn decoder(
        self,
        data: Cow<'a, [u8]>,
        preferred: &[PixelDescriptor],
    ) -> Result<Self::Dec, Self::Error>;

    fn dyn_incremental_decoder(
        self,
        preferred: &[PixelDescriptor],
    ) -> Result<Box<super::incremental::DynIncrementalDecoder>, BoxedError>;
}

Selected methods · 23 more in the full contract.

Required methods: with_pulse, with_limits, probe, output_info, decoder, push_decoder, streaming_decoder, animation_frame_decoder. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Functionzencodec::find_cause
pub fn find_cause<'a, T: core::error::Error + 'static>(
    mut err: &'a (dyn core::error::Error + 'static),
) -> Option<&'a T>;

Selected methods · 1 more in the full contract.

Read defaults, imports and full contract ↗
2Keep one cancellation source across the codecsAdd this need

Extend the registered gallery with the pixel limit and the application’s SharedPulse. CodecSet supplies that control to each checked operation. Keep the application handle that signals cancellation. When a borrowed control is needed, pass Some(pulse) to retain reporting, phase planning and sharing as well as cancellation. Use StopOnly at the boundary for an existing legacy Stop.

Native JPEG/PNG cancellation and publication controls
Source ↗
Imports used by this source
use zencodec::{CodecSet, ResourceLimits, SharedPulse};
use zencodecs::{JpegDecoderConfig, PngDecoderConfig, PngEncoderConfig};
Show setup above ↑5 lines
use zencodec::{CodecSet, ResourceLimits, SharedPulse};
use zencodecs::{JpegDecoderConfig, PngDecoderConfig, PngEncoderConfig};

pub fn gallery_codecs_with_cancellation(pulse: SharedPulse, max_pixels: u64) -> CodecSet {
    CodecSet::new()
        .with_limits(ResourceLimits::for_untrusted_input().with_max_pixels(max_pixels))
        .with_pulse(pulse)
        .with_decoder(JpegDecoderConfig::new())
        .with_decoder(PngDecoderConfig::new())
        .with_send_encoder(PngEncoderConfig::new())
Show continuation below ↓5 lines
}
// Keep the application's signalling handle so it can cancel during work.
// Passing SharedPulse preserves progress reporting and phase planning too.
// Borrowed per-call controls accept None; retained job controls still apply.
// Adapt a legacy Stop at the application boundary with StopOnly when needed.
Signatures & related contracts

Signatures from this revision · bodies omitted

Inherent methodszencodec::CodecSet
impl CodecSet {
    pub fn new() -> Self;

    pub fn with_decoder(mut self, config: impl DecoderConfig + 'static) -> Self;

    pub fn with_encoder<C>(mut self, config: C) -> Self
    where
        C: EncoderConfig + 'static,
        <C::Job as EncodeJob>::Enc: Encoder + Send,
        <C::Job as EncodeJob>::AnimationFrameEnc: AnimationFrameEncoder;

    pub fn with_send_encoder<C>(mut self, config: C) -> Self
    where
        C: EncoderConfig + 'static,
        C::Job: Send,
        <C::Job as EncodeJob>::Enc: Encoder + Send,
        <C::Job as EncodeJob>::AnimationFrameEnc: AnimationFrameEncoder;

    pub fn with_limits(mut self, limits: ResourceLimits) -> Self;

    pub fn with_pulse(mut self, pulse: how_far::SharedPulse) -> Self;

    pub fn pulse(&self) -> &dyn crate::Pulse;
}

Selected methods · 42 more in the full contract.

Read defaults, imports and full contract ↗

Animation ↔ video; preserve exact timing

1Keep exact animation timingStart here

An animation enters a video pipeline. The conformance test preserves rational timing through both adapters rather than rounding it at the boundary.

Media conformance test
Source ↗
Imports used by this source
use std::sync::Arc;
use enough::{Stop, StopReason};
use zencodec::decode::AnimationFrameDecoder;
use zencodec::encode::AnimationFrameEncoder;
use zencodec::how_far::NoPulse;
use zencodec::time::{FrameDuration, Rounding, TimeBase, Timestamp};
use zencodec_io::{DriveFailure, SliceCursor};
use zenpixels::{PixelBuffer, PixelDescriptor};
use zencodec_media::adapt::{AdaptError, AnimationVideoEncoder, Gray8, VideoAnimationDecoder};
use zencodec_media::container::{
    DemuxEvent, DemuxStage, Demuxer, DynMuxer, Input, MuxHint, MuxTiming, Muxer, SeekMode,
    SeekScope, SeekStatus, SeekTarget, erase_muxer,
};
use zencodec_media::frame::Frame;
use zencodec_media::limits::MediaLimits;
use zencodec_media::packet::{Packet, RandomAccess};
use zencodec_media::session::{
    CodecConfig, DynVideoDecoder, DynVideoEncoder, Receive, SendStatus, SessionError, VideoDecoder,
    drain_decoder, erase_video_decoder, erase_video_encoder, send_packet_until_accepted,
};
use zencodec_media::track::{CodecId, CodecTag, TrackInfo, VideoTrackInfo};
use zencodec_media_testkit::toy::{
    ToyDecoder, ToyDemuxer, ToyEncoder, ToyError, ToyMuxer, toy_payload,
};
use zencodec_media::session::UnsupportedMedia;
use zencodec_media::container::{MuxFinish, Patch, PatchError};
Show preceding definitions ↑746 lines
//! The toy reference implementations against every rule, and every
//! conformance check against the toy. Each test names the rule it enforces.

use std::sync::Arc;

use enough::{Stop, StopReason};
use zencodec::decode::AnimationFrameDecoder;
use zencodec::encode::AnimationFrameEncoder;
use zencodec::how_far::NoPulse;
use zencodec::time::{FrameDuration, Rounding, TimeBase, Timestamp};
use zencodec_io::{DriveFailure, SliceCursor};
use zenpixels::{PixelBuffer, PixelDescriptor};

use zencodec_media::adapt::{AdaptError, AnimationVideoEncoder, Gray8, VideoAnimationDecoder};
use zencodec_media::container::{
    DemuxEvent, DemuxStage, Demuxer, DynMuxer, Input, MuxHint, MuxTiming, Muxer, SeekMode,
    SeekScope, SeekStatus, SeekTarget, erase_muxer,
};
use zencodec_media::frame::Frame;
use zencodec_media::limits::MediaLimits;
use zencodec_media::packet::{Packet, RandomAccess};
use zencodec_media::session::{
    CodecConfig, DynVideoDecoder, DynVideoEncoder, Receive, SendStatus, SessionError, VideoDecoder,
    drain_decoder, erase_video_decoder, erase_video_encoder, send_packet_until_accepted,
};
use zencodec_media::track::{CodecId, CodecTag, TrackInfo, VideoTrackInfo};
use zencodec_media_testkit::toy::{
    ToyDecoder, ToyDemuxer, ToyEncoder, ToyError, ToyMuxer, toy_payload,
};

const W: u32 = 4;
const H: u32 = 3;
const PIX: usize = (W * H) as usize;

fn tb() -> TimeBase {
    TimeBase::new(1001, 30000).unwrap()
}

#[test]
fn exact_timing_default_refuses_without_starting_legacy_muxer() {
    use zencodec_media::session::UnsupportedMedia;
    let mut mux = ToyMuxer::new(false);
    assert!(matches!(
        Muxer::begin_exact_timing(&mut mux, &[], MuxHint::default()),
        Err(SessionError::Unsupported(UnsupportedMedia::ExactTiming))
    ));
    assert!(mux.pending_output().is_empty());
    let mut erased = erase_muxer(ToyMuxer::new(false));
    assert!(matches!(
        erased.begin_exact_timing(&[], MuxHint::forward_only()),
        Err(SessionError::Unsupported(UnsupportedMedia::ExactTiming))
    ));
    assert!(erased.pending_output().is_empty());
}

fn gray(v: u8) -> Vec<u8> {
    vec![v; PIX]
}

fn config() -> CodecConfig {
    CodecConfig::new(CodecId::Other(CodecTag::new("toy")), tb())
        .with_video(VideoTrackInfo::new(W, H))
}

fn packet(payload: Vec<u8>, pts: i64, ra: bool) -> Packet {
    Packet::from_vec(payload)
        .unwrap()
        .with_track(0)
        .with_pts(Timestamp::new(pts, tb()))
        .with_random_access(if ra {
            RandomAccess::Yes
        } else {
            RandomAccess::No
        })
}

/// Drive a demuxer over a slice to `Done`, handing each event to `on_event`.
fn demux_slice<D: Demuxer>(
    dem: &mut D,
    bytes: &[u8],
    window: usize,
    stop: Option<&dyn zencodec::Pulse>,
    mut on_event: impl FnMut(DemuxEvent) -> Result<(), SessionError<D::Error>>,
) -> Result<zencodec_io::DriveStats, DriveFailure<SessionError<D::Error>, std::convert::Infallible>>
{
    let mut cursor = SliceCursor::new(window);
    loop {
        let y = cursor.next(bytes, &mut DemuxStage(&mut *dem), stop)?;
        let done = y.is_finished();
        on_event(y.into_inner()).map_err(DriveFailure::Step)?;
        if done {
            return Ok(cursor.stats());
        }
    }
}

/// A TIVF file with `n` single-picture packets, pts = i, RAP every `keyint`.
fn toy_file(n: u32, keyint: u32, write_count: bool) -> Vec<u8> {
    let mut m = ToyMuxer::new(write_count);
    let track = TrackInfo::video(
        0,
        CodecId::Other(CodecTag::new("toy")),
        tb(),
        VideoTrackInfo::new(W, H),
    )
    .build()
    .unwrap();
    m.begin(&[track], MuxHint::default()).unwrap();
    let mut out = Vec::new();
    for i in 0..n {
        let p = packet(
            toy_payload(&[&gray(i as u8)], false),
            i64::from(i),
            i % keyint == 0,
        );
        m.write_packet(p, Some(&NoPulse)).unwrap();
        m.take_output(&mut out);
    }
    m.finish(Some(&NoPulse))
        .unwrap()
        .apply_to(&mut out)
        .unwrap();
    out
}

#[test]
fn absent_container_and_decode_controls_match_explicit_noop_bytes_samples_and_timing() {
    let track = TrackInfo::video(0, config().codec, tb(), VideoTrackInfo::new(W, H))
        .build()
        .unwrap();
    let mux = |pulse: Option<&dyn zencodec::Pulse>| {
        let mut mux = erase_muxer(ToyMuxer::new(true));
        mux.begin(std::slice::from_ref(&track), MuxHint::default())
            .unwrap();
        let mut out = Vec::new();
        for i in 0..3 {
            mux.write_packet(
                packet(toy_payload(&[&gray(i)], false), i64::from(i), true),
                pulse,
            )
            .unwrap();
            mux.take_output(&mut out);
        }
        mux.finish(pulse).unwrap().apply_to(&mut out).unwrap();
        out
    };
    let absent = mux(None);
    assert_eq!(absent, mux(Some(&NoPulse)));
    for pulse in [None, Some(&NoPulse as &dyn zencodec::Pulse)] {
        let mut demux = ToyDemuxer::new(MediaLimits::default());
        let mut packets = Vec::new();
        demux_slice(&mut demux, &absent, 7, pulse, |event| {
            if let DemuxEvent::Packet(packet) = event {
                packets.push(packet);
            }
            Ok(())
        })
        .unwrap();
        assert_eq!(packets.len(), 3);
        let mut decoder = erase_video_decoder(ToyDecoder::new(4));
        decoder.configure(&config(), pulse).unwrap();
        let mut frames = Vec::new();
        for mut packet in packets {
            send_packet_until_accepted(&mut decoder, &mut packet, pulse, |frame| {
                frames.push(frame);
                Ok::<(), core::convert::Infallible>(())
            })
            .unwrap();
        }
        drain_decoder(&mut decoder, pulse, |frame| {
            frames.push(frame);
            Ok::<(), core::convert::Infallible>(())
        })
        .unwrap();
        assert_eq!(frames.len(), 3);
        for (i, frame) in frames.iter().enumerate() {
            assert_eq!(frame.timing().pts, Some(Timestamp::new(i as i64, tb())));
            let mapped = frame.map().unwrap();
            let plane = mapped.plane(0).unwrap();
            for y in 0..H as usize {
                let zencodec_media::frame::PlaneSamples::U8(row) = plane.row(y).unwrap() else {
                    panic!("unexpected sample type")
                };
                assert_eq!(row, &gray(i as u8)[..W as usize]);
            }
        }
    }
}

// ------------------------------------------------------ object safety ---

fn assert_send<T: Send>() {}
fn takes_dyn(
    _: &DynVideoDecoder,
    _: &DynVideoEncoder,
    _: &(dyn DynMuxer + Send),
    _: &(dyn Demuxer<Error = ToyError> + Send),
) {
}

#[test]
fn sessions_are_object_safe_and_send() {
    assert_send::<ToyDecoder>();
    assert_send::<ToyEncoder>();
    assert_send::<ToyMuxer>();
    assert_send::<ToyDemuxer>();
    assert_send::<Frame>();
    let dec = erase_video_decoder(ToyDecoder::new(2));
    let enc = erase_video_encoder(ToyEncoder::new(W, H, tb(), 1, 3));
    let mux: Box<dyn DynMuxer + Send> = erase_muxer(ToyMuxer::new(false));
    let dem = ToyDemuxer::new(MediaLimits::default());
    takes_dyn(&*dec, &*enc, &*mux, &dem);
    // Decoder state moves between threads (zenjxl-decoder #60's requirement).
    let handle = std::thread::spawn(move || {
        let mut dec = dec;
        dec.configure(&config(), Some(&NoPulse)).unwrap();
        dec
    });
    let _dec = handle.join().unwrap();
}

// ---------------------------------------------- backpressure and drain ---

#[test]
fn receive_pending_keeps_the_packet_and_guarantees_output() {
    let mut dec = ToyDecoder::new(2);
    dec.configure(&config(), Some(&NoPulse)).unwrap();
    let mut p0 = packet(toy_payload(&[&gray(1), &gray(2)], false), 0, true);
    let mut p1 = packet(toy_payload(&[&gray(3)], false), 1, false);
    let mut p2 = packet(toy_payload(&[&gray(4)], false), 2, false);
    assert_eq!(
        dec.send(&mut p0, Some(&NoPulse)).unwrap(),
        SendStatus::Accepted
    );
    assert!(p0.is_taken());
    // p1 releases p0's two pictures into the ready queue (one-packet reorder).
    assert_eq!(
        dec.send(&mut p1, Some(&NoPulse)).unwrap(),
        SendStatus::Accepted
    );
    assert!(!matches!(
        dec.receive(Some(&NoPulse)).unwrap(),
        Receive::NeedInput
    ));
    // Refill: now ready has 1 and hold has 1 => a third send is backpressured.
    let st = dec.send(&mut p2, Some(&NoPulse)).unwrap();
    assert_eq!(st, SendStatus::ReceivePending);
    assert!(
        !p2.is_taken(),
        "a backpressured packet is retained for retry"
    );
    assert!(
        matches!(dec.receive(Some(&NoPulse)).unwrap(), Receive::Ready(_)),
        "ReceivePending guarantees output"
    );
    assert_eq!(
        dec.send(&mut p2, Some(&NoPulse)).unwrap(),
        SendStatus::Accepted
    );
    // Drain: end_input is idempotent, EndOfStream is stable.
    dec.end_input().unwrap();
    dec.end_input().unwrap();
    let mut n = 0;
    loop {
        match dec.receive(Some(&NoPulse)).unwrap() {
            Receive::Ready(_) => n += 1,
            Receive::EndOfStream => break,
            _ => panic!("NeedInput after end_input"),
        }
    }
    assert_eq!(n, 2, "p1's and p2's pictures were still held");
    assert!(matches!(
        dec.receive(Some(&NoPulse)).unwrap(),
        Receive::EndOfStream
    ));
    assert!(matches!(
        dec.send(
            &mut packet(toy_payload(&[], false), 3, false),
            Some(&NoPulse)
        ),
        Err(SessionError::Protocol(_))
    ));
}

#[test]
fn packets_are_not_frames_and_show_existing_shares_storage() {
    let mut dec = ToyDecoder::new(8);
    dec.configure(&config(), Some(&NoPulse)).unwrap();
    let mut frames: Vec<Frame> = Vec::new();
    fn feed(dec: &mut ToyDecoder, p: &mut Packet, frames: &mut Vec<Frame>) {
        send_packet_until_accepted(dec, p, Some(&NoPulse), |f| {
            frames.push(f);
            Ok::<(), ()>(())
        })
        .unwrap();
        while let Receive::Ready(f) = dec.receive(Some(&NoPulse)).unwrap() {
            frames.push(f);
        }
    }
    feed(
        &mut dec,
        &mut packet(toy_payload(&[&gray(1), &gray(2)], false), 0, true),
        &mut frames,
    ); // 2 pictures
    assert!(frames.is_empty(), "frames come out one packet late");
    feed(
        &mut dec,
        &mut packet(toy_payload(&[], false), 1, false),
        &mut frames,
    ); // 0 pictures
    assert_eq!(frames.len(), 2);
    feed(
        &mut dec,
        &mut packet(toy_payload(&[], true), 2, false),
        &mut frames,
    ); // show existing
    assert_eq!(frames.len(), 2);
    drain_decoder(&mut dec, Some(&NoPulse), |f| {
        frames.push(f);
        Ok::<(), ()>(())
    })
    .unwrap();
    assert_eq!(frames.len(), 3);
    let again = &frames[2];
    assert!(again.presentation().show_existing);
    assert!(
        again.shares_picture(&frames[1]),
        "the re-presentation shares the picture"
    );
    assert_ne!(
        again.timing().pts,
        frames[1].timing().pts,
        "but not the timing"
    );
    assert!(frames[0].presentation().keyframe && !frames[1].presentation().keyframe);
    assert_eq!(
        Arc::strong_count(frames[1].picture()),
        3,
        "two presentations plus the decoder's reference picture"
    );
    drop(dec);
    assert_eq!(
        Arc::strong_count(frames[1].picture()),
        2,
        "the decoder's reference is gone; both frames stay valid"
    );
    assert_eq!(frames[0].timing().packet_ordinal, None);
    assert_eq!(
        frames[2].map().unwrap().plane(0).unwrap().code(0, 0),
        Some(2)
    );
}

#[test]
fn an_old_mapping_survives_more_decoding_reset_and_decoder_drop() {
    let mut dec = ToyDecoder::new(8);
    dec.configure(&config(), Some(&NoPulse)).unwrap();
    dec.send(
        &mut packet(toy_payload(&[&gray(9)], false), 0, true),
        Some(&NoPulse),
    )
    .unwrap();
    dec.end_input().unwrap();
    let Receive::Ready(first) = dec.receive(Some(&NoPulse)).unwrap() else {
        panic!()
    };
    let mapping = first.map().unwrap();
    let plane = mapping.plane(0).unwrap();
    // Keep the mapping while the decoder decodes more, resets and is dropped.
    dec.reset().unwrap();
    dec.send(
        &mut packet(toy_payload(&[&gray(5)], false), 0, true),
        Some(&NoPulse),
    )
    .unwrap();
    dec.end_input().unwrap();
    let Receive::Ready(second) = dec.receive(Some(&NoPulse)).unwrap() else {
        panic!()
    };
    drop(dec);
    assert_eq!(plane.code(3, 2), Some(9));
    assert_eq!(second.map().unwrap().plane(0).unwrap().code(0, 0), Some(5));
    assert!(!first.shares_picture(&second));
}

#[test]
fn a_queued_frame_keeps_the_interpretation_it_was_decoded_under() {
    let mut dec = ToyDecoder::new(8);
    dec.configure(&config(), Some(&NoPulse)).unwrap();
    dec.send(
        &mut packet(toy_payload(&[&gray(1)], false), 0, true),
        Some(&NoPulse),
    )
    .unwrap();
    // Epoch change before the queued frame was received.
    let cfg = config().with_epoch(1);
    dec.configure(&cfg, Some(&NoPulse)).unwrap();
    let mut late = packet(toy_payload(&[&gray(2)], false), 1, true).with_config_epoch(1);
    dec.send(&mut late, Some(&NoPulse)).unwrap();
    assert!(
        matches!(
            dec.send(
                &mut packet(toy_payload(&[&gray(3)], false), 2, true),
                Some(&NoPulse)
            ),
            Err(SessionError::Protocol(_))
        ),
        "a packet from the old epoch is refused"
    );
    dec.end_input().unwrap();
    let Receive::Ready(old) = dec.receive(Some(&NoPulse)).unwrap() else {
        panic!()
    };
    let Receive::Ready(new) = dec.receive(Some(&NoPulse)).unwrap() else {
        panic!()
    };
    assert_eq!(old.interpretation().config_epoch, 0);
    assert_eq!(new.interpretation().config_epoch, 1);
    assert!(!Arc::ptr_eq(old.interpretation(), new.interpretation()));
}

struct Tripped;
impl Stop for Tripped {
    fn check(&self) -> Result<(), StopReason> {
        Err(StopReason::Cancelled)
    }
}

#[test]
fn cancellation_is_terminal_until_reset() {
    let mut dec = erase_video_decoder(ToyDecoder::new(2));
    dec.configure(&config(), Some(&NoPulse)).unwrap();
    assert!(matches!(
        dec.receive(Some(&zencodec::how_far::StopOnly::borrowed(&Tripped))),
        Err(SessionError::Cancelled(_))
    ));
    assert!(matches!(
        dec.receive(Some(&NoPulse)),
        Err(SessionError::Finished)
    ));
    dec.reset().unwrap();
    assert!(matches!(
        dec.receive(Some(&NoPulse)),
        Ok(Receive::NeedInput)
    ));
    // A codec error is terminal the same way.
    let mut bad = packet(vec![9, 0], 0, true);
    assert!(matches!(
        dec.send(&mut bad, Some(&NoPulse)),
        Err(SessionError::Codec(_))
    ));
    assert!(matches!(
        dec.send(&mut bad, Some(&NoPulse)),
        Err(SessionError::Finished)
    ));
}

// ------------------------------------------------------------ container ---

#[test]
fn demuxer_output_is_identical_for_every_window_and_truncation_is_typed() {
    let file = toy_file(7, 3, true);
    let mut baseline: Option<Vec<(i64, RandomAccess, Vec<u8>)>> = None;
    for window in [1usize, 7, 64, file.len()] {
        let mut dem = ToyDemuxer::new(MediaLimits::default());
        let mut got = Vec::new();
        let mut headers = 0;
        let stats = demux_slice(&mut dem, &file, window, Some(&NoPulse), |ev| {
            match ev {
                DemuxEvent::Headers => headers += 1,
                DemuxEvent::Packet(p) => got.push((
                    p.pts().unwrap().ticks(),
                    p.random_access(),
                    p.bytes().to_vec(),
                )),
                _ => {}
            }
            Ok(())
        })
        .unwrap();
        assert_eq!(headers, 1);
        assert_eq!(got.len(), 7);
        assert!(stats.steps >= 8);
        match &baseline {
            None => baseline = Some(got),
            Some(b) => assert_eq!(b, &got, "window {window}"),
        }
        assert_eq!(dem.tracks()[0].declared_packets(), Some(7));
    }
    // Truncated inside a packet: a codec error from the stage, not a driver violation.
    let mut dem = ToyDemuxer::new(MediaLimits::default());
    let cut = &file[..file.len() - 3];
    let err = demux_slice(&mut dem, cut, 5, Some(&NoPulse), |_| Ok(())).unwrap_err();
    assert!(
        matches!(
            err,
            DriveFailure::Step(SessionError::Codec(ToyError::Truncated))
        ),
        "{err:?}"
    );
    // A packet above the limit is a typed limit error, checked before allocation.
    let mut dem = ToyDemuxer::new(MediaLimits::default().with_max_packet_bytes(4));
    let err = demux_slice(&mut dem, &file, 64, Some(&NoPulse), |_| Ok(())).unwrap_err();
    assert!(
        matches!(err, DriveFailure::Step(SessionError::Limit(_))),
        "{err:?}"
    );
    // Unrequested input (wrong offset) is ignored: nothing consumed, a request named.
    let mut dem = ToyDemuxer::new(MediaLimits::default());
    let p = dem.step(Input::new(5, &file[5..]), Some(&NoPulse)).unwrap();
    assert_eq!(p.consumed, 0);
    assert!(matches!(p.event, DemuxEvent::Need));
    assert!(!dem.requested().is_empty());
}

#[test]
fn muxer_bytes_do_not_depend_on_how_they_are_drained() {
    let track = TrackInfo::video(
        0,
        CodecId::Other(CodecTag::new("toy")),
        tb(),
        VideoTrackInfo::new(W, H),
    )
    .build()
    .unwrap();
    let run = |drain: u8| -> Vec<u8> {
        let mut m = ToyMuxer::new(true);
        assert_eq!(
            m.begin(std::slice::from_ref(&track), MuxHint::default())
                .unwrap(),
            MuxTiming::streaming_with_patches()
        );
        let mut out = Vec::new();
        let mut rng = 12345u32;
        for i in 0..9u32 {
            m.write_packet(
                packet(
                    toy_payload(&[&gray(i as u8)], false),
                    i64::from(i),
                    i % 4 == 0,
                ),
                Some(&NoPulse),
            )
            .unwrap();
            match drain {
                0 => {}
                1 => {
                    let n = m.pending_output().len();
                    out.extend_from_slice(m.pending_output());
                    m.consume_output(n);
                }
                2 => {
                    rng = rng.wrapping_mul(1103515245).wrapping_add(12345);
                    let n = (rng >> 8) as usize % (m.pending_output().len() + 2);
                    out.extend_from_slice(&m.pending_output()[..n.min(m.pending_output().len())]);
                    m.consume_output(n); // may exceed pending: clamps
                }
                _ => m.take_output(&mut out),
            }
        }
        m.finish(Some(&NoPulse))
            .unwrap()
            .apply_to(&mut out)
            .unwrap();
        out
    };
    let undrained = run(0);
    assert_eq!(run(1), undrained);
    assert_eq!(run(2), undrained);
    assert_eq!(run(3), undrained);
    assert_eq!(
        u32::from_le_bytes(undrained[16..20].try_into().unwrap()),
        9,
        "the patch landed"
    );
    // A patching layout is refused for a forward-only destination at begin.
    let mut m = ToyMuxer::new(true);
    assert!(matches!(
        m.begin(std::slice::from_ref(&track), MuxHint::forward_only()),
        Err(SessionError::Unsupported(_))
    ));
    let mut m = ToyMuxer::new(false);
    assert_eq!(
        m.begin(std::slice::from_ref(&track), MuxHint::forward_only())
            .unwrap(),
        MuxTiming::streaming()
    );
    assert!(m.finish(Some(&NoPulse)).unwrap().patches.is_empty());
    // Begin twice is a protocol violation; a packet on another clock is refused.
    let mut m = ToyMuxer::new(false);
    m.begin(std::slice::from_ref(&track), MuxHint::default())
        .unwrap();
    assert!(matches!(
        m.begin(std::slice::from_ref(&track), MuxHint::default()),
        Err(SessionError::Protocol(_))
    ));
    let wrong = Packet::from_vec(vec![1])
        .unwrap()
        .with_track(0)
        .with_pts(Timestamp::new(0, TimeBase::MILLIS));
    assert!(matches!(
        m.write_packet(wrong, Some(&NoPulse)),
        Err(SessionError::Protocol(_))
    ));
}

#[test]
fn malformed_finish_does_not_partially_modify_the_output() {
    use zencodec_media::container::{MuxFinish, Patch, PatchError};
    let original = b"prefix".to_vec();
    for bad in [
        Patch {
            offset: 99,
            bytes: vec![1],
        },
        Patch {
            offset: u64::MAX,
            bytes: vec![1, 2],
        },
    ] {
        let mut output = original.clone();
        let finish = MuxFinish::new(
            b"tail".to_vec(),
            vec![
                Patch {
                    offset: 0,
                    bytes: b"BAD".to_vec(),
                },
                bad,
            ],
        );
        assert_eq!(finish.apply_to(&mut output), Err(PatchError::OutOfRange));
        assert_eq!(
            output, original,
            "validate every patch before applying any effect"
        );
    }
}

// ----------------------------------------------------------------- seek ---

#[test]
fn seek_by_time_reports_the_actual_pts_and_random_access_evidence() {
    let file = toy_file(10, 4, false); // RAPs at 0, 4, 8
    let mut dem = ToyDemuxer::new(MediaLimits::default());
    let mut cursor = SliceCursor::new(16);
    assert!(matches!(
        cursor
            .next(&file, &mut DemuxStage(&mut dem), Some(&NoPulse))
            .unwrap()
            .into_inner(),
        DemuxEvent::Headers
    ));
    // Target 6.5 frames in, asked in milliseconds: the demuxer compares exactly.
    let target_ms = Timestamp::new(6, tb())
        .rescale(TimeBase::MILLIS, Rounding::Nearest)
        .unwrap();
    let seek = |dem: &mut ToyDemuxer, cursor: &mut SliceCursor, mode, scope| {
        let t = SeekTarget::new(0, target_ms, mode, scope);
        match dem.seek(&t).unwrap() {
            SeekStatus::Positioned(r) => r,
            SeekStatus::Scanning => loop {
                if let DemuxEvent::Seeked(r) = cursor
                    .next(&file, &mut DemuxStage(&mut *dem), Some(&NoPulse))
                    .unwrap()
                    .into_inner()
                {
                    break r;
                }
            },
            _ => unreachable!(),
        }
    };
    let r = seek(&mut dem, &mut cursor, SeekMode::Nearest, SeekScope::Any);
    assert_eq!(r.pts.ticks(), 6);
    assert_eq!(r.random_access, RandomAccess::No);
    let r = seek(
        &mut dem,
        &mut cursor,
        SeekMode::AtOrBefore,
        SeekScope::RandomAccessPoints,
    );
    assert_eq!(
        (r.pts.ticks(), r.ordinal, r.random_access),
        (4, 4, RandomAccess::Yes)
    );
    let r = seek(
        &mut dem,
        &mut cursor,
        SeekMode::AtOrAfter,
        SeekScope::RandomAccessPoints,
    );
    assert_eq!((r.pts.ticks(), r.ordinal), (8, 8));
    // After a seek the packets resume at the position, and the decoder is
    // reset + reconfigured by the driver; decode work is reported.
    let r = seek(
        &mut dem,
        &mut cursor,
        SeekMode::AtOrBefore,
        SeekScope::RandomAccessPoints,
    );
    assert_eq!(r.ordinal, 4);
    let mut dec = ToyDecoder::new(8);
    dec.reset().unwrap();
    dec.configure(&CodecConfig::from(&dem.tracks()[0]), Some(&NoPulse))
        .unwrap();
    let (mut packets_read, mut discarded, mut shown) = (0u64, 0u64, Vec::new());
    loop {
        match cursor
            .next(&file, &mut DemuxStage(&mut dem), Some(&NoPulse))
            .unwrap()
            .into_inner()
        {
            DemuxEvent::Packet(mut p) => {
                packets_read += 1;
                send_packet_until_accepted(&mut dec, &mut p, Some(&NoPulse), |f| {
                    if f.timing().pts.unwrap().compare(target_ms) == std::cmp::Ordering::Less {
                        discarded += 1;
                    } else {
                        shown.push(f.timing().pts.unwrap().ticks());
                    }
                    Ok::<(), ()>(())
                })
                .unwrap();
            }
            DemuxEvent::Done => break,
            _ => {}
        }
    }
    drain_decoder(&mut dec, Some(&NoPulse), |f| {
        shown.push(f.timing().pts.unwrap().ticks());
        Ok::<(), ()>(())
    })
    .unwrap();
    assert_eq!(packets_read, 6);
    assert_eq!(
        discarded, 2,
        "frames 4 and 5 decoded and discarded to reach 6"
    );
    assert_eq!(shown, vec![6, 7, 8, 9]);
}

// ------------------------------------------------- animation over video ---

#[test]
fn animation_round_trips_through_the_video_model_with_exact_timing() {
    let frames: Vec<PixelBuffer> = (0..5u8)
        .map(|i| {
            let mut b = PixelBuffer::new(W, H, PixelDescriptor::GRAY8_SRGB);
            for y in 0..H {
                for (x, px) in b.as_slice_mut().row_mut(y).iter_mut().enumerate() {
                    *px = i * 40 + y as u8 * 4 + x as u8;
                }
            }
            b
        })
        .collect();
    let durations: Vec<FrameDuration> = (1..=5u64)
        .map(|n| FrameDuration::new(n * 1001, 30000).unwrap())
        .collect();

    // Encode: the animation encoder is a layer over VideoEncoder + Muxer, and
    // PR 149's begin/pending_output are the muxer's pull buffer.
    let encode = |drain: bool| -> (zencodec::encode::AnimationOutputTiming, Vec<u8>, Vec<usize>) {
        let enc = ToyEncoder::new(W, H, tb(), 1, 3);
        let mut a =
            AnimationVideoEncoder::new(enc, ToyMuxer::new(false), Gray8::default(), 1).unwrap();
        let timing = a.begin(W, H, PixelDescriptor::GRAY8_SRGB, Some(5)).unwrap();
        let mut out = Vec::new();
        let mut pending_after_push = Vec::new();
        if drain {
            a.take_output(&mut out);
        }
        for (f, d) in frames.iter().zip(&durations) {
            a.push_frame_exact(f.as_slice(), *d, None).unwrap();
            pending_after_push.push(out.len() + a.pending_output().len());
            if drain {
                a.take_output(&mut out);
            }
        }
        out.extend_from_slice(a.finish(None).unwrap().data());
        (timing, out, pending_after_push)
    };
    let (timing, drained, points) = encode(true);
    let (_, undrained, _) = encode(false);
    assert_eq!(timing.lag_frames(), Some(1));
    assert_eq!(drained, undrained, "draining never changes the bytes");
    // With a lag of one frame, bytes grow between any two points two frames apart.
    for w in points.windows(2) {
        assert!(
            w[1] > w[0],
            "no bytes became pending across the lag: {points:?}"
        );
    }
    // A fractional duration on this clock is refused as AnimationTiming.
    let enc = ToyEncoder::new(W, H, tb(), 1, 3);
    let mut a = AnimationVideoEncoder::new(enc, ToyMuxer::new(false), Gray8::default(), 1).unwrap();
    let err = a.push_frame(frames[0].as_slice(), 40, None).unwrap_err();
    assert!(matches!(
        err,
        AdaptError::Unsupported(zencodec::UnsupportedOperation::AnimationTiming)
    ));

    // Decode through the real AnimationFrameDecoder trait.
    let dem = ToyDemuxer::new(MediaLimits::default());
    let dec = ToyDecoder::new(4);
    let mut anim = VideoAnimationDecoder::new(
        drained.clone().into(),
        dem,
        dec,
        Gray8::default(),
        durations[4],
        5,
    );
    let mut got = Vec::new();
    while let Some(f) = anim.render_next_frame(None).unwrap() {
        got.push((f.duration(), f.frame_index(), f.pixels().row(1).to_vec()));
    }
    assert_eq!(got.len(), 5);
    assert_eq!(anim.info().width, W);
    for (i, (d, idx, row)) in got.iter().enumerate() {
        assert_eq!(*d, durations[i], "frame {i} duration from adjacent pts");
        assert_eq!(*idx, i as u32);
        assert_eq!(row, &frames[i].as_slice().row(1).to_vec());
    }
    assert_eq!(anim.frames_decoded(), 5);
}
Show following definitions ↓72 lines


#[test]
fn declared_frame_count_is_binding() {
    let enc = ToyEncoder::new(W, H, tb(), 1, 3);
    let mut a = AnimationVideoEncoder::new(enc, ToyMuxer::new(false), Gray8::default(), 1).unwrap();
    a.begin(W, H, PixelDescriptor::GRAY8_SRGB, Some(2)).unwrap();
    let b = PixelBuffer::new(W, H, PixelDescriptor::GRAY8_SRGB);
    a.push_frame_exact(b.as_slice(), FrameDuration::new(1001, 30000).unwrap(), None)
        .unwrap();
    assert!(matches!(
        a.finish(None),
        Err(AdaptError::FrameCountMismatch)
    ));
}

#[test]
fn finish_preflight_supports_tail_patches_and_ordered_overlaps_and_rejects_overflow() {
    use zencodec_media::container::{MuxFinish, Patch, PatchError};
    let finish = MuxFinish::new(
        b"tail".to_vec(),
        vec![
            Patch {
                offset: 3,
                bytes: b"XYZ".to_vec(),
            },
            Patch {
                offset: 4,
                bytes: b"!".to_vec(),
            },
            Patch {
                offset: 7,
                bytes: vec![],
            },
        ],
    );
    assert_eq!(finish.validate(3), Ok(7));
    let mut output = b"pre".to_vec();
    finish.apply_to(&mut output).unwrap();
    assert_eq!(&output, b"preX!Zl");
    assert_eq!(
        MuxFinish::new(vec![1], vec![]).validate(u64::MAX),
        Err(PatchError::OutOfRange)
    );
    let finish = MuxFinish::new(
        vec![],
        vec![Patch {
            offset: 4,
            bytes: vec![],
        }],
    );
    assert_eq!(finish.validate(3), Err(PatchError::OutOfRange));
}

#[test]
#[allow(deprecated)]
fn deprecated_finish_helper_uses_the_same_transactional_validation() {
    use zencodec_media::container::{MuxFinish, Patch, PatchError, apply_finish};
    let mut output = b"keep".to_vec();
    let bad = MuxFinish::new(
        b"tail".to_vec(),
        vec![Patch {
            offset: 9,
            bytes: vec![0],
        }],
    );
    assert_eq!(apply_finish(&mut output, bad), Err(PatchError::OutOfRange));
    assert_eq!(&output, b"keep");
    apply_finish(&mut output, MuxFinish::new(b"tail".to_vec(), vec![])).unwrap();
    assert_eq!(&output, b"keeptail");
}
Signatures & related contracts

Signatures from this revision · bodies omitted

Trait methodszencodec::decode::AnimationFrameDecoder
pub trait AnimationFrameDecoder: Sized {
    type Error: core::error::Error + Send + Sync + 'static;

    fn render_next_frame(
        &mut self,
        pulse: Option<&dyn crate::Pulse>,
    ) -> Result<Option<AnimationFrame<'_>>, Self::Error>;

    fn render_next_frame_owned(
        &mut self,
        pulse: Option<&dyn crate::Pulse>,
    ) -> Result<Option<OwnedAnimationFrame>, Self::Error>;
}

Selected methods · 5 more in the full contract.

Required methods: wrap_sink_error, info, render_next_frame, render_next_frame_to_sink. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Trait methodszencodec_media::session::VideoDecoder
pub trait VideoDecoder {
    type Error: core::error::Error + Send + Sync + 'static;

    fn configure(
        &mut self,
        config: &CodecConfig,
        pulse: Option<&dyn zencodec::Pulse>,
    ) -> Result<(), SessionError<Self::Error>>;

    fn send(
        &mut self,
        packet: &mut Packet,
        pulse: Option<&dyn zencodec::Pulse>,
    ) -> Result<SendStatus, SessionError<Self::Error>>;

    fn receive(
        &mut self,
        pulse: Option<&dyn zencodec::Pulse>,
    ) -> Result<Receive<Frame>, SessionError<Self::Error>>;

    fn end_input(&mut self) -> Result<(), SessionError<Self::Error>>;

    fn reset(&mut self) -> Result<(), SessionError<Self::Error>>;
}

Selected methods · 1 more in the full contract.

Required methods: configure, send, receive, end_input, reset, stream_info. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗

Plan color + layout → execute on held pixels

1Require both layout and colorStart here

A native image has auxiliary channels and a particular color interpretation. Prepare one output plan and execute it on the held source; layout conversion alone is insufficient.

Native JXL + zenpixels-convert qualification
Source ↗
Imports used by this source
use zencodec::At;
use zenpixels::{ColorOrigin, PixelDescriptor, PixelSlice};
use zenpixels_convert::{ConvertError, ConvertOptions, OutputPlan, OutputProfile};
Show setup above ↑10 lines
use zencodec::At;
use zenpixels::{ColorOrigin, PixelDescriptor, PixelSlice};
use zenpixels_convert::{ConvertError, ConvertOptions, OutputPlan, OutputProfile};

fn plan_required_layout(
    source: &PixelSlice<'_>,
    origin: &ColorOrigin,
    required: PixelDescriptor,
) -> Result<OutputPlan, At<ConvertError>> {
    OutputPlan::new_preserving_samples(
        source.descriptor(),
        source.color_context().cloned(),
        origin,
        OutputProfile::SameAsOrigin,
        required,
    )
Show continuation below ↓1 lines
}
Signatures & related contracts

Signatures from this revision · bodies omitted

Inherent methodszencodec::image::ImageOutput
impl ImageOutput {
    pub fn interpretation(&self) -> &ImageInterpretation;

    pub fn source_frame(&self) -> Option<&Arc<FrameInterpretation>>;

    pub fn map(&self) -> Result<Box<dyn MappedImage + '_>, At<OutputError>>;

    pub fn with_pixels<T>(
        &self,
        consume: impl FnOnce(PixelSlice<'_>) -> T,
    ) -> Result<T, At<OutputError>>;
}

Selected methods · 4 more in the full contract.

Read defaults, imports and full contract ↗
Pinned dependency · imazen/zenpixels
use alloc::sync::Arc;
use zenpixels::{ColorContext, ColorOrigin, PixelDescriptor, PixelFormat, PixelSlice, PixelSliceMut};
use zenpixels_convert::{ConvertError, ConvertOptions, OutputPlan, output::{OutputMetadata, OutputProfile}};
use whereat::At;

impl OutputPlan {
    pub fn new(
        source_desc: PixelDescriptor,
        current: Option<Arc<ColorContext>>,
        origin: &ColorOrigin,
        target: OutputProfile,
        pixel_format: PixelFormat,
        options: &ConvertOptions,
        cms: Option<&dyn zenpixels_convert::cms::PluggableCms>,
    ) -> Result<Self, At<ConvertError>>;

    pub fn new_with_descriptor(
        source_desc: PixelDescriptor,
        current: Option<Arc<ColorContext>>,
        origin: &ColorOrigin,
        target: OutputProfile,
        output: PixelDescriptor,
        options: &ConvertOptions,
        cms: Option<&dyn zenpixels_convert::cms::PluggableCms>,
    ) -> Result<Self, At<ConvertError>>;

    pub fn new_preserving_samples(
        source_desc: PixelDescriptor,
        current: Option<Arc<ColorContext>>,
        origin: &ColorOrigin,
        target: OutputProfile,
        output: PixelDescriptor,
    ) -> Result<Self, At<ConvertError>>;

    pub fn output_descriptor(&self) -> PixelDescriptor;

    pub fn metadata(&self) -> &OutputMetadata;

    pub fn prepare(&mut self, max_width: u32) -> Result<(), At<ConvertError>>;

    pub fn borrow_rows<'a>(
        &self,
        source: PixelSlice<'a>,
    ) -> Result<Option<PixelSlice<'a>>, At<ConvertError>>;

    pub fn convert_rows<'a>(
        &mut self,
        source: PixelSlice<'_>,
        mut destination: PixelSliceMut<'a>,
    ) -> Result<PixelSliceMut<'a>, At<ConvertError>>;
}

impl ConvertOptions {
    pub const fn retain_channels() -> Self;

    pub const fn with_clip_out_of_gamut(mut self, clip: bool) -> Self;
}
Source at f23ac753 ↗

Every exported signature

All features on the generation host (Linux), across eight crates. Canonical compiler paths; blanket, auto and derived implementations omitted. Private experiments and dependency-only APIs are outside this index. A signature is not a claim of native codec support.

The search also includes three compiler-omitted inherent dyn SourceEncodingDetails methods, extracted from source with their documentation and links. Crate counts and raw API files remain compiler-derived. Source-backed supplements.

Loading signatures…

Coverage, source and regeneration

The flows above cover concrete uses; the index covers the compiler-derived exported surface. Open each contract for its source documentation. Items without local docs link to their crate source. Third-party APIs used by examples, including zenpixels-convert, retain their original imports and source links.

just code-catalog /path/to/prepared/source-export

The build rejects a catalog whose Rust sources or manifest changed. Raw API files: zencodec · zencodec-io · zencodec-coroutine · zencodec-media · zencodec-testkit · zencodec-media-testkit · zenmeta · zencodec-render.