Skip to main content

shardline_server/error/
server.rs

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/// Server runtime failure.
39///
40/// This is the unified error type for the Shardline server layer. It maps
41/// domain-specific errors from storage, indexing, protocol, auth, and OCI
42/// subsystems into a single enum with HTTP status code mapping.
43///
44/// # Design
45///
46/// Each variant maps to exactly one HTTP status code via [`ServerError::status_code`].
47/// Subsystem errors (e.g., [`LocalIndexStoreError`], [`S3ObjectStoreError`]) are
48/// converted via `From` implementations for ergonomic `?` usage.
49///
50/// When adding new error sources, prefer adding a new variant with a `#[from]`
51/// attribute over manual `From` implementations unless the source error maps
52/// to multiple variants.
53#[derive(Debug, Error)]
54pub enum ServerError {
55    /// Local storage IO failed.
56    #[error("local storage operation failed")]
57    Io(#[from] IoError),
58    /// JSON serialization or deserialization failed.
59    #[error("json operation failed")]
60    Json(#[from] JsonError),
61    /// Request body streaming failed.
62    #[error("request body stream failed")]
63    RequestBodyRead(#[source] AxumError),
64    /// Request body exceeded the configured maximum accepted byte count.
65    #[error("request body exceeded the configured maximum accepted byte count")]
66    RequestBodyTooLarge,
67    /// Request query exceeded the bounded metadata parser budget.
68    #[error("request query exceeded the bounded metadata parser budget")]
69    RequestQueryTooLarge,
70    /// Request body frame slicing exceeded checked bounds.
71    #[error("request body frame exceeded checked bounds")]
72    RequestBodyFrameOutOfBounds,
73    /// Numeric conversion exceeded supported bounds.
74    #[error("numeric conversion exceeded supported bounds")]
75    NumericConversion(#[from] TryFromIntError),
76    /// Hash parsing failed.
77    #[error("invalid content hash")]
78    HashParse(#[from] HashParseError),
79    /// Object-storage subsystem failure.
80    #[error("object storage operation failed")]
81    ObjectStore(#[from] ObjectStoreError),
82    /// Metadata index subsystem failure.
83    #[error("index adapter operation failed")]
84    Index(#[from] IndexError),
85    /// Stored file metadata exceeded the bounded parser ceiling.
86    #[error("stored file metadata exceeded the bounded parser ceiling")]
87    StoredFileMetadataTooLarge {
88        /// Observed file length in bytes.
89        observed_bytes: u64,
90        /// Maximum accepted file length in bytes.
91        maximum_bytes: u64,
92    },
93    /// Stored file metadata changed after bounded validation.
94    #[error("stored file metadata length did not match the validated length")]
95    StoredFileMetadataLengthMismatch,
96    /// A file identifier was unsafe.
97    #[error(
98        "file identifier must be relative and must not contain traversal or control characters"
99    )]
100    InvalidFileId,
101    /// A content hash was malformed.
102    #[error("content hash must be 64 hexadecimal characters")]
103    InvalidContentHash,
104    /// The xorb transfer path prefix was unsupported.
105    #[error("xorb transfer prefix must be default")]
106    InvalidXorbPrefix,
107    /// The uploaded xorb bytes did not match the requested hash.
108    #[error("xorb body hash did not match the requested path hash")]
109    XorbHashMismatch,
110    /// The uploaded xorb bytes were not a valid serialized xorb object.
111    #[error("xorb body was not a valid serialized xorb object")]
112    InvalidSerializedXorb,
113    /// The uploaded shard bytes were not a valid serialized shard object.
114    #[error("shard body was not a valid serialized shard object")]
115    InvalidSerializedShard(#[from] InvalidSerializedShardError),
116    /// A shard upload referenced a missing xorb.
117    #[error("shard referenced a missing xorb")]
118    MissingReferencedXorb,
119    /// Shard metadata exceeded bounded parser safety limits.
120    #[error("shard metadata exceeded bounded parser safety limits")]
121    TooManyShardTerms,
122    /// Batch reconstruction requested too many file identifiers.
123    #[error("batch reconstruction requested too many file identifiers")]
124    TooManyBatchReconstructionFileIds,
125    /// Requested content was not found.
126    #[error("content not found")]
127    NotFound,
128    /// Arithmetic overflowed a checked bound.
129    #[error("arithmetic overflow")]
130    Overflow,
131    /// The reconstruction range header was malformed.
132    #[error("range header must use bytes=<start>-<end> syntax")]
133    InvalidRangeHeader,
134    /// The reconstruction range start exceeded the end of the file.
135    #[error("requested range is not satisfiable")]
136    RangeNotSatisfiable,
137    /// The request did not include an authorization header.
138    #[error("authorization header is missing")]
139    MissingAuthorization,
140    /// The authorization header was malformed.
141    #[error("authorization header must use bearer format")]
142    InvalidAuthorizationHeader,
143    /// The bearer token was invalid.
144    #[error("bearer token was invalid")]
145    InvalidToken(TokenCodecError),
146    /// The token signing key is misconfigured.
147    #[error("token signing key is misconfigured: {0}")]
148    SigningKeyError(String),
149    /// The bearer token did not grant the required scope.
150    #[error("bearer token does not grant the required scope")]
151    InsufficientScope,
152    /// The provider issuance endpoint is not configured.
153    #[error("provider token issuance endpoint is not configured")]
154    ProviderTokensDisabled,
155    /// The provider bootstrap key was missing.
156    #[error("provider bootstrap key is missing")]
157    MissingProviderApiKey,
158    /// The provider bootstrap key was invalid.
159    #[error("provider bootstrap key is invalid")]
160    InvalidProviderApiKey,
161    /// The provider subject was missing from a bootstrap request.
162    #[error("provider subject is missing")]
163    MissingProviderSubject,
164    /// The provider token issuance request contained invalid bounded metadata.
165    #[error("provider token request was invalid")]
166    InvalidProviderTokenRequest,
167    /// The provider webhook authentication header was missing.
168    #[error("provider webhook authentication is missing")]
169    MissingProviderWebhookAuthentication,
170    /// The provider webhook authentication header was invalid.
171    #[error("provider webhook authentication is invalid")]
172    InvalidProviderWebhookAuthentication,
173    /// The provider webhook payload was invalid.
174    #[error("provider webhook payload was invalid")]
175    InvalidProviderWebhookPayload,
176    /// The requested provider is not configured.
177    #[error("provider is not configured")]
178    UnknownProvider,
179    /// Provider authorization denied the request.
180    #[error("provider denied requested repository access")]
181    ProviderDenied,
182    /// Provider token issuance failed due to configuration or adapter errors.
183    #[error("provider token issuance failed")]
184    Provider(#[from] ProviderServiceError),
185    /// Reconstruction cache adapter access failed.
186    #[error("reconstruction cache adapter operation failed")]
187    ReconstructionCache(#[from] ReconstructionCacheError),
188    /// Server configuration was invalid for the selected runtime surface.
189    #[error("server configuration was invalid")]
190    Config(#[from] ServerConfigError),
191    /// The uploaded body did not match the expected SHA-256 identifier.
192    #[error("uploaded body hash did not match the expected sha256")]
193    ExpectedBodyHashMismatch,
194    /// A digest string was malformed.
195    #[error("digest must use sha256:<64 lowercase hex> format")]
196    InvalidDigest,
197    /// A repository name or namespace path was malformed.
198    #[error("repository name was invalid")]
199    InvalidRepositoryName,
200    /// A manifest reference or tag was malformed.
201    #[error("manifest reference was invalid")]
202    InvalidManifestReference,
203    /// The requested representation does not match any accepted media type.
204    #[error("requested representation was not acceptable")]
205    NotAcceptable,
206    /// The request requires a protocol-specific authentication challenge.
207    #[error("authorization challenge required")]
208    UnauthorizedChallenge(String),
209    /// An upload session identifier was malformed.
210    #[error("upload session identifier was invalid")]
211    InvalidUploadSession,
212    /// Too many OCI upload sessions are currently active.
213    #[error("too many active oci upload sessions")]
214    TooManyUploadSessions,
215    /// Too many OCI registry token exchanges are currently active.
216    #[error("too many active oci registry token requests")]
217    TooManyRegistryTokenRequests,
218    /// Redis reconstruction cache was selected without a URL.
219    #[error("redis reconstruction cache requires a redis url")]
220    MissingReconstructionCacheRedisUrl,
221    /// The transfer concurrency limiter was unexpectedly unavailable.
222    #[error("transfer concurrency limiter is unavailable")]
223    TransferLimiterClosed,
224    /// The transfer concurrency limiter timed out waiting for capacity.
225    #[error("transfer concurrency limiter timed out")]
226    TransferLimiterTimedOut,
227    /// A blocking worker task failed before it could finish storage work.
228    #[error("blocking worker task failed")]
229    BlockingTask(#[source] JoinError),
230}
231
232impl ServerError {
233    /// Maps this error to the OCI Distribution specification error code.
234    ///
235    /// When adding a new [`ServerError`] variant, add it explicitly to this match.
236    /// If the variant has an OCI-specific code, map it above the catch-all.
237    /// If it is an internal error (no OCI-specific meaning), keep it in the catch-all.
238    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            // All remaining variants are internal server errors with no
253            // OCI-specific code. Add new OCI-mappable variants above.
254            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}