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