Skip to main content

zencodec_io/
policy_decoder.rs

1//! Serve mandatory decoder suspensions through one fallible policy resolver.
2
3use core::fmt;
4
5use zencodec::decode::{
6    ControlledIncrementalDecode, ControlledProgress, DecodeRowSink, Event, Fused,
7    IncrementalDecode, Input, InputRequest, MetadataUpdate, Progress, StepError,
8};
9use zencodec::operation::{
10    OperationPolicy, OperationReport, PolicyFailure, PolicySet, UnmetRequirements, resolve_boundary,
11};
12use zencodec::source::AccessPattern;
13use zencodec::{At, ImageInfo};
14use zenpixels::PixelDescriptor;
15
16/// Native failure or traced application-policy failure during controlled decode.
17#[derive(Debug)]
18#[non_exhaustive]
19pub enum PolicyDecodeError<E> {
20    /// The native decoder's original error.
21    Native(E),
22    /// Required workspace is unqualified before native polling.
23    Requirements(At<UnmetRequirements>),
24    /// Callback refusal/failure, invalid choice, or cancellation at a decision.
25    Policy(At<PolicyFailure>),
26}
27impl<E: fmt::Display> fmt::Display for PolicyDecodeError<E> {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        match self {
30            Self::Native(e) => e.fmt(f),
31            Self::Requirements(e) => e.fmt(f),
32            Self::Policy(e) => e.fmt(f),
33        }
34    }
35}
36impl<E: core::error::Error + 'static> core::error::Error for PolicyDecodeError<E> {
37    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
38        Some(match self {
39            Self::Native(e) => e,
40            Self::Requirements(e) => e,
41            Self::Policy(e) => e,
42        })
43    }
44}
45
46/// Connect a controlled decoder to every existing slice, file and network
47/// driver. Decisions run only at actual suspensions, before native work resumes.
48/// The decoder remains terminal after a callback failure or caught unwind.
49///
50/// `ContinueReading` leaves affected output blocked. This adapter does not
51/// certify native workspace or admit a codec's internal staging. Its bounded
52/// report contains facts and decisions, not metadata or application payloads.
53/// A workspace requirement refuses before the first native step because this
54/// adapter has no qualified native bound. Construction of the supplied decoder
55/// precedes this check and remains the caller/adapter's admission responsibility.
56pub struct PolicyDecoder<D, R> {
57    decoder: Option<Fused<D>>,
58    resolver: R,
59    preset: PolicySet,
60    report: OperationReport,
61    failed: bool,
62}
63impl<D, R> PolicyDecoder<D, R> {
64    /// Use the resolver at mandatory boundaries and the default built-in policy
65    /// when it returns `UsePreset`.
66    pub fn new(decoder: D, resolver: R) -> Self {
67        Self {
68            decoder: Some(Fused::new(decoder)),
69            resolver,
70            preset: PolicySet::new(),
71            report: OperationReport::default(),
72            failed: false,
73        }
74    }
75    /// Configure the built-in policy used for explicit delegation.
76    /// During migration, these requirements and the policy argument
77    /// both apply. A bound policy supplies preferences in place of these.
78    #[deprecated(note = "Pass PolicySet or BoundPolicy as the operation policy argument")]
79    pub fn with_policy_set(mut self, preset: PolicySet) -> Self {
80        self.preset = preset;
81        self
82    }
83    /// Decisions made before success or failure. Available after a driver error.
84    pub const fn report(&self) -> &OperationReport {
85        &self.report
86    }
87}
88impl<D, R: OperationPolicy> PolicyDecoder<D, R> {
89    /// Admit requirements before constructing a native decoder.
90    ///
91    /// Prefer this to [`Self::new`] when native construction may allocate. The
92    /// factory is invoked exactly once on successful admission and never on
93    /// admission refusal. Its original error is retained as
94    /// [`PolicyDecodeError::Native`]. The same policy is moved into the returned
95    /// adapter, where metadata and interpretation decisions still occur later.
96    ///
97    /// This generic adapter has no qualified native workspace bound, so any
98    /// configured workspace limit refuses with `Workspace(Unknown)` before the
99    /// factory runs. Other requirements remain pending for their boundaries;
100    /// success here does not certify metadata, fidelity or output scheduling.
101    /// Configure requirements on `policy` before this call, not with a later
102    /// legacy setter. Values evaluated before the closure is invoked (including
103    /// captured allocations) cannot be admitted by this method.
104    ///
105    /// ```ignore
106    /// let decoder = PolicyDecoder::try_new(
107    ///     || request.start_controlled(&codecs, storage),
108    ///     policy,
109    ///     pulse,
110    /// )?;
111    /// ```
112    ///
113    /// Cancellation is checked before and after construction. If cancellation
114    /// arrives during a successful factory call, the decoder is dropped before
115    /// return. Lengthy factories must poll the same pulse themselves; this check
116    /// cannot preempt synchronous work. `None` skips these per-call checks only.
117    pub fn try_new<E>(
118        factory: impl FnOnce() -> Result<D, E>,
119        policy: R,
120        pulse: Option<&dyn zencodec::Pulse>,
121    ) -> Result<Self, StepError<PolicyDecodeError<E>>> {
122        let pulse = pulse.unwrap_or(&zencodec::NoPulse);
123        pulse.check()?;
124        let decoder = Self::from_admitted_factory(factory, policy).map_err(StepError::Codec)?;
125        pulse.check()?;
126        Ok(decoder)
127    }
128
129    /// Construct after workspace admission, without per-call cancellation.
130    #[deprecated(
131        note = "Use try_new(factory, policy, pulse); pass None when no per-call control is needed"
132    )]
133    pub fn try_from_factory<E>(
134        factory: impl FnOnce() -> Result<D, E>,
135        policy: R,
136    ) -> Result<Self, PolicyDecodeError<E>> {
137        Self::from_admitted_factory(factory, policy)
138    }
139
140    fn from_admitted_factory<E>(
141        factory: impl FnOnce() -> Result<D, E>,
142        policy: R,
143    ) -> Result<Self, PolicyDecodeError<E>> {
144        if let Some(requirements) = policy.requirements() {
145            requirements.check_workspace(None).map_err(|error| {
146                PolicyDecodeError::Requirements(
147                    At::wrap(error).at_named("controlled decoder factory admission"),
148                )
149            })?;
150        }
151        let decoder = factory().map_err(PolicyDecodeError::Native)?;
152        Ok(Self::new(decoder, policy))
153    }
154}
155impl<D: ControlledIncrementalDecode, R: OperationPolicy> IncrementalDecode for PolicyDecoder<D, R> {
156    type Error = PolicyDecodeError<D::Error>;
157    fn step(
158        &mut self,
159        input: Input<'_>,
160        sink: &mut dyn DecodeRowSink,
161        pulse: Option<&dyn zencodec::Pulse>,
162    ) -> Result<Progress, StepError<Self::Error>> {
163        if self.failed {
164            return Err(StepError::Finished);
165        }
166        // Pessimistic poisoning covers panics in native code and the callback.
167        self.failed = true;
168        // Keep the native session on this stack while doing fallible work. An
169        // error or unwind drops its retained storage even if the caller keeps
170        // this adapter to inspect the report. Only a successful step restores it.
171        let mut decoder = self.decoder.take().ok_or(StepError::Finished)?;
172        // Deferring metadata discovery must not defer a resource deadline. This
173        // adapter has no qualified native workspace evidence. Check both entry
174        // policies before even polling the native coroutine; a discovery step
175        // may already allocate. The taken owner drops on refusal or unwind.
176        for requirements in [Some(self.preset), self.resolver.requirements()]
177            .into_iter()
178            .flatten()
179        {
180            requirements.check_workspace(None).map_err(|error| {
181                StepError::Codec(PolicyDecodeError::Requirements(
182                    At::wrap(error).at_named("controlled decode workspace admission"),
183                ))
184            })?;
185        }
186        let progress = match decoder
187            .step_controlled(input, sink, pulse)
188            .map_err(|e| e.map_codec(PolicyDecodeError::Native))?
189        {
190            ControlledProgress::Decode(progress) => progress,
191            ControlledProgress::Suspended { consumed, boundary } => {
192                let (action, record) =
193                    match resolve_boundary(boundary, &mut self.resolver, self.preset, pulse) {
194                        Ok(answer) => answer,
195                        Err(error) => {
196                            self.report.record(error.error().record());
197                            return Err(StepError::Codec(PolicyDecodeError::Policy(error)));
198                        }
199                    };
200                self.report.record(record);
201                decoder
202                    .resume(boundary.id(), action, pulse)
203                    .map_err(|e| e.map_codec(PolicyDecodeError::Native))?;
204                // The decision is already handled. Let the outer driver observe
205                // current metadata and schedule its next turn before more work.
206                Progress::new(consumed, Event::Metadata(MetadataUpdate::Updated))
207            }
208        };
209        self.decoder = Some(decoder);
210        self.failed = false;
211        Ok(progress)
212    }
213    fn requested(&self) -> &[InputRequest] {
214        if self.failed {
215            &[]
216        } else {
217            self.decoder.as_ref().map_or(&[], |d| d.requested())
218        }
219    }
220    fn info(&self) -> Option<&ImageInfo> {
221        self.decoder.as_ref().and_then(|d| d.info())
222    }
223    fn take_details(&mut self) -> Option<zencodec::decode::DecodeDetails> {
224        if self.failed {
225            return None;
226        }
227        self.decoder.as_mut().and_then(|d| d.take_details())
228    }
229    fn output_descriptor(&self) -> Option<PixelDescriptor> {
230        self.decoder.as_ref().and_then(|d| d.output_descriptor())
231    }
232    fn access_pattern(&self) -> AccessPattern {
233        self.decoder
234            .as_ref()
235            .map_or(AccessPattern::default(), |d| d.access_pattern())
236    }
237}