alpha2_model/error.rs
1use crate::{ClipId, CompositionId, EffectResourceId, NodeId, TrackId};
2use alpha2_media::SourceId;
3use std::fmt;
4
5/// What holds a shape a [`ModelError::InvalidShape`] names.
6#[derive(Clone, Debug, Eq, PartialEq)]
7pub enum ShapeOwner {
8 /// A generated source's shapes generator.
9 Source(SourceId),
10 /// A clip's masks.
11 Clip(ClipId),
12 /// A composition node's shapes generator or masks.
13 Node {
14 /// The composition.
15 composition: CompositionId,
16 /// The node.
17 node: NodeId,
18 },
19}
20
21impl fmt::Display for ShapeOwner {
22 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
23 match self {
24 Self::Source(source) => write!(formatter, "source {source}"),
25 Self::Clip(clip) => write!(formatter, "clip {clip}"),
26 Self::Node { composition, node } => {
27 write!(formatter, "composition {composition} node {node}")
28 }
29 }
30 }
31}
32
33/// A document invariant violation.
34#[derive(Clone, Debug, Eq, PartialEq)]
35#[non_exhaustive]
36pub enum ModelError {
37 /// This build cannot interpret the document version.
38 UnsupportedSchemaVersion {
39 /// Version found in the decoded document.
40 found: u32,
41 },
42 /// At least one ID was empty.
43 EmptyIdentifier,
44 /// Two sources share one ID.
45 DuplicateSourceId(SourceId),
46 /// Two effect resources share one ID.
47 DuplicateEffectResourceId(EffectResourceId),
48 /// Two tracks share one ID.
49 DuplicateTrackId(TrackId),
50 /// Two clips share one ID.
51 DuplicateClipId(ClipId),
52 /// A source locator was empty.
53 EmptyLocator {
54 /// Source whose locator was empty.
55 source: SourceId,
56 },
57 /// A still image source says it has audio, which no image holds.
58 AudioOnImage {
59 /// The image source.
60 source: SourceId,
61 },
62 /// A source's provider does not fit it: another kind of source than the
63 /// provider supplies, audio, or incoherent settings; or a generated
64 /// source has one.
65 InvalidProvider {
66 /// The source.
67 source: SourceId,
68 },
69 /// A source's fingerprint is malformed, or a generated source has one.
70 InvalidFingerprint {
71 /// The source.
72 source: SourceId,
73 },
74 /// A source declares a color whose dynamic range its transfer
75 /// contradicts: HDR is exactly a PQ or HLG transfer.
76 ContradictoryColor {
77 /// The source.
78 source: SourceId,
79 },
80 /// A generated source has no valid generator, or has a locator, color
81 /// metadata, or audio; or another source has a generator.
82 InvalidGenerator {
83 /// The source.
84 source: SourceId,
85 },
86 /// A shapes generator's content tree, or a mask's outline, breaks a
87 /// limit, has a value out of range, or has keyframes that cannot
88 /// interpolate.
89 InvalidShape {
90 /// What holds the shape.
91 owner: ShapeOwner,
92 /// Where in it the problem lies, like `items[2].items[0].path` or
93 /// `masks[1].shape.size`.
94 at: String,
95 /// What is wrong there.
96 problem: crate::ShapeProblem,
97 },
98 /// An effect resource locator was empty.
99 EmptyEffectResourceLocator {
100 /// Resource whose locator was empty.
101 resource: EffectResourceId,
102 },
103 /// A clip effect references an absent effect resource.
104 UnknownEffectResource {
105 /// Clip containing the reference.
106 clip: ClipId,
107 /// Missing resource identity.
108 resource: EffectResourceId,
109 },
110 /// A clip has an empty source or timeline range.
111 EmptyClipRange {
112 /// Clip with an empty range.
113 clip: ClipId,
114 },
115 /// A clip starts before the project or source timeline origin.
116 NegativeClipRange {
117 /// Clip with a negative range start.
118 clip: ClipId,
119 },
120 /// Two clips overlap without one transition covering their intersection.
121 OverlappingClips {
122 /// Track containing the overlap.
123 track: TrackId,
124 /// First overlapping clip.
125 first: ClipId,
126 /// Second overlapping clip.
127 second: ClipId,
128 },
129 /// A clip refers to an absent source.
130 UnknownClipSource {
131 /// Clip containing the absent source reference.
132 clip: ClipId,
133 /// Referenced source that was not in the project.
134 source: SourceId,
135 },
136 /// A source cannot be placed on the selected track kind.
137 IncompatibleClipSource {
138 /// Clip containing the incompatible reference.
139 clip: ClipId,
140 /// Track that cannot accept the source kind.
141 track: TrackId,
142 /// Source with the incompatible kind.
143 source: SourceId,
144 },
145 /// A visual clip has non-finite values, invalid opacity, or unordered keyframes.
146 InvalidVisualProperties {
147 /// Clip containing the invalid presentation data.
148 clip: ClipId,
149 },
150 /// A clip has an invalid gain, pan, fade, or audio effect parameter.
151 InvalidAudioProperties {
152 /// Clip containing the invalid audio data.
153 clip: ClipId,
154 },
155 /// A track bus has an invalid gain, pan, effect, or ducking parameter.
156 InvalidTrackAudio {
157 /// Track containing the invalid bus data.
158 track: TrackId,
159 },
160 /// The project master bus has an invalid gain, effect, or limiter parameter.
161 InvalidMasterAudio,
162 /// A ducking sidechain names a missing track, itself, or forms a cycle.
163 InvalidDuckingSidechain {
164 /// Track whose ducking reference is unusable.
165 track: TrackId,
166 /// Referenced sidechain track.
167 sidechain: TrackId,
168 },
169 /// A clip names an absent composition.
170 UnknownComposition {
171 /// The clip.
172 clip: ClipId,
173 /// The composition it names.
174 composition: CompositionId,
175 },
176 /// A clip's composition has a non-positive scale, reaches a composition
177 /// time past [`alpha2_time::MAX_SERIALIZED_TICKS`] before the clip ends,
178 /// or is on an audio clip.
179 InvalidClipComposition {
180 /// The clip.
181 clip: ClipId,
182 },
183 /// Two compositions share one ID.
184 DuplicateCompositionId(CompositionId),
185 /// A node ID has a `/`, which joins the IDs of the group and media nodes
186 /// a drawn node was inlined through.
187 InvalidNodeId {
188 /// The composition.
189 composition: CompositionId,
190 /// The node ID.
191 node: NodeId,
192 },
193 /// Two nodes of one composition share an ID.
194 DuplicateNodeId {
195 /// The composition.
196 composition: CompositionId,
197 /// The repeated node ID.
198 node: NodeId,
199 },
200 /// A composition has more nodes than the renderer will schedule.
201 TooManyCompositionNodes {
202 /// The composition.
203 composition: CompositionId,
204 /// Number supplied by the document.
205 found: usize,
206 /// Largest supported count.
207 maximum: usize,
208 },
209 /// A node has an invalid setting: an effect identity, version,
210 /// parameter, or value; a transform, progress, opacity, or mask out of
211 /// range; too many merge layers or masks; or a bypass on a node without
212 /// inputs.
213 InvalidNode {
214 /// The composition.
215 composition: CompositionId,
216 /// The node.
217 node: NodeId,
218 },
219 /// A node input names a node its composition does not have.
220 UnknownNodeInput {
221 /// The composition.
222 composition: CompositionId,
223 /// The node whose input it is.
224 node: NodeId,
225 /// The input: `input`, `from`, `to`, `matte`, `layers[i]`, or
226 /// `inputs[i]`.
227 input: String,
228 /// The node named.
229 target: NodeId,
230 },
231 /// A node depends on its own output.
232 CompositionCycle {
233 /// The composition.
234 composition: CompositionId,
235 /// A node on the cycle.
236 node: NodeId,
237 },
238 /// A composition's output names a node it does not have.
239 UnknownCompositionOutput {
240 /// The composition.
241 composition: CompositionId,
242 /// The node named.
243 node: NodeId,
244 },
245 /// A media node names an absent source or one that is not visual.
246 InvalidNodeSource {
247 /// The composition.
248 composition: CompositionId,
249 /// The node.
250 node: NodeId,
251 /// The source named.
252 source: SourceId,
253 },
254 /// A composition's group inputs are unnamed, repeated, or too many, or
255 /// a control names no effect setting of its nodes or repeats one.
256 InvalidCompositionPorts {
257 /// The composition.
258 composition: CompositionId,
259 },
260 /// A group node names an absent composition.
261 UnknownGroupComposition {
262 /// The composition holding the group node.
263 composition: CompositionId,
264 /// The group node.
265 node: NodeId,
266 /// The composition it names.
267 target: CompositionId,
268 },
269 /// A composition uses itself through group nodes or composition
270 /// sources.
271 CompositionUsesItself {
272 /// A composition on the loop.
273 composition: CompositionId,
274 },
275 /// A composition draws compositions, through group nodes or
276 /// composition sources, nested more than
277 /// [`crate::MAX_COMPOSITION_DEPTH`] deep.
278 CompositionNestedTooDeep {
279 /// The first such composition, in document order.
280 composition: CompositionId,
281 /// Deepest supported nesting.
282 maximum: usize,
283 },
284 /// The compositions draw more nodes than
285 /// [`crate::MAX_INLINED_NODES`], each group node and composition-source
286 /// media node counted as the nodes of the composition it draws.
287 TooManyInlinedNodes {
288 /// Largest supported count.
289 maximum: usize,
290 },
291 /// The clips on the timeline at one instant draw more than
292 /// [`crate::MAX_FRAME_INLINED_NODES`] composition nodes together.
293 FrameDrawsTooManyNodes {
294 /// The first clip, in document order, starting at the first such
295 /// instant.
296 clip: ClipId,
297 /// The nodes the clips at that instant draw.
298 found: u64,
299 /// Largest supported count.
300 maximum: usize,
301 },
302 /// A composition source names an absent composition, or has what only a
303 /// host-supplied source has.
304 InvalidCompositionSource {
305 /// The source.
306 source: SourceId,
307 },
308 /// A node's effect references an absent effect resource.
309 UnknownNodeResource {
310 /// The composition.
311 composition: CompositionId,
312 /// The node.
313 node: NodeId,
314 /// The resource named.
315 resource: EffectResourceId,
316 },
317 /// A clip has more than [`crate::MAX_CLIP_MASKS`] masks, a mask with a
318 /// negative or non-finite value, or masks on an audio track.
319 InvalidClipMask {
320 /// Clip carrying the invalid mask.
321 clip: ClipId,
322 },
323 /// A caption track has no captions, or has clips or transitions, or its
324 /// cues or style are invalid; or another track has captions.
325 InvalidCaptions {
326 /// The track.
327 track: TrackId,
328 },
329 /// A clip freezes on an instant outside its source range.
330 InvalidTimeMap {
331 /// The clip.
332 clip: ClipId,
333 },
334 /// A clip's matte names no visual clip, itself, a clip with a matte of its
335 /// own, or a clip in a transition; or an audio clip has a matte.
336 InvalidMatte {
337 /// Clip whose matte is invalid.
338 clip: ClipId,
339 },
340 /// A transition is not contained in two distinct visual clips on its track.
341 InvalidClipTransition {
342 /// Track containing the invalid transition.
343 track: TrackId,
344 },
345 /// Transitions active at one instant lead from clip to clip in a circle,
346 /// which leaves no clip to show.
347 TransitionCycle {
348 /// Track containing the transitions.
349 track: TrackId,
350 /// An instant they are all active.
351 at: alpha2_time::MediaTime,
352 },
353 /// Two clips have more than one transition between them, in either
354 /// direction: each overlap shows exactly one.
355 DuplicateTransition {
356 /// Track containing the transitions.
357 track: TrackId,
358 /// The transition's first clip.
359 first: ClipId,
360 /// The transition's second clip.
361 second: ClipId,
362 },
363 /// The project contains too many compositions.
364 TooManyCompositions {
365 /// Number supplied by the document.
366 found: usize,
367 /// Largest supported count.
368 maximum: usize,
369 },
370 /// The source table exceeds the bounded project resource budget.
371 TooManySources {
372 /// Number supplied by the document.
373 found: usize,
374 /// Largest supported count.
375 maximum: usize,
376 },
377 /// The timeline contains too many tracks.
378 TooManyTracks {
379 /// Number supplied by the document.
380 found: usize,
381 /// Largest supported count.
382 maximum: usize,
383 },
384 /// The timeline contains too many clips in total.
385 TooManyClips {
386 /// Number supplied by the document.
387 found: usize,
388 /// Largest supported count.
389 maximum: usize,
390 },
391 /// More of the project's audio effects hold state at once than
392 /// [`crate::MAX_AUDIO_EFFECTS`] (see [`crate::Project::audio_effect_load`]).
393 TooManyAudioEffects {
394 /// The most effects holding state at once.
395 found: usize,
396 /// Largest supported count.
397 maximum: usize,
398 /// The earliest timeline position, in ticks, at which `found` hold
399 /// state.
400 at_ticks: i64,
401 },
402 /// The project's audio effects hold more than
403 /// [`crate::MAX_AUDIO_DELAY_MS`] of delay at once (see
404 /// [`crate::Project::audio_effect_load`]).
405 TooMuchAudioDelay {
406 /// The most milliseconds held at once, rounded up.
407 found_ms: u64,
408 /// Largest supported total.
409 maximum_ms: u32,
410 /// The earliest timeline position, in ticks, at which `found_ms` is
411 /// held.
412 at_ticks: i64,
413 },
414 /// One track contains too many clips.
415 TooManyClipsOnTrack {
416 /// Track containing the excessive clip list.
417 track: TrackId,
418 /// Number supplied by the document.
419 found: usize,
420 /// Largest supported count.
421 maximum: usize,
422 },
423 /// A caption track holds more than [`crate::MAX_CLIPS_PER_TRACK`] cues.
424 TooManyCaptionCues {
425 /// The caption track.
426 track: TrackId,
427 /// Number supplied by the document.
428 found: usize,
429 /// Largest supported count.
430 maximum: usize,
431 },
432 /// One track contains too many transitions.
433 TooManyTransitions {
434 /// Track containing the excessive transition list.
435 track: TrackId,
436 /// Number supplied by the document.
437 found: usize,
438 /// Largest supported count.
439 maximum: usize,
440 },
441 /// Width or height was zero.
442 ZeroCanvasDimension,
443 /// A canvas edge exceeds the portable GPU-independent limit.
444 CanvasDimensionTooLarge {
445 /// Supplied width.
446 width: u32,
447 /// Supplied height.
448 height: u32,
449 /// Largest supported edge.
450 maximum: u32,
451 },
452 /// The canvas area exceeds the bounded pixel budget.
453 CanvasPixelBudgetExceeded {
454 /// Supplied pixel area.
455 pixels: u64,
456 /// Largest supported pixel area.
457 maximum: u64,
458 },
459 /// A background component is non-finite or outside `0..=1`.
460 InvalidBackground,
461 /// The color pipeline's HDR peak is outside
462 /// [`crate::HDR_PEAK_NITS_RANGE`].
463 InvalidColorPipeline,
464 /// The project sample rate is outside the supported DSP interval.
465 UnsupportedSampleRate {
466 /// Supplied rate.
467 found: u32,
468 /// Smallest supported rate.
469 minimum: u32,
470 /// Largest supported rate.
471 maximum: u32,
472 },
473}
474
475impl fmt::Display for ModelError {
476 #[allow(clippy::too_many_lines)]
477 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
478 match self {
479 Self::UnsupportedSchemaVersion { found } => {
480 write!(formatter, "unsupported schema version {found}")
481 }
482 Self::EmptyIdentifier => formatter.write_str("document contains an empty identifier"),
483 Self::DuplicateSourceId(id) => write!(formatter, "duplicate source ID {id}"),
484 Self::DuplicateEffectResourceId(id) => {
485 write!(formatter, "duplicate effect resource ID {id}")
486 }
487 Self::DuplicateTrackId(id) => write!(formatter, "duplicate track ID {id}"),
488 Self::DuplicateClipId(id) => write!(formatter, "duplicate clip ID {id}"),
489 Self::EmptyLocator { source } => {
490 write!(formatter, "source {source} has an empty locator")
491 }
492 Self::AudioOnImage { source } => {
493 write!(
494 formatter,
495 "source {source} is a still image and has no audio"
496 )
497 }
498 Self::InvalidGenerator { source } => {
499 write!(formatter, "source {source} has an invalid generator")
500 }
501 Self::InvalidShape { owner, at, problem } => {
502 write!(formatter, "{owner} has an invalid shape: {at} {problem}")
503 }
504 Self::ContradictoryColor { source } => write!(
505 formatter,
506 "source {source} declares a dynamic range its transfer contradicts"
507 ),
508 Self::InvalidFingerprint { source } => {
509 write!(formatter, "source {source} has an invalid fingerprint")
510 }
511 Self::InvalidProvider { source } => {
512 write!(formatter, "source {source} has an invalid provider")
513 }
514 Self::EmptyEffectResourceLocator { resource } => {
515 write!(formatter, "effect resource {resource} has an empty locator")
516 }
517 Self::UnknownEffectResource { clip, resource } => {
518 write!(
519 formatter,
520 "clip {clip} references unknown effect resource {resource}"
521 )
522 }
523 Self::EmptyClipRange { clip } => write!(formatter, "clip {clip} has an empty range"),
524 Self::NegativeClipRange { clip } => {
525 write!(formatter, "clip {clip} has a range before timeline origin")
526 }
527 Self::OverlappingClips {
528 track,
529 first,
530 second,
531 } => write!(
532 formatter,
533 "clips {first} and {second} overlap without a covering transition on track {track}"
534 ),
535 Self::UnknownClipSource { clip, source } => {
536 write!(formatter, "clip {clip} references unknown source {source}")
537 }
538 Self::IncompatibleClipSource {
539 clip,
540 track,
541 source,
542 } => {
543 write!(
544 formatter,
545 "clip {clip} cannot use source {source} on track {track}"
546 )
547 }
548 Self::InvalidVisualProperties { clip } => {
549 write!(formatter, "clip {clip} has invalid visual properties")
550 }
551 Self::InvalidAudioProperties { clip } => {
552 write!(formatter, "clip {clip} has invalid audio properties")
553 }
554 Self::InvalidTrackAudio { track } => {
555 write!(formatter, "track {track} has invalid audio bus settings")
556 }
557 Self::InvalidMasterAudio => {
558 write!(formatter, "the project master bus is invalid")
559 }
560 Self::InvalidDuckingSidechain { track, sidechain } => write!(
561 formatter,
562 "track {track} cannot duck from sidechain track {sidechain}"
563 ),
564 Self::UnknownComposition { clip, composition } => {
565 write!(
566 formatter,
567 "clip {clip} references unknown composition {composition}"
568 )
569 }
570 Self::InvalidClipComposition { clip } => {
571 write!(
572 formatter,
573 "clip {clip} has an invalid composition reference"
574 )
575 }
576 Self::DuplicateCompositionId(id) => write!(formatter, "duplicate composition ID {id}"),
577 Self::InvalidNodeId { composition, node } => {
578 write!(
579 formatter,
580 "composition {composition} has node ID {node}, which has a `/`"
581 )
582 }
583 Self::DuplicateNodeId { composition, node } => {
584 write!(
585 formatter,
586 "composition {composition} has duplicate node ID {node}"
587 )
588 }
589 Self::TooManyCompositionNodes {
590 composition,
591 found,
592 maximum,
593 } => write!(
594 formatter,
595 "composition {composition} has {found} nodes; the maximum is {maximum}"
596 ),
597 Self::InvalidNode { composition, node } => {
598 write!(
599 formatter,
600 "node {node} of composition {composition} has an invalid setting"
601 )
602 }
603 Self::UnknownNodeInput {
604 composition,
605 node,
606 input,
607 target,
608 } => write!(
609 formatter,
610 "input {input} of node {node} in composition {composition} names unknown node {target}"
611 ),
612 Self::CompositionCycle { composition, node } => write!(
613 formatter,
614 "node {node} of composition {composition} depends on its own output"
615 ),
616 Self::UnknownCompositionOutput { composition, node } => write!(
617 formatter,
618 "composition {composition} outputs unknown node {node}"
619 ),
620 Self::InvalidNodeSource {
621 composition,
622 node,
623 source,
624 } => write!(
625 formatter,
626 "node {node} of composition {composition} cannot show source {source}"
627 ),
628 Self::InvalidCompositionPorts { composition } => write!(
629 formatter,
630 "composition {composition} has invalid group inputs or controls"
631 ),
632 Self::UnknownGroupComposition {
633 composition,
634 node,
635 target,
636 } => write!(
637 formatter,
638 "group node {node} of composition {composition} names unknown composition {target}"
639 ),
640 Self::CompositionUsesItself { composition } => {
641 write!(formatter, "composition {composition} uses itself")
642 }
643 Self::CompositionNestedTooDeep {
644 composition,
645 maximum,
646 } => write!(
647 formatter,
648 "composition {composition} nests compositions more than {maximum} deep"
649 ),
650 Self::TooManyInlinedNodes { maximum } => write!(
651 formatter,
652 "compositions draw more than {maximum} nodes, counting each group and composition source as the nodes it draws"
653 ),
654 Self::FrameDrawsTooManyNodes {
655 clip,
656 found,
657 maximum,
658 } => write!(
659 formatter,
660 "the clips on the timeline where clip {clip} starts draw {found} composition nodes; a frame draws at most {maximum}"
661 ),
662 Self::InvalidCompositionSource { source } => {
663 write!(
664 formatter,
665 "source {source} is not a valid composition source"
666 )
667 }
668 Self::UnknownNodeResource {
669 composition,
670 node,
671 resource,
672 } => write!(
673 formatter,
674 "node {node} of composition {composition} references unknown effect resource {resource}"
675 ),
676 Self::InvalidClipMask { clip } => write!(formatter, "clip {clip} has an invalid mask"),
677 Self::InvalidMatte { clip } => write!(formatter, "clip {clip} has an invalid matte"),
678 Self::InvalidCaptions { track } => {
679 write!(formatter, "track {track} has invalid captions")
680 }
681 Self::InvalidTimeMap { clip } => {
682 write!(formatter, "clip {clip} freezes outside its source range")
683 }
684 Self::InvalidClipTransition { track } => {
685 write!(formatter, "track {track} has an invalid clip transition")
686 }
687 Self::TransitionCycle { track, at } => write!(
688 formatter,
689 "transitions on track {track} active at {} ticks lead from clip to clip in a circle",
690 at.ticks()
691 ),
692 Self::DuplicateTransition {
693 track,
694 first,
695 second,
696 } => write!(
697 formatter,
698 "clips {first} and {second} have more than one transition on track {track}"
699 ),
700 Self::TooManyCompositions { found, maximum } => write!(
701 formatter,
702 "project has {found} compositions; the maximum is {maximum}"
703 ),
704 Self::TooManySources { found, maximum } => {
705 write!(
706 formatter,
707 "project has {found} sources; the maximum is {maximum}"
708 )
709 }
710 Self::TooManyTracks { found, maximum } => {
711 write!(
712 formatter,
713 "project has {found} tracks; the maximum is {maximum}"
714 )
715 }
716 Self::TooManyClips { found, maximum } => {
717 write!(
718 formatter,
719 "project has {found} clips; the maximum is {maximum}"
720 )
721 }
722 Self::TooManyAudioEffects {
723 found,
724 maximum,
725 at_ticks,
726 } => write!(
727 formatter,
728 "project has {found} audio effects holding state at tick {at_ticks}; the maximum at once is {maximum}"
729 ),
730 Self::TooMuchAudioDelay {
731 found_ms,
732 maximum_ms,
733 at_ticks,
734 } => write!(
735 formatter,
736 "project audio effects hold {found_ms} ms of delay at tick {at_ticks}; the maximum at once is {maximum_ms} ms"
737 ),
738 Self::TooManyClipsOnTrack {
739 track,
740 found,
741 maximum,
742 } => write!(
743 formatter,
744 "track {track} has {found} clips; the maximum is {maximum}"
745 ),
746 Self::TooManyCaptionCues {
747 track,
748 found,
749 maximum,
750 } => write!(
751 formatter,
752 "track {track} has {found} caption cues; the maximum is {maximum}"
753 ),
754 Self::TooManyTransitions {
755 track,
756 found,
757 maximum,
758 } => write!(
759 formatter,
760 "track {track} has {found} transitions; the maximum is {maximum}"
761 ),
762 Self::ZeroCanvasDimension => formatter.write_str("canvas dimensions must be positive"),
763 Self::CanvasDimensionTooLarge {
764 width,
765 height,
766 maximum,
767 } => write!(
768 formatter,
769 "canvas {width}x{height} exceeds the maximum edge {maximum}"
770 ),
771 Self::CanvasPixelBudgetExceeded { pixels, maximum } => write!(
772 formatter,
773 "canvas has {pixels} pixels; the maximum is {maximum}"
774 ),
775 Self::InvalidBackground => {
776 formatter.write_str("background color components must be finite and within 0..=1")
777 }
778 Self::InvalidColorPipeline => {
779 formatter.write_str("the HDR peak must be within 100..=10000 nits")
780 }
781 Self::UnsupportedSampleRate {
782 found,
783 minimum,
784 maximum,
785 } => write!(
786 formatter,
787 "sample rate {found} is outside {minimum}..={maximum}"
788 ),
789 }
790 }
791}
792
793impl std::error::Error for ModelError {}
794
795/// Failure while decoding a serialized project document.
796#[derive(Debug)]
797#[non_exhaustive]
798pub enum ProjectLoadError {
799 /// The JSON input exceeded the parser byte budget.
800 TooLarge {
801 /// Supplied UTF-8 byte length.
802 bytes: usize,
803 /// Largest accepted byte length.
804 maximum: usize,
805 },
806 /// JSON syntax or shape was invalid.
807 Json(serde_json::Error),
808 /// The decoded document violated a model invariant.
809 InvalidProject(ModelError),
810}
811
812impl fmt::Display for ProjectLoadError {
813 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
814 match self {
815 Self::TooLarge { bytes, maximum } => {
816 write!(
817 formatter,
818 "project JSON is {bytes} bytes; the maximum is {maximum}"
819 )
820 }
821 Self::Json(error) => write!(formatter, "invalid project JSON: {error}"),
822 Self::InvalidProject(error) => write!(formatter, "invalid project: {error}"),
823 }
824 }
825}
826
827impl std::error::Error for ProjectLoadError {
828 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
829 match self {
830 Self::TooLarge { .. } => None,
831 Self::Json(error) => Some(error),
832 Self::InvalidProject(error) => Some(error),
833 }
834 }
835}