Rust implementation of OCI Distribution Spec with granular access control
at main 129 lines 3.6 kB view raw
1use axum::{body::Body, http::StatusCode, response::IntoResponse, response::Response}; 2use serde::{Deserialize, Serialize}; 3 4#[derive(Debug, Clone, Serialize, Deserialize)] 5pub enum ErrorCode { 6 #[serde(rename = "BLOB_UNKNOWN")] 7 BlobUnknown, 8 9 #[serde(rename = "BLOB_UPLOAD_INVALID")] 10 BlobUploadInvalid, 11 12 #[serde(rename = "BLOB_UPLOAD_UNKNOWN")] 13 BlobUploadUnknown, 14 15 #[serde(rename = "DIGEST_INVALID")] 16 DigestInvalid, 17 18 #[serde(rename = "MANIFEST_BLOB_UNKNOWN")] 19 ManifestBlobUnknown, 20 21 #[serde(rename = "MANIFEST_INVALID")] 22 ManifestInvalid, 23 24 #[serde(rename = "MANIFEST_UNKNOWN")] 25 ManifestUnknown, 26 27 #[serde(rename = "MANIFEST_UNVERIFIED")] 28 ManifestUnverified, 29 30 #[serde(rename = "NAME_INVALID")] 31 NameInvalid, 32 33 #[serde(rename = "NAME_UNKNOWN")] 34 NameUnknown, 35 36 #[serde(rename = "SIZE_INVALID")] 37 SizeInvalid, 38 39 #[serde(rename = "TAG_INVALID")] 40 TagInvalid, 41 42 #[serde(rename = "UNAUTHORIZED")] 43 Unauthorized, 44 45 #[serde(rename = "DENIED")] 46 Denied, 47 48 #[serde(rename = "UNSUPPORTED")] 49 Unsupported, 50} 51 52#[derive(Debug, Clone, Serialize, Deserialize)] 53pub struct OciError { 54 pub code: ErrorCode, 55 pub message: String, 56 #[serde(skip_serializing_if = "Option::is_none")] 57 pub detail: Option<String>, 58} 59 60#[derive(Debug, Serialize, Deserialize)] 61pub struct OciErrorResponse { 62 pub errors: Vec<OciError>, 63} 64 65impl OciErrorResponse { 66 pub fn new(code: ErrorCode, message: impl Into<String>) -> Self { 67 Self { 68 errors: vec![OciError { 69 code, 70 message: message.into(), 71 detail: None, 72 }], 73 } 74 } 75 76 pub fn with_detail( 77 code: ErrorCode, 78 message: impl Into<String>, 79 detail: impl Into<String>, 80 ) -> Self { 81 Self { 82 errors: vec![OciError { 83 code, 84 message: message.into(), 85 detail: Some(detail.into()), 86 }], 87 } 88 } 89 90 pub fn to_response(&self, status: StatusCode) -> Response { 91 let json = serde_json::to_string(self).unwrap_or_else(|_| { 92 r#"{"errors":[{"code":"UNKNOWN","message":"internal error"}]}"#.to_string() 93 }); 94 95 Response::builder() 96 .status(status) 97 .header("Content-Type", "application/json") 98 .body(Body::from(json)) 99 .unwrap() 100 } 101} 102 103impl IntoResponse for OciErrorResponse { 104 fn into_response(self) -> Response { 105 let status = match self.errors.first() { 106 Some(err) => match err.code { 107 ErrorCode::Unauthorized => StatusCode::UNAUTHORIZED, 108 ErrorCode::Denied => StatusCode::FORBIDDEN, 109 ErrorCode::BlobUnknown 110 | ErrorCode::ManifestUnknown 111 | ErrorCode::NameUnknown 112 | ErrorCode::BlobUploadUnknown => StatusCode::NOT_FOUND, 113 ErrorCode::DigestInvalid 114 | ErrorCode::ManifestInvalid 115 | ErrorCode::NameInvalid 116 | ErrorCode::TagInvalid 117 | ErrorCode::SizeInvalid 118 | ErrorCode::BlobUploadInvalid => StatusCode::BAD_REQUEST, 119 ErrorCode::Unsupported => StatusCode::METHOD_NOT_ALLOWED, 120 ErrorCode::ManifestBlobUnknown | ErrorCode::ManifestUnverified => { 121 StatusCode::BAD_REQUEST 122 } 123 }, 124 None => StatusCode::INTERNAL_SERVER_ERROR, 125 }; 126 127 self.to_response(status) 128 } 129}