1use std::{
2 io::{Error as IoError, ErrorKind},
3 num::TryFromIntError,
4};
5
6use axum::{
7 Error as AxumError, Json,
8 http::{HeaderValue, StatusCode, header::WWW_AUTHENTICATE},
9 response::{IntoResponse, Response},
10};
11use serde_json::Error as JsonError;
12use shardline_cache::ReconstructionCacheError;
13use shardline_gc::GcError;
14use shardline_index::{
15 FileRecordInvariantError, LocalIndexStoreError, MemoryIndexStoreError, MemoryRecordStoreError,
16 PostgresMetadataStoreError, QuarantineCandidateError, RetentionHoldError, WebhookDeliveryError,
17};
18use shardline_oci_adapter::OciAdapterError;
19use shardline_protocol::{HashParseError, HttpRangeParseError, TokenCodecError};
20use shardline_protocol_adapters::ProtocolError;
21use shardline_provider_events::ProviderEventsError;
22use shardline_server_core::{
23 InvalidLifecycleMetadataError, InvalidReconstructionResponseError, InvalidSerializedShardError,
24 ParseStoredFileRecordError,
25};
26use shardline_storage::{LocalObjectStoreError, ObjectPrefixError, S3ObjectStoreError};
27use shardline_xet_adapter::XetAdapterError;
28use thiserror::Error;
29use tokio::task::JoinError;
30
31use super::body::ErrorBody;
32use super::{IndexError, ObjectStoreError};
33
34use crate::{
35 config::ServerConfigError, provider::ProviderServiceError, xet_adapter::XorbParseError,
36};
37
38#[derive(Debug, Error)]
54pub enum ServerError {
55 #[error("local storage operation failed")]
57 Io(#[from] IoError),
58 #[error("json operation failed")]
60 Json(#[from] JsonError),
61 #[error("request body stream failed")]
63 RequestBodyRead(#[source] AxumError),
64 #[error("request body exceeded the configured maximum accepted byte count")]
66 RequestBodyTooLarge,
67 #[error("request query exceeded the bounded metadata parser budget")]
69 RequestQueryTooLarge,
70 #[error("request body frame exceeded checked bounds")]
72 RequestBodyFrameOutOfBounds,
73 #[error("numeric conversion exceeded supported bounds")]
75 NumericConversion(#[from] TryFromIntError),
76 #[error("invalid content hash")]
78 HashParse(#[from] HashParseError),
79 #[error("object storage operation failed")]
81 ObjectStore(#[from] ObjectStoreError),
82 #[error("index adapter operation failed")]
84 Index(#[from] IndexError),
85 #[error("stored file metadata exceeded the bounded parser ceiling")]
87 StoredFileMetadataTooLarge {
88 observed_bytes: u64,
90 maximum_bytes: u64,
92 },
93 #[error("stored file metadata length did not match the validated length")]
95 StoredFileMetadataLengthMismatch,
96 #[error(
98 "file identifier must be relative and must not contain traversal or control characters"
99 )]
100 InvalidFileId,
101 #[error("content hash must be 64 hexadecimal characters")]
103 InvalidContentHash,
104 #[error("xorb transfer prefix must be default")]
106 InvalidXorbPrefix,
107 #[error("xorb body hash did not match the requested path hash")]
109 XorbHashMismatch,
110 #[error("xorb body was not a valid serialized xorb object")]
112 InvalidSerializedXorb,
113 #[error("shard body was not a valid serialized shard object")]
115 InvalidSerializedShard(#[from] InvalidSerializedShardError),
116 #[error("shard referenced a missing xorb")]
118 MissingReferencedXorb,
119 #[error("shard metadata exceeded bounded parser safety limits")]
121 TooManyShardTerms,
122 #[error("batch reconstruction requested too many file identifiers")]
124 TooManyBatchReconstructionFileIds,
125 #[error("content not found")]
127 NotFound,
128 #[error("arithmetic overflow")]
130 Overflow,
131 #[error("range header must use bytes=<start>-<end> syntax")]
133 InvalidRangeHeader,
134 #[error("requested range is not satisfiable")]
136 RangeNotSatisfiable,
137 #[error("authorization header is missing")]
139 MissingAuthorization,
140 #[error("authorization header must use bearer format")]
142 InvalidAuthorizationHeader,
143 #[error("bearer token was invalid")]
145 InvalidToken(TokenCodecError),
146 #[error("token signing key is misconfigured: {0}")]
148 SigningKeyError(String),
149 #[error("bearer token does not grant the required scope")]
151 InsufficientScope,
152 #[error("provider token issuance endpoint is not configured")]
154 ProviderTokensDisabled,
155 #[error("provider bootstrap key is missing")]
157 MissingProviderApiKey,
158 #[error("provider bootstrap key is invalid")]
160 InvalidProviderApiKey,
161 #[error("provider subject is missing")]
163 MissingProviderSubject,
164 #[error("provider token request was invalid")]
166 InvalidProviderTokenRequest,
167 #[error("provider webhook authentication is missing")]
169 MissingProviderWebhookAuthentication,
170 #[error("provider webhook authentication is invalid")]
172 InvalidProviderWebhookAuthentication,
173 #[error("provider webhook payload was invalid")]
175 InvalidProviderWebhookPayload,
176 #[error("provider is not configured")]
178 UnknownProvider,
179 #[error("provider denied requested repository access")]
181 ProviderDenied,
182 #[error("provider token issuance failed")]
184 Provider(#[from] ProviderServiceError),
185 #[error("reconstruction cache adapter operation failed")]
187 ReconstructionCache(#[from] ReconstructionCacheError),
188 #[error("server configuration was invalid")]
190 Config(#[from] ServerConfigError),
191 #[error("uploaded body hash did not match the expected sha256")]
193 ExpectedBodyHashMismatch,
194 #[error("digest must use sha256:<64 lowercase hex> format")]
196 InvalidDigest,
197 #[error("repository name was invalid")]
199 InvalidRepositoryName,
200 #[error("manifest reference was invalid")]
202 InvalidManifestReference,
203 #[error("requested representation was not acceptable")]
205 NotAcceptable,
206 #[error("authorization challenge required")]
208 UnauthorizedChallenge(String),
209 #[error("upload session identifier was invalid")]
211 InvalidUploadSession,
212 #[error("too many active oci upload sessions")]
214 TooManyUploadSessions,
215 #[error("too many active oci registry token requests")]
217 TooManyRegistryTokenRequests,
218 #[error("redis reconstruction cache requires a redis url")]
220 MissingReconstructionCacheRedisUrl,
221 #[error("transfer concurrency limiter is unavailable")]
223 TransferLimiterClosed,
224 #[error("transfer concurrency limiter timed out")]
226 TransferLimiterTimedOut,
227 #[error("blocking worker task failed")]
229 BlockingTask(#[source] JoinError),
230}
231
232impl ServerError {
233 pub(crate) const fn oci_error_code(&self) -> &'static str {
239 match self {
240 Self::NotFound => "MANIFEST_UNKNOWN",
241 Self::InvalidDigest => "DIGEST_INVALID",
242 Self::InvalidRepositoryName => "NAME_INVALID",
243 Self::InvalidManifestReference | Self::InvalidUploadSession => "MANIFEST_INVALID",
244 Self::RequestBodyTooLarge => "SIZE_INVALID",
245 Self::MissingAuthorization
246 | Self::InvalidAuthorizationHeader
247 | Self::InvalidToken(_) => "UNAUTHORIZED",
248 Self::InsufficientScope => "DENIED",
249 Self::NotAcceptable => "UNSUPPORTED",
250 Self::ExpectedBodyHashMismatch => "DIGEST_INVALID",
251 Self::TooManyUploadSessions | Self::TooManyRegistryTokenRequests => "TOO_MANY_REQUESTS",
252 Self::Io(_)
255 | Self::Json(_)
256 | Self::RequestBodyRead(_)
257 | Self::RequestQueryTooLarge
258 | Self::RequestBodyFrameOutOfBounds
259 | Self::NumericConversion(_)
260 | Self::HashParse(_)
261 | Self::ObjectStore(_)
262 | Self::Index(_)
263 | Self::StoredFileMetadataTooLarge { .. }
264 | Self::StoredFileMetadataLengthMismatch
265 | Self::InvalidFileId
266 | Self::InvalidContentHash
267 | Self::InvalidXorbPrefix
268 | Self::XorbHashMismatch
269 | Self::InvalidSerializedXorb
270 | Self::InvalidSerializedShard(_)
271 | Self::MissingReferencedXorb
272 | Self::TooManyShardTerms
273 | Self::TooManyBatchReconstructionFileIds
274 | Self::Overflow
275 | Self::InvalidRangeHeader
276 | Self::RangeNotSatisfiable
277 | Self::SigningKeyError(_)
278 | Self::ProviderTokensDisabled
279 | Self::MissingProviderApiKey
280 | Self::InvalidProviderApiKey
281 | Self::MissingProviderSubject
282 | Self::InvalidProviderTokenRequest
283 | Self::MissingProviderWebhookAuthentication
284 | Self::InvalidProviderWebhookAuthentication
285 | Self::InvalidProviderWebhookPayload
286 | Self::UnknownProvider
287 | Self::ProviderDenied
288 | Self::Provider(_)
289 | Self::ReconstructionCache(_)
290 | Self::Config(_)
291 | Self::UnauthorizedChallenge(_)
292 | Self::MissingReconstructionCacheRedisUrl
293 | Self::TransferLimiterClosed
294 | Self::TransferLimiterTimedOut
295 | Self::BlockingTask(_) => "INTERNAL",
296 }
297 }
298
299 pub(crate) const fn status_code(&self) -> StatusCode {
300 match self {
301 Self::InvalidFileId
302 | Self::InvalidContentHash
303 | Self::InvalidDigest
304 | Self::InvalidRepositoryName
305 | Self::InvalidManifestReference
306 | Self::InvalidUploadSession
307 | Self::InvalidXorbPrefix
308 | Self::HashParse(_) => StatusCode::BAD_REQUEST,
309 Self::NotAcceptable => StatusCode::NOT_ACCEPTABLE,
310 Self::UnauthorizedChallenge(_) => StatusCode::UNAUTHORIZED,
311 Self::InvalidRangeHeader => StatusCode::BAD_REQUEST,
312 Self::RangeNotSatisfiable => StatusCode::RANGE_NOT_SATISFIABLE,
313 Self::XorbHashMismatch
314 | Self::InvalidSerializedXorb
315 | Self::InvalidSerializedShard(_)
316 | Self::MissingReferencedXorb => StatusCode::BAD_REQUEST,
317 Self::TooManyShardTerms
318 | Self::TooManyBatchReconstructionFileIds
319 | Self::RequestBodyTooLarge => StatusCode::PAYLOAD_TOO_LARGE,
320 Self::RequestQueryTooLarge => StatusCode::URI_TOO_LONG,
321 Self::RequestBodyRead(_) | Self::RequestBodyFrameOutOfBounds => StatusCode::BAD_REQUEST,
322 Self::ExpectedBodyHashMismatch => StatusCode::BAD_REQUEST,
323 Self::NotFound | Self::UnknownProvider | Self::ProviderTokensDisabled => {
324 StatusCode::NOT_FOUND
325 }
326 Self::MissingAuthorization
327 | Self::InvalidAuthorizationHeader
328 | Self::InvalidToken(_)
329 | Self::MissingProviderApiKey
330 | Self::MissingProviderSubject
331 | Self::MissingProviderWebhookAuthentication => StatusCode::UNAUTHORIZED,
332 Self::InsufficientScope
333 | Self::InvalidProviderApiKey
334 | Self::InvalidProviderWebhookAuthentication
335 | Self::ProviderDenied => StatusCode::FORBIDDEN,
336 Self::InvalidProviderTokenRequest | Self::InvalidProviderWebhookPayload => {
337 StatusCode::BAD_REQUEST
338 }
339 Self::TooManyUploadSessions | Self::TooManyRegistryTokenRequests => {
340 StatusCode::TOO_MANY_REQUESTS
341 }
342 Self::TransferLimiterClosed | Self::TransferLimiterTimedOut => {
343 StatusCode::SERVICE_UNAVAILABLE
344 }
345 Self::Io(_)
346 | Self::Json(_)
347 | Self::NumericConversion(_)
348 | Self::ObjectStore(_)
349 | Self::Index(_)
350 | Self::StoredFileMetadataTooLarge { .. }
351 | Self::StoredFileMetadataLengthMismatch
352 | Self::Config(_)
353 | Self::Overflow
354 | Self::MissingReconstructionCacheRedisUrl
355 | Self::ReconstructionCache(_)
356 | Self::BlockingTask(_)
357 | Self::SigningKeyError(_)
358 | Self::Provider(_) => StatusCode::INTERNAL_SERVER_ERROR,
359 }
360 }
361}
362
363impl IntoResponse for ServerError {
364 fn into_response(self) -> Response {
365 let status = self.status_code();
366 let should_attach_default_challenge = matches!(
367 self,
368 Self::MissingAuthorization | Self::InvalidAuthorizationHeader | Self::InvalidToken(_)
369 );
370 let custom_challenge = if let Self::UnauthorizedChallenge(custom_header) = &self {
371 Some(custom_header.as_str())
372 } else {
373 None
374 };
375 let body = ErrorBody {
376 error: self.to_string(),
377 };
378 let mut response = (status, Json(body)).into_response();
379 if let Some(custom_header) = custom_challenge {
380 if let Ok(header_value) = HeaderValue::from_str(custom_header) {
381 response
382 .headers_mut()
383 .insert(WWW_AUTHENTICATE, header_value);
384 }
385 } else if should_attach_default_challenge {
386 response.headers_mut().insert(
387 WWW_AUTHENTICATE,
388 HeaderValue::from_static("Bearer realm=\"shardline\""),
389 );
390 }
391 response
392 }
393}
394
395impl From<HttpRangeParseError> for ServerError {
396 fn from(value: HttpRangeParseError) -> Self {
397 match value {
398 HttpRangeParseError::Unsatisfiable => Self::RangeNotSatisfiable,
399 HttpRangeParseError::MissingBytesUnit
400 | HttpRangeParseError::InvalidSyntax(_)
401 | HttpRangeParseError::InvalidNumber(_) => Self::InvalidRangeHeader,
402 }
403 }
404}
405
406impl From<XorbParseError> for ServerError {
407 fn from(value: XorbParseError) -> Self {
408 match value {
409 XorbParseError::HashMismatch => Self::XorbHashMismatch,
410 XorbParseError::InvalidFormat(_)
411 | XorbParseError::NumericConversion(_)
412 | XorbParseError::Io(_) => Self::InvalidSerializedXorb,
413 }
414 }
415}
416
417impl From<XetAdapterError> for ServerError {
418 fn from(value: XetAdapterError) -> Self {
419 match value {
420 XetAdapterError::Io(e) => Self::Io(e),
421 XetAdapterError::NumericConversion(e) => Self::NumericConversion(e),
422 XetAdapterError::HashParse(e) => Self::HashParse(e),
423 XetAdapterError::ObjectStore(e) => Self::from(e),
424 XetAdapterError::LocalObjectStore(e) => Self::ObjectStore(ObjectStoreError::Local(e)),
425 XetAdapterError::S3ObjectStore(e) => Self::ObjectStore(ObjectStoreError::S3(e)),
426 XetAdapterError::IndexStore(e) => Self::Index(IndexError::Local(e)),
427 XetAdapterError::MemoryIndexStore(e) => Self::Index(IndexError::MemoryIndex(e)),
428 XetAdapterError::MemoryRecordStore(e) => Self::Index(IndexError::MemoryRecord(e)),
429 XetAdapterError::PostgresMetadata(e) => Self::Index(IndexError::PostgresMetadata(e)),
430 XetAdapterError::FileRecordInvariant(e) => {
431 Self::Index(IndexError::FileRecordInvariant(e))
432 }
433 XetAdapterError::InvalidContentHash => Self::InvalidContentHash,
434 XetAdapterError::InvalidXorbPrefix => Self::InvalidXorbPrefix,
435 XetAdapterError::XorbHashMismatch => Self::XorbHashMismatch,
436 XetAdapterError::InvalidSerializedXorb => Self::InvalidSerializedXorb,
437 XetAdapterError::InvalidSerializedShard(e) => Self::InvalidSerializedShard(e),
438 XetAdapterError::MissingReferencedXorb => Self::MissingReferencedXorb,
439 XetAdapterError::TooManyShardTerms => Self::TooManyShardTerms,
440 XetAdapterError::NotFound => Self::NotFound,
441 XetAdapterError::Overflow => Self::Overflow,
442 XetAdapterError::RangeNotSatisfiable => Self::RangeNotSatisfiable,
443 }
444 }
445}
446
447impl From<ProviderEventsError> for ServerError {
448 fn from(value: ProviderEventsError) -> Self {
449 match value {
450 ProviderEventsError::Overflow => Self::Overflow,
451 ProviderEventsError::InvalidContentHash => Self::InvalidContentHash,
452 ProviderEventsError::InvalidProviderWebhookPayload => {
453 Self::InvalidProviderWebhookPayload
454 }
455 ProviderEventsError::ConflictingRenameTargetRecord => {
456 Self::Index(IndexError::ConflictingRenameTargetRecord)
457 }
458 ProviderEventsError::Json(e) => Self::Json(e),
459 ProviderEventsError::NumericConversion(e) => Self::NumericConversion(e),
460 ProviderEventsError::RetentionHold(e) => Self::Index(IndexError::RetentionHold(e)),
461 ProviderEventsError::XetAdapter(e) => Self::from(e),
462 ProviderEventsError::IndexStore(e) => Self::Index(IndexError::Local(e)),
463 ProviderEventsError::MemoryIndexStore(e) => Self::Index(IndexError::MemoryIndex(e)),
464 ProviderEventsError::MemoryRecordStore(e) => Self::Index(IndexError::MemoryRecord(e)),
465 ProviderEventsError::PostgresMetadata(e) => {
466 Self::Index(IndexError::PostgresMetadata(e))
467 }
468 ProviderEventsError::WebhookDelivery(e) => Self::Index(IndexError::WebhookDelivery(e)),
469 ProviderEventsError::ObjectStore(e) => Self::from(e),
470 ProviderEventsError::ParseStoredFileRecord(e) => {
471 Self::Io(IoError::new(ErrorKind::InvalidData, e.to_string()))
472 }
473 }
474 }
475}
476
477impl From<GcError> for ServerError {
478 fn from(err: GcError) -> Self {
479 match err {
480 GcError::Io(e) => Self::Io(e),
481 GcError::Json(e) => Self::Json(e),
482 GcError::NumericConversion(e) => Self::NumericConversion(e),
483 GcError::ObjectStore(e) => Self::from(e),
484 GcError::LocalObjectStore(e) => Self::ObjectStore(ObjectStoreError::Local(e)),
485 GcError::S3ObjectStore(e) => Self::ObjectStore(ObjectStoreError::S3(e)),
486 GcError::ObjectPrefix(e) => Self::ObjectStore(ObjectStoreError::Prefix(e)),
487 GcError::IndexStore(e) => Self::Index(IndexError::Local(e)),
488 GcError::MemoryIndexStore(e) => Self::Index(IndexError::MemoryIndex(e)),
489 GcError::MemoryRecordStore(e) => Self::Index(IndexError::MemoryRecord(e)),
490 GcError::PostgresMetadata(e) => Self::Index(IndexError::PostgresMetadata(e)),
491 GcError::RetentionHold(e) => Self::Index(IndexError::RetentionHold(e)),
492 GcError::QuarantineCandidate(e) => Self::Index(IndexError::QuarantineCandidate(e)),
493 GcError::WebhookDelivery(e) => Self::Index(IndexError::WebhookDelivery(e)),
494 GcError::FileRecordInvariant(e) => Self::Index(IndexError::FileRecordInvariant(e)),
495 GcError::InvalidLifecycleMetadata(e) => {
496 Self::Index(IndexError::InvalidLifecycleMetadata(e))
497 }
498 GcError::InvalidContentHash => Self::InvalidContentHash,
499 GcError::Overflow => Self::Overflow,
500 GcError::XetAdapter(e) => Self::from(e),
501 }
502 }
503}
504
505impl From<OciAdapterError> for ServerError {
506 fn from(value: OciAdapterError) -> Self {
507 match value {
508 OciAdapterError::Io(e) => Self::Io(e),
509 OciAdapterError::Json(e) => Self::Json(e),
510 OciAdapterError::NumericConversion(e) => Self::NumericConversion(e),
511 OciAdapterError::ObjectStore(e) => Self::from(e),
512 OciAdapterError::S3ObjectStore(e) => Self::ObjectStore(ObjectStoreError::S3(e)),
513 OciAdapterError::LocalObjectStore(e) => Self::ObjectStore(ObjectStoreError::Local(e)),
514 OciAdapterError::ObjectPrefix(e) => Self::ObjectStore(ObjectStoreError::Prefix(e)),
515 OciAdapterError::NotFound => Self::NotFound,
516 OciAdapterError::Overflow => Self::Overflow,
517 OciAdapterError::InvalidContentHash => Self::InvalidContentHash,
518 OciAdapterError::InvalidDigest => Self::InvalidDigest,
519 OciAdapterError::InvalidRepositoryName => Self::InvalidRepositoryName,
520 OciAdapterError::InvalidManifestReference => Self::InvalidManifestReference,
521 OciAdapterError::InvalidUploadSession => Self::InvalidUploadSession,
522 OciAdapterError::TooManyUploadSessions => Self::TooManyUploadSessions,
523 OciAdapterError::ExpectedBodyHashMismatch => Self::ExpectedBodyHashMismatch,
524 OciAdapterError::BlockingTask(e) => Self::BlockingTask(e),
525 }
526 }
527}
528
529impl From<ProtocolError> for ServerError {
530 fn from(error: ProtocolError) -> Self {
531 match error {
532 ProtocolError::InvalidContentHash => Self::InvalidContentHash,
533 }
534 }
535}
536
537impl From<LocalObjectStoreError> for ServerError {
538 fn from(e: LocalObjectStoreError) -> Self {
539 Self::ObjectStore(ObjectStoreError::Local(e))
540 }
541}
542
543impl From<S3ObjectStoreError> for ServerError {
544 fn from(e: S3ObjectStoreError) -> Self {
545 Self::ObjectStore(ObjectStoreError::S3(e))
546 }
547}
548
549impl From<ObjectPrefixError> for ServerError {
550 fn from(e: ObjectPrefixError) -> Self {
551 Self::ObjectStore(ObjectStoreError::Prefix(e))
552 }
553}
554
555impl From<LocalIndexStoreError> for ServerError {
556 fn from(e: LocalIndexStoreError) -> Self {
557 Self::Index(IndexError::Local(e))
558 }
559}
560
561impl From<MemoryIndexStoreError> for ServerError {
562 fn from(e: MemoryIndexStoreError) -> Self {
563 Self::Index(IndexError::MemoryIndex(e))
564 }
565}
566
567impl From<MemoryRecordStoreError> for ServerError {
568 fn from(e: MemoryRecordStoreError) -> Self {
569 Self::Index(IndexError::MemoryRecord(e))
570 }
571}
572
573impl From<PostgresMetadataStoreError> for ServerError {
574 fn from(e: PostgresMetadataStoreError) -> Self {
575 Self::Index(IndexError::PostgresMetadata(e))
576 }
577}
578
579impl From<RetentionHoldError> for ServerError {
580 fn from(e: RetentionHoldError) -> Self {
581 Self::Index(IndexError::RetentionHold(e))
582 }
583}
584
585impl From<QuarantineCandidateError> for ServerError {
586 fn from(e: QuarantineCandidateError) -> Self {
587 Self::Index(IndexError::QuarantineCandidate(e))
588 }
589}
590
591impl From<WebhookDeliveryError> for ServerError {
592 fn from(e: WebhookDeliveryError) -> Self {
593 Self::Index(IndexError::WebhookDelivery(e))
594 }
595}
596
597impl From<FileRecordInvariantError> for ServerError {
598 fn from(e: FileRecordInvariantError) -> Self {
599 Self::Index(IndexError::FileRecordInvariant(e))
600 }
601}
602
603impl From<InvalidLifecycleMetadataError> for ServerError {
604 fn from(e: InvalidLifecycleMetadataError) -> Self {
605 Self::Index(IndexError::InvalidLifecycleMetadata(e))
606 }
607}
608
609impl From<InvalidReconstructionResponseError> for ServerError {
610 fn from(e: InvalidReconstructionResponseError) -> Self {
611 Self::Index(IndexError::InvalidReconstructionResponse(e))
612 }
613}
614
615impl From<ParseStoredFileRecordError> for ServerError {
616 fn from(e: ParseStoredFileRecordError) -> Self {
617 match e {
618 ParseStoredFileRecordError::StoredFileMetadataTooLarge {
619 observed_bytes,
620 maximum_bytes,
621 } => Self::StoredFileMetadataTooLarge {
622 observed_bytes,
623 maximum_bytes,
624 },
625 ParseStoredFileRecordError::Json(e) => Self::Json(e),
626 }
627 }
628}