1use std::{io::Error as IoError, num::TryFromIntError};
2
3use axum::{
4 Error as AxumError, Json,
5 http::{HeaderValue, StatusCode, header::WWW_AUTHENTICATE},
6 response::{IntoResponse, Response},
7};
8use serde::Serialize;
9use serde_json::Error as JsonError;
10use shardline_cache::ReconstructionCacheError;
11use shardline_index::{
12 FileRecordInvariantError, LocalIndexStoreError, MemoryIndexStoreError, MemoryRecordStoreError,
13 PostgresMetadataStoreError, QuarantineCandidateError, RetentionHoldError, WebhookDeliveryError,
14};
15use shardline_protocol::{HashParseError, HttpRangeParseError, TokenCodecError};
16pub use shardline_server_core::{
17 InvalidLifecycleMetadataError, InvalidReconstructionResponseError, InvalidSerializedShardError,
18 ParseStoredFileRecordError,
19};
20use shardline_storage::{LocalObjectStoreError, ObjectPrefixError, S3ObjectStoreError};
21use thiserror::Error;
22use tokio::task::JoinError;
23
24use crate::{
25 config::ServerConfigError, provider::ProviderServiceError, xet_adapter::XorbParseError,
26};
27use shardline_gc::GcError;
28use shardline_oci_adapter::OciAdapterError;
29use shardline_provider_events::ProviderEventsError;
30use shardline_xet_adapter::XetAdapterError;
31
32#[derive(Debug, Error)]
34pub enum ObjectStoreError {
35 #[error("local storage operation failed")]
37 Local(#[from] LocalObjectStoreError),
38 #[error("s3 object storage adapter operation failed")]
40 S3(#[from] S3ObjectStoreError),
41 #[error("object storage prefix validation failed")]
43 Prefix(#[from] ObjectPrefixError),
44 #[error("s3 object storage configuration is missing")]
46 MissingS3Config,
47 #[error("stored object length did not match indexed metadata")]
49 StoredLengthMismatch,
50 #[error(
52 "storage migration source object hash mismatch for key {key}: expected {expected_hash}, observed {observed_hash}"
53 )]
54 MigrationSourceHashMismatch {
55 key: String,
57 expected_hash: String,
59 observed_hash: String,
61 },
62}
63
64#[derive(Debug, Error)]
66pub enum IndexError {
67 #[error("index adapter operation failed")]
69 Local(#[from] LocalIndexStoreError),
70 #[error("memory index adapter operation failed")]
72 MemoryIndex(#[from] MemoryIndexStoreError),
73 #[error("memory record adapter operation failed")]
75 MemoryRecord(#[from] MemoryRecordStoreError),
76 #[error("postgres metadata adapter operation failed")]
78 PostgresMetadata(#[from] PostgresMetadataStoreError),
79 #[error("retention hold input was invalid")]
81 RetentionHold(#[from] RetentionHoldError),
82 #[error("quarantine candidate input was invalid")]
84 QuarantineCandidate(#[from] QuarantineCandidateError),
85 #[error("webhook delivery metadata was invalid")]
87 WebhookDelivery(#[from] WebhookDeliveryError),
88 #[error("stored file metadata was invalid")]
90 FileRecordInvariant(#[from] FileRecordInvariantError),
91 #[error("lifecycle metadata was internally inconsistent")]
93 InvalidLifecycleMetadata(#[from] InvalidLifecycleMetadataError),
94 #[error("reconstruction response invariant failed: {0}")]
96 InvalidReconstructionResponse(#[from] InvalidReconstructionResponseError),
97 #[error("required metadata table is missing: {0}")]
99 MissingRequiredMetadataTable(String),
100 #[error("repository rename target already contains conflicting metadata")]
102 ConflictingRenameTargetRecord,
103}
104
105#[derive(Debug, Error)]
121pub enum ServerError {
122 #[error("local storage operation failed")]
124 Io(#[from] IoError),
125 #[error("json operation failed")]
127 Json(#[from] JsonError),
128 #[error("request body stream failed")]
130 RequestBodyRead(#[source] AxumError),
131 #[error("request body exceeded the configured maximum accepted byte count")]
133 RequestBodyTooLarge,
134 #[error("request query exceeded the bounded metadata parser budget")]
136 RequestQueryTooLarge,
137 #[error("request body frame exceeded checked bounds")]
139 RequestBodyFrameOutOfBounds,
140 #[error("numeric conversion exceeded supported bounds")]
142 NumericConversion(#[from] TryFromIntError),
143 #[error("invalid content hash")]
145 HashParse(#[from] HashParseError),
146 #[error("object storage operation failed")]
148 ObjectStore(#[from] ObjectStoreError),
149 #[error("index adapter operation failed")]
151 Index(#[from] IndexError),
152 #[error("stored file metadata exceeded the bounded parser ceiling")]
154 StoredFileMetadataTooLarge {
155 observed_bytes: u64,
157 maximum_bytes: u64,
159 },
160 #[error("stored file metadata length did not match the validated length")]
162 StoredFileMetadataLengthMismatch,
163 #[error(
165 "file identifier must be relative and must not contain traversal or control characters"
166 )]
167 InvalidFileId,
168 #[error("content hash must be 64 hexadecimal characters")]
170 InvalidContentHash,
171 #[error("xorb transfer prefix must be default")]
173 InvalidXorbPrefix,
174 #[error("xorb body hash did not match the requested path hash")]
176 XorbHashMismatch,
177 #[error("xorb body was not a valid serialized xorb object")]
179 InvalidSerializedXorb,
180 #[error("shard body was not a valid serialized shard object")]
182 InvalidSerializedShard(#[from] InvalidSerializedShardError),
183 #[error("shard referenced a missing xorb")]
185 MissingReferencedXorb,
186 #[error("shard metadata exceeded bounded parser safety limits")]
188 TooManyShardTerms,
189 #[error("batch reconstruction requested too many file identifiers")]
191 TooManyBatchReconstructionFileIds,
192 #[error("content not found")]
194 NotFound,
195 #[error("arithmetic overflow")]
197 Overflow,
198 #[error("range header must use bytes=<start>-<end> syntax")]
200 InvalidRangeHeader,
201 #[error("requested range is not satisfiable")]
203 RangeNotSatisfiable,
204 #[error("authorization header is missing")]
206 MissingAuthorization,
207 #[error("authorization header must use bearer format")]
209 InvalidAuthorizationHeader,
210 #[error("bearer token was invalid")]
212 InvalidToken(TokenCodecError),
213 #[error("bearer token does not grant the required scope")]
215 InsufficientScope,
216 #[error("provider token issuance endpoint is not configured")]
218 ProviderTokensDisabled,
219 #[error("provider bootstrap key is missing")]
221 MissingProviderApiKey,
222 #[error("provider bootstrap key is invalid")]
224 InvalidProviderApiKey,
225 #[error("provider subject is missing")]
227 MissingProviderSubject,
228 #[error("provider token request was invalid")]
230 InvalidProviderTokenRequest,
231 #[error("provider webhook authentication is missing")]
233 MissingProviderWebhookAuthentication,
234 #[error("provider webhook authentication is invalid")]
236 InvalidProviderWebhookAuthentication,
237 #[error("provider webhook payload was invalid")]
239 InvalidProviderWebhookPayload,
240 #[error("provider is not configured")]
242 UnknownProvider,
243 #[error("provider denied requested repository access")]
245 ProviderDenied,
246 #[error("provider token issuance failed")]
248 Provider(#[from] ProviderServiceError),
249 #[error("reconstruction cache adapter operation failed")]
251 ReconstructionCache(#[from] ReconstructionCacheError),
252 #[error("server configuration was invalid")]
254 Config(#[from] ServerConfigError),
255 #[error("uploaded body hash did not match the expected sha256")]
257 ExpectedBodyHashMismatch,
258 #[error("digest must use sha256:<64 lowercase hex> format")]
260 InvalidDigest,
261 #[error("repository name was invalid")]
263 InvalidRepositoryName,
264 #[error("manifest reference was invalid")]
266 InvalidManifestReference,
267 #[error("requested representation was not acceptable")]
269 NotAcceptable,
270 #[error("authorization challenge required")]
272 UnauthorizedChallenge(String),
273 #[error("upload session identifier was invalid")]
275 InvalidUploadSession,
276 #[error("too many active oci upload sessions")]
278 TooManyUploadSessions,
279 #[error("too many active oci registry token requests")]
281 TooManyRegistryTokenRequests,
282 #[error("redis reconstruction cache requires a redis url")]
284 MissingReconstructionCacheRedisUrl,
285 #[error("transfer concurrency limiter is unavailable")]
287 TransferLimiterClosed,
288 #[error("blocking worker task failed")]
290 BlockingTask(#[source] JoinError),
291}
292
293impl ServerError {
294 const fn status_code(&self) -> StatusCode {
295 match self {
296 Self::InvalidFileId
297 | Self::InvalidContentHash
298 | Self::InvalidDigest
299 | Self::InvalidRepositoryName
300 | Self::InvalidManifestReference
301 | Self::InvalidUploadSession
302 | Self::InvalidXorbPrefix
303 | Self::HashParse(_) => StatusCode::BAD_REQUEST,
304 Self::NotAcceptable => StatusCode::NOT_ACCEPTABLE,
305 Self::UnauthorizedChallenge(_) => StatusCode::UNAUTHORIZED,
306 Self::InvalidRangeHeader => StatusCode::BAD_REQUEST,
307 Self::RangeNotSatisfiable => StatusCode::RANGE_NOT_SATISFIABLE,
308 Self::XorbHashMismatch
309 | Self::InvalidSerializedXorb
310 | Self::InvalidSerializedShard(_)
311 | Self::MissingReferencedXorb => StatusCode::BAD_REQUEST,
312 Self::TooManyShardTerms
313 | Self::TooManyBatchReconstructionFileIds
314 | Self::RequestBodyTooLarge => StatusCode::PAYLOAD_TOO_LARGE,
315 Self::RequestQueryTooLarge => StatusCode::URI_TOO_LONG,
316 Self::RequestBodyRead(_) | Self::RequestBodyFrameOutOfBounds => StatusCode::BAD_REQUEST,
317 Self::ExpectedBodyHashMismatch => StatusCode::BAD_REQUEST,
318 Self::NotFound | Self::UnknownProvider | Self::ProviderTokensDisabled => {
319 StatusCode::NOT_FOUND
320 }
321 Self::MissingAuthorization
322 | Self::InvalidAuthorizationHeader
323 | Self::InvalidToken(_)
324 | Self::MissingProviderApiKey
325 | Self::MissingProviderSubject
326 | Self::MissingProviderWebhookAuthentication => StatusCode::UNAUTHORIZED,
327 Self::InsufficientScope
328 | Self::InvalidProviderApiKey
329 | Self::InvalidProviderWebhookAuthentication
330 | Self::ProviderDenied => StatusCode::FORBIDDEN,
331 Self::InvalidProviderTokenRequest | Self::InvalidProviderWebhookPayload => {
332 StatusCode::BAD_REQUEST
333 }
334 Self::TooManyUploadSessions | Self::TooManyRegistryTokenRequests => {
335 StatusCode::TOO_MANY_REQUESTS
336 }
337 Self::TransferLimiterClosed => StatusCode::SERVICE_UNAVAILABLE,
338 Self::Io(_)
339 | Self::Json(_)
340 | Self::NumericConversion(_)
341 | Self::ObjectStore(_)
342 | Self::Index(_)
343 | Self::StoredFileMetadataTooLarge { .. }
344 | Self::StoredFileMetadataLengthMismatch
345 | Self::Config(_)
346 | Self::Overflow
347 | Self::MissingReconstructionCacheRedisUrl
348 | Self::ReconstructionCache(_)
349 | Self::BlockingTask(_)
350 | Self::Provider(_) => StatusCode::INTERNAL_SERVER_ERROR,
351 }
352 }
353}
354
355impl IntoResponse for ServerError {
356 fn into_response(self) -> Response {
357 let status = self.status_code();
358 let should_attach_default_challenge = matches!(
359 self,
360 Self::MissingAuthorization | Self::InvalidAuthorizationHeader | Self::InvalidToken(_)
361 );
362 let custom_challenge = if let Self::UnauthorizedChallenge(custom_header) = &self {
363 Some(custom_header.as_str())
364 } else {
365 None
366 };
367 let body = ErrorBody {
368 error: self.to_string(),
369 };
370 let mut response = (status, Json(body)).into_response();
371 if let Some(custom_header) = custom_challenge {
372 if let Ok(header_value) = HeaderValue::from_str(custom_header) {
373 response
374 .headers_mut()
375 .insert(WWW_AUTHENTICATE, header_value);
376 }
377 } else if should_attach_default_challenge {
378 response.headers_mut().insert(
379 WWW_AUTHENTICATE,
380 HeaderValue::from_static("Bearer realm=\"shardline\""),
381 );
382 }
383 response
384 }
385}
386
387impl From<HttpRangeParseError> for ServerError {
388 fn from(value: HttpRangeParseError) -> Self {
389 match value {
390 HttpRangeParseError::Unsatisfiable => Self::RangeNotSatisfiable,
391 HttpRangeParseError::MissingBytesUnit
392 | HttpRangeParseError::InvalidSyntax
393 | HttpRangeParseError::InvalidNumber => Self::InvalidRangeHeader,
394 }
395 }
396}
397
398impl From<XorbParseError> for ServerError {
399 fn from(value: XorbParseError) -> Self {
400 match value {
401 XorbParseError::HashMismatch => Self::XorbHashMismatch,
402 XorbParseError::InvalidFormat(_)
403 | XorbParseError::NumericConversion(_)
404 | XorbParseError::Io(_) => Self::InvalidSerializedXorb,
405 }
406 }
407}
408
409impl From<XetAdapterError> for ServerError {
410 fn from(value: XetAdapterError) -> Self {
411 match value {
412 XetAdapterError::Io(e) => Self::Io(e),
413 XetAdapterError::NumericConversion(e) => Self::NumericConversion(e),
414 XetAdapterError::HashParse(e) => Self::HashParse(e),
415 XetAdapterError::ObjectStore(e) => Self::from(e),
416 XetAdapterError::LocalObjectStore(e) => Self::ObjectStore(ObjectStoreError::Local(e)),
417 XetAdapterError::S3ObjectStore(e) => Self::ObjectStore(ObjectStoreError::S3(e)),
418 XetAdapterError::IndexStore(e) => Self::Index(IndexError::Local(e)),
419 XetAdapterError::MemoryIndexStore(e) => Self::Index(IndexError::MemoryIndex(e)),
420 XetAdapterError::MemoryRecordStore(e) => Self::Index(IndexError::MemoryRecord(e)),
421 XetAdapterError::PostgresMetadata(e) => Self::Index(IndexError::PostgresMetadata(e)),
422 XetAdapterError::FileRecordInvariant(e) => {
423 Self::Index(IndexError::FileRecordInvariant(e))
424 }
425 XetAdapterError::InvalidContentHash => Self::InvalidContentHash,
426 XetAdapterError::InvalidXorbPrefix => Self::InvalidXorbPrefix,
427 XetAdapterError::XorbHashMismatch => Self::XorbHashMismatch,
428 XetAdapterError::InvalidSerializedXorb => Self::InvalidSerializedXorb,
429 XetAdapterError::InvalidSerializedShard(e) => Self::InvalidSerializedShard(e),
430 XetAdapterError::MissingReferencedXorb => Self::MissingReferencedXorb,
431 XetAdapterError::TooManyShardTerms => Self::TooManyShardTerms,
432 XetAdapterError::NotFound => Self::NotFound,
433 XetAdapterError::Overflow => Self::Overflow,
434 XetAdapterError::RangeNotSatisfiable => Self::RangeNotSatisfiable,
435 }
436 }
437}
438
439impl From<ProviderEventsError> for ServerError {
440 fn from(value: ProviderEventsError) -> Self {
441 match value {
442 ProviderEventsError::Overflow => Self::Overflow,
443 ProviderEventsError::InvalidContentHash => Self::InvalidContentHash,
444 ProviderEventsError::InvalidProviderWebhookPayload => {
445 Self::InvalidProviderWebhookPayload
446 }
447 ProviderEventsError::ConflictingRenameTargetRecord => {
448 Self::Index(IndexError::ConflictingRenameTargetRecord)
449 }
450 ProviderEventsError::Json(e) => Self::Json(e),
451 ProviderEventsError::NumericConversion(e) => Self::NumericConversion(e),
452 ProviderEventsError::RetentionHold(e) => Self::Index(IndexError::RetentionHold(e)),
453 ProviderEventsError::XetAdapter(e) => Self::from(e),
454 ProviderEventsError::IndexStore(e) => Self::Index(IndexError::Local(e)),
455 ProviderEventsError::MemoryIndexStore(e) => Self::Index(IndexError::MemoryIndex(e)),
456 ProviderEventsError::MemoryRecordStore(e) => Self::Index(IndexError::MemoryRecord(e)),
457 ProviderEventsError::PostgresMetadata(e) => {
458 Self::Index(IndexError::PostgresMetadata(e))
459 }
460 ProviderEventsError::WebhookDelivery(e) => Self::Index(IndexError::WebhookDelivery(e)),
461 ProviderEventsError::ObjectStore(e) => Self::from(e),
462 ProviderEventsError::ParseStoredFileRecord(e) => Self::Io(std::io::Error::new(
463 std::io::ErrorKind::InvalidData,
464 e.to_string(),
465 )),
466 }
467 }
468}
469
470impl From<GcError> for ServerError {
471 fn from(err: GcError) -> Self {
472 match err {
473 GcError::Io(e) => Self::Io(e),
474 GcError::Json(e) => Self::Json(e),
475 GcError::NumericConversion(e) => Self::NumericConversion(e),
476 GcError::ObjectStore(e) => Self::from(e),
477 GcError::LocalObjectStore(e) => Self::ObjectStore(ObjectStoreError::Local(e)),
478 GcError::S3ObjectStore(e) => Self::ObjectStore(ObjectStoreError::S3(e)),
479 GcError::ObjectPrefix(e) => Self::ObjectStore(ObjectStoreError::Prefix(e)),
480 GcError::IndexStore(e) => Self::Index(IndexError::Local(e)),
481 GcError::MemoryIndexStore(e) => Self::Index(IndexError::MemoryIndex(e)),
482 GcError::MemoryRecordStore(e) => Self::Index(IndexError::MemoryRecord(e)),
483 GcError::PostgresMetadata(e) => Self::Index(IndexError::PostgresMetadata(e)),
484 GcError::RetentionHold(e) => Self::Index(IndexError::RetentionHold(e)),
485 GcError::QuarantineCandidate(e) => Self::Index(IndexError::QuarantineCandidate(e)),
486 GcError::WebhookDelivery(e) => Self::Index(IndexError::WebhookDelivery(e)),
487 GcError::FileRecordInvariant(e) => Self::Index(IndexError::FileRecordInvariant(e)),
488 GcError::InvalidLifecycleMetadata(e) => {
489 Self::Index(IndexError::InvalidLifecycleMetadata(e))
490 }
491 GcError::InvalidContentHash => Self::InvalidContentHash,
492 GcError::Overflow => Self::Overflow,
493 GcError::XetAdapter(e) => Self::from(e),
494 }
495 }
496}
497
498impl From<OciAdapterError> for ServerError {
499 fn from(value: OciAdapterError) -> Self {
500 match value {
501 OciAdapterError::Io(e) => Self::Io(e),
502 OciAdapterError::Json(e) => Self::Json(e),
503 OciAdapterError::NumericConversion(e) => Self::NumericConversion(e),
504 OciAdapterError::ObjectStore(e) => Self::from(e),
505 OciAdapterError::S3ObjectStore(e) => Self::ObjectStore(ObjectStoreError::S3(e)),
506 OciAdapterError::LocalObjectStore(e) => Self::ObjectStore(ObjectStoreError::Local(e)),
507 OciAdapterError::ObjectPrefix(e) => Self::ObjectStore(ObjectStoreError::Prefix(e)),
508 OciAdapterError::NotFound => Self::NotFound,
509 OciAdapterError::Overflow => Self::Overflow,
510 OciAdapterError::InvalidContentHash => Self::InvalidContentHash,
511 OciAdapterError::InvalidDigest => Self::InvalidDigest,
512 OciAdapterError::InvalidRepositoryName => Self::InvalidRepositoryName,
513 OciAdapterError::InvalidManifestReference => Self::InvalidManifestReference,
514 OciAdapterError::InvalidUploadSession => Self::InvalidUploadSession,
515 OciAdapterError::TooManyUploadSessions => Self::TooManyUploadSessions,
516 OciAdapterError::ExpectedBodyHashMismatch => Self::ExpectedBodyHashMismatch,
517 OciAdapterError::BlockingTask(e) => Self::BlockingTask(e),
518 }
519 }
520}
521
522impl From<shardline_protocol_adapters::ProtocolError> for ServerError {
523 fn from(error: shardline_protocol_adapters::ProtocolError) -> Self {
524 match error {
525 shardline_protocol_adapters::ProtocolError::InvalidContentHash => {
526 Self::InvalidContentHash
527 }
528 }
529 }
530}
531
532impl From<LocalObjectStoreError> for ServerError {
533 fn from(e: LocalObjectStoreError) -> Self {
534 Self::ObjectStore(ObjectStoreError::Local(e))
535 }
536}
537
538impl From<S3ObjectStoreError> for ServerError {
539 fn from(e: S3ObjectStoreError) -> Self {
540 Self::ObjectStore(ObjectStoreError::S3(e))
541 }
542}
543
544impl From<ObjectPrefixError> for ServerError {
545 fn from(e: ObjectPrefixError) -> Self {
546 Self::ObjectStore(ObjectStoreError::Prefix(e))
547 }
548}
549
550impl From<LocalIndexStoreError> for ServerError {
551 fn from(e: LocalIndexStoreError) -> Self {
552 Self::Index(IndexError::Local(e))
553 }
554}
555
556impl From<MemoryIndexStoreError> for ServerError {
557 fn from(e: MemoryIndexStoreError) -> Self {
558 Self::Index(IndexError::MemoryIndex(e))
559 }
560}
561
562impl From<MemoryRecordStoreError> for ServerError {
563 fn from(e: MemoryRecordStoreError) -> Self {
564 Self::Index(IndexError::MemoryRecord(e))
565 }
566}
567
568impl From<PostgresMetadataStoreError> for ServerError {
569 fn from(e: PostgresMetadataStoreError) -> Self {
570 Self::Index(IndexError::PostgresMetadata(e))
571 }
572}
573
574impl From<RetentionHoldError> for ServerError {
575 fn from(e: RetentionHoldError) -> Self {
576 Self::Index(IndexError::RetentionHold(e))
577 }
578}
579
580impl From<QuarantineCandidateError> for ServerError {
581 fn from(e: QuarantineCandidateError) -> Self {
582 Self::Index(IndexError::QuarantineCandidate(e))
583 }
584}
585
586impl From<WebhookDeliveryError> for ServerError {
587 fn from(e: WebhookDeliveryError) -> Self {
588 Self::Index(IndexError::WebhookDelivery(e))
589 }
590}
591
592impl From<FileRecordInvariantError> for ServerError {
593 fn from(e: FileRecordInvariantError) -> Self {
594 Self::Index(IndexError::FileRecordInvariant(e))
595 }
596}
597
598impl From<InvalidLifecycleMetadataError> for ServerError {
599 fn from(e: InvalidLifecycleMetadataError) -> Self {
600 Self::Index(IndexError::InvalidLifecycleMetadata(e))
601 }
602}
603
604impl From<InvalidReconstructionResponseError> for ServerError {
605 fn from(e: InvalidReconstructionResponseError) -> Self {
606 Self::Index(IndexError::InvalidReconstructionResponse(e))
607 }
608}
609
610impl From<ParseStoredFileRecordError> for ServerError {
611 fn from(e: ParseStoredFileRecordError) -> Self {
612 match e {
613 ParseStoredFileRecordError::StoredFileMetadataTooLarge {
614 observed_bytes,
615 maximum_bytes,
616 } => Self::StoredFileMetadataTooLarge {
617 observed_bytes,
618 maximum_bytes,
619 },
620 ParseStoredFileRecordError::Json(e) => Self::Json(e),
621 }
622 }
623}
624
625#[derive(Debug, Serialize)]
626struct ErrorBody {
627 error: String,
628}
629
630#[cfg(test)]
631mod tests {
632 use axum::http::StatusCode;
633
634 use super::ServerError;
635
636 fn status_for(error: &ServerError) -> StatusCode {
637 error.status_code()
638 }
639
640 #[test]
641 fn not_found_maps_to_404() {
642 assert_eq!(status_for(&ServerError::NotFound), StatusCode::NOT_FOUND);
643 }
644
645 #[test]
646 fn insufficient_scope_maps_to_403() {
647 assert_eq!(
648 status_for(&ServerError::InsufficientScope),
649 StatusCode::FORBIDDEN
650 );
651 }
652
653 #[test]
654 fn too_many_upload_sessions_maps_to_429() {
655 assert_eq!(
656 status_for(&ServerError::TooManyUploadSessions),
657 StatusCode::TOO_MANY_REQUESTS
658 );
659 }
660
661 #[test]
662 fn too_many_registry_token_requests_maps_to_429() {
663 assert_eq!(
664 status_for(&ServerError::TooManyRegistryTokenRequests),
665 StatusCode::TOO_MANY_REQUESTS
666 );
667 }
668
669 #[test]
670 fn missing_authorization_maps_to_401() {
671 assert_eq!(
672 status_for(&ServerError::MissingAuthorization),
673 StatusCode::UNAUTHORIZED
674 );
675 }
676
677 #[test]
678 fn request_body_too_large_maps_to_413() {
679 assert_eq!(
680 status_for(&ServerError::RequestBodyTooLarge),
681 StatusCode::PAYLOAD_TOO_LARGE
682 );
683 }
684
685 #[test]
686 fn invalid_file_id_maps_to_400() {
687 assert_eq!(
688 status_for(&ServerError::InvalidFileId),
689 StatusCode::BAD_REQUEST
690 );
691 }
692
693 #[test]
694 fn range_not_satisfiable_maps_to_416() {
695 assert_eq!(
696 status_for(&ServerError::RangeNotSatisfiable),
697 StatusCode::RANGE_NOT_SATISFIABLE
698 );
699 }
700
701 #[test]
702 fn not_acceptable_maps_to_406() {
703 assert_eq!(
704 status_for(&ServerError::NotAcceptable),
705 StatusCode::NOT_ACCEPTABLE
706 );
707 }
708
709 #[test]
710 fn io_error_maps_to_500() {
711 let io_err = std::io::Error::other("test");
712 assert_eq!(
713 status_for(&ServerError::Io(io_err)),
714 StatusCode::INTERNAL_SERVER_ERROR
715 );
716 }
717
718 #[test]
719 fn transfer_limiter_closed_maps_to_503() {
720 assert_eq!(
721 status_for(&ServerError::TransferLimiterClosed),
722 StatusCode::SERVICE_UNAVAILABLE
723 );
724 }
725
726 #[test]
727 fn provider_denied_maps_to_403() {
728 assert_eq!(
729 status_for(&ServerError::ProviderDenied),
730 StatusCode::FORBIDDEN
731 );
732 }
733
734 #[test]
735 fn unknown_provider_maps_to_404() {
736 assert_eq!(
737 status_for(&ServerError::UnknownProvider),
738 StatusCode::NOT_FOUND
739 );
740 }
741
742 #[test]
743 fn overflow_maps_to_500() {
744 assert_eq!(
745 status_for(&ServerError::Overflow),
746 StatusCode::INTERNAL_SERVER_ERROR
747 );
748 }
749
750 #[test]
751 fn invalid_content_hash_maps_to_400() {
752 assert_eq!(
753 status_for(&ServerError::InvalidContentHash),
754 StatusCode::BAD_REQUEST
755 );
756 }
757
758 #[test]
759 fn expected_body_hash_mismatch_maps_to_400() {
760 assert_eq!(
761 status_for(&ServerError::ExpectedBodyHashMismatch),
762 StatusCode::BAD_REQUEST
763 );
764 }
765}