mirror of
https://github.com/versity/versitygw.git
synced 2026-09-24 08:54:47 +00:00
feat: global error refactoring
Fixes #2123 Fixes #2120 Fixes #2116 Fixes #2111 Fixes #2108 Fixes #2086 Fixes #2085 Fixes #2083 Fixes #2081 Fixes #2080 Fixes #2073 Fixes #2072 Fixes #2071 Fixes #2069 Fixes #2044 Fixes #2043 Fixes #2042 Fixes #2041 Fixes #2040 Fixes #2039 Fixes #2036 Fixes #2035 Fixes #2034 Fixes #2028 Fixes #2020 Fixes #1842 Fixes #1810 Fixes #1780 Fixes #1775 Fixes #1736 Fixes #1705 Fixes #1663 Fixes #1645 Fixes #1583 Fixes #1526 Fixes #1514 Fixes #1493 Fixes #1487 Fixes #959 Fixes #779 Closes #823 Closes #85 Refactor global S3 error handling around structured error types and centralized XML response generation. All S3 errors now share the common APIError base for the fields every error has: Code, HTTP status code, and Message. Non-traditional errors that need AWS-compatible XML fields now have dedicated typed errors in the s3err package. Each typed error implements the shared S3Error behavior so controllers and middleware can handle errors consistently while still emitting error-specific XML fields. Add a dedicated InvalidArgumentError type because InvalidArgument is used widely across request validation, auth, copy source handling, object lock validation, multipart validation, and header parsing. The new InvalidArgument path uses explicit InvalidArgErrorCode constants with predefined descriptions and ArgumentName values, keeping call sites readable while preserving the correct InvalidArgument XML shape and optional ArgumentValue. New structured errors added in s3err: - `AccessForbiddenError`: Method, ResourceType - `BadDigestError`: CalculatedDigest, ExpectedDigest - `BucketError`: BucketName - `ContentSHA256MismatchError`: ClientComputedContentSHA256, S3ComputedContentSHA256 - `EntityTooLargeError`: ProposedSize, MaxSizeAllowed - `EntityTooSmallError`: ProposedSize, MinSizeAllowed - `ExpiredPresignedURLError`: ServerTime, XAmzExpires, Expires - `InvalidAccessKeyIdError`: AWSAccessKeyId - `InvalidArgumentError`: Description, ArgumentName, ArgumentValue - `InvalidChunkSizeError`: Chunk, BadChunkSize - `InvalidDigestError`: ContentMD5 - `InvalidLocationConstraintError`: LocationConstraint - `InvalidPartError`: UploadId, PartNumber, ETag - `InvalidRangeError`: RangeRequested, ActualObjectSize - `InvalidTagError`: TagKey, TagValue - `KeyTooLongError`: Size, MaxSizeAllowed - `MetadataTooLargeError`: Size, MaxSizeAllowed - `MethodNotAllowedError`: Method, ResourceType, AllowedMethods - `NoSuchUploadError`: UploadId - `NoSuchVersionError`: Key, VersionId - `NotImplementedError`: Header, AdditionalMessage - `PreconditionFailedError`: Condition - `RequestTimeTooSkewedError`: RequestTime, ServerTime, MaxAllowedSkewMilliseconds - `SignatureDoesNotMatchError`: AWSAccessKeyId, StringToSign, SignatureProvided, StringToSignBytes, CanonicalRequest, CanonicalRequestBytes Fix CompleteMultipartUpload validation in the Azure backend so missing or empty `ETag` values return the appropriate S3 error instead of allowing a gateway panic. Fix presigned authentication expiration validation to compare server time in `UTC`, matching the `UTC` timestamp used by presigned URL signing. Add request ID and host ID support across S3 requests. Each request now receives AWS S3-like identifiers, returned in response headers as `x-amz-request-id` and `x-amz-id-2` and included in all XML error responses as RequestId and HostId. The generated ID structure is designed to resemble AWS S3 request IDs and host IDs. The request signature calculation/validation for streaming uploads was previously delayed until the request body was fully read, both for Authorization header authentication and presigned URLs. Now, the signature is validated immediately in the authorization middlewares without reading the request body, since the signature calculation itself does not depend on the request body. Instead, only the `x-amz-content-sha256` SHA-256 hash calculation is delayed.
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
// Copyright 2026 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package s3err
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// AccessForbiddenError is returned when a CORS request is not allowed.
|
||||
// Produces <Method> and <ResourceType> fields in the XML response.
|
||||
type AccessForbiddenError struct {
|
||||
APIError
|
||||
Method string
|
||||
ResourceType ResourceType
|
||||
}
|
||||
|
||||
func (e AccessForbiddenError) XMLBody(requestID, hostID string) []byte {
|
||||
return encodeResponse(struct {
|
||||
XMLName xml.Name `xml:"Error"`
|
||||
Code string
|
||||
Message string
|
||||
Method string `xml:",omitempty"`
|
||||
ResourceType ResourceType `xml:",omitempty"`
|
||||
RequestID string `xml:"RequestId,omitempty"`
|
||||
HostID string `xml:"HostId,omitempty"`
|
||||
}{
|
||||
Code: e.Code,
|
||||
Message: e.Description,
|
||||
Method: e.Method,
|
||||
ResourceType: e.ResourceType,
|
||||
RequestID: requestID,
|
||||
HostID: hostID,
|
||||
})
|
||||
}
|
||||
|
||||
func (e AccessForbiddenError) Is(target error) bool {
|
||||
t, ok := target.(APIError)
|
||||
return ok && e.APIError == t
|
||||
}
|
||||
|
||||
func GetAccessForbiddenErr(code ErrorCode, method string, resourceType ResourceType) AccessForbiddenError {
|
||||
return AccessForbiddenError{
|
||||
APIError: GetAPIError(code),
|
||||
Method: method,
|
||||
ResourceType: resourceType,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2026 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package s3err
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// BadDigestError is returned when the Content-MD5 does not match the received data.
|
||||
// Produces <CalculatedDigest> and <ExpectedDigest> fields in the XML response.
|
||||
type BadDigestError struct {
|
||||
APIError
|
||||
CalculatedDigest string
|
||||
ExpectedDigest string
|
||||
}
|
||||
|
||||
func (e BadDigestError) XMLBody(requestID, hostID string) []byte {
|
||||
return encodeResponse(struct {
|
||||
XMLName xml.Name `xml:"Error"`
|
||||
Code string
|
||||
Message string
|
||||
CalculatedDigest string `xml:",omitempty"`
|
||||
ExpectedDigest string `xml:",omitempty"`
|
||||
RequestID string `xml:"RequestId,omitempty"`
|
||||
HostID string `xml:"HostId,omitempty"`
|
||||
}{
|
||||
Code: e.Code,
|
||||
Message: e.Description,
|
||||
CalculatedDigest: e.CalculatedDigest,
|
||||
ExpectedDigest: e.ExpectedDigest,
|
||||
RequestID: requestID,
|
||||
HostID: hostID,
|
||||
})
|
||||
}
|
||||
|
||||
func (e BadDigestError) Is(target error) bool {
|
||||
t, ok := target.(APIError)
|
||||
return ok && e.APIError == t
|
||||
}
|
||||
|
||||
func GetBadDigestErr(calculated, expected string) BadDigestError {
|
||||
return BadDigestError{
|
||||
APIError: GetAPIError(ErrBadDigest),
|
||||
CalculatedDigest: calculated,
|
||||
ExpectedDigest: expected,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright 2026 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package s3err
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// BucketError is returned for errors that include the bucket name.
|
||||
// Produces a <BucketName> field in the XML response.
|
||||
type BucketError struct {
|
||||
APIError
|
||||
BucketName string
|
||||
}
|
||||
|
||||
func (e BucketError) XMLBody(requestID, hostID string) []byte {
|
||||
return encodeResponse(struct {
|
||||
XMLName xml.Name `xml:"Error"`
|
||||
Code string
|
||||
Message string
|
||||
BucketName string
|
||||
RequestID string `xml:"RequestId,omitempty"`
|
||||
HostID string `xml:"HostId,omitempty"`
|
||||
}{
|
||||
Code: e.Code,
|
||||
Message: e.Description,
|
||||
BucketName: e.BucketName,
|
||||
RequestID: requestID,
|
||||
HostID: hostID,
|
||||
})
|
||||
}
|
||||
|
||||
func (e BucketError) Is(target error) bool {
|
||||
t, ok := target.(APIError)
|
||||
return ok && e.APIError == t
|
||||
}
|
||||
|
||||
// GetBucketErr creates a BucketError for the given error code and bucket name.
|
||||
// Use for: ErrNoSuchBucketPolicy, ErrOwnershipControlsNotFound, ErrBucketNotEmpty,
|
||||
// ErrNoSuchCORSConfiguration, and similar errors that should include the bucket name.
|
||||
func GetBucketErr(code ErrorCode, bucket string) BucketError {
|
||||
return BucketError{
|
||||
APIError: GetAPIError(code),
|
||||
BucketName: bucket,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright 2026 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package s3err
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// ContentSHA256MismatchError is returned when the x-amz-content-sha256 header does not match
|
||||
// the computed hash of the request payload.
|
||||
// Produces <ClientComputedContentSHA256> and <S3ComputedContentSHA256> fields.
|
||||
type ContentSHA256MismatchError struct {
|
||||
APIError
|
||||
ClientComputedContentSHA256 string
|
||||
S3ComputedContentSHA256 string
|
||||
}
|
||||
|
||||
func (e ContentSHA256MismatchError) XMLBody(requestID, hostID string) []byte {
|
||||
return encodeResponse(struct {
|
||||
XMLName xml.Name `xml:"Error"`
|
||||
Code string
|
||||
Message string
|
||||
ClientComputedContentSHA256 string `xml:",omitempty"`
|
||||
S3ComputedContentSHA256 string `xml:",omitempty"`
|
||||
RequestID string `xml:"RequestId,omitempty"`
|
||||
HostID string `xml:"HostId,omitempty"`
|
||||
}{
|
||||
Code: e.Code,
|
||||
Message: e.Description,
|
||||
ClientComputedContentSHA256: e.ClientComputedContentSHA256,
|
||||
S3ComputedContentSHA256: e.S3ComputedContentSHA256,
|
||||
RequestID: requestID,
|
||||
HostID: hostID,
|
||||
})
|
||||
}
|
||||
|
||||
func (e ContentSHA256MismatchError) Is(target error) bool {
|
||||
t, ok := target.(APIError)
|
||||
return ok && e.APIError == t
|
||||
}
|
||||
|
||||
func GetContentSHA256MismatchErr(clientHash, s3Hash string) ContentSHA256MismatchError {
|
||||
return ContentSHA256MismatchError{
|
||||
APIError: GetAPIError(ErrContentSHA256Mismatch),
|
||||
ClientComputedContentSHA256: clientHash,
|
||||
S3ComputedContentSHA256: s3Hash,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2026 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package s3err
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// EntityTooLargeError is returned when the proposed upload exceeds the maximum allowed size.
|
||||
// Produces <ProposedSize> and <MaxSizeAllowed> fields in the XML response.
|
||||
type EntityTooLargeError struct {
|
||||
APIError
|
||||
ProposedSize int64
|
||||
MaxSizeAllowed int64
|
||||
}
|
||||
|
||||
func (e EntityTooLargeError) XMLBody(requestID, hostID string) []byte {
|
||||
return encodeResponse(struct {
|
||||
XMLName xml.Name `xml:"Error"`
|
||||
Code string
|
||||
Message string
|
||||
ProposedSize int64 `xml:",omitempty"`
|
||||
MaxSizeAllowed int64 `xml:",omitempty"`
|
||||
RequestID string `xml:"RequestId,omitempty"`
|
||||
HostID string `xml:"HostId,omitempty"`
|
||||
}{
|
||||
Code: e.Code,
|
||||
Message: e.Description,
|
||||
ProposedSize: e.ProposedSize,
|
||||
MaxSizeAllowed: e.MaxSizeAllowed,
|
||||
RequestID: requestID,
|
||||
HostID: hostID,
|
||||
})
|
||||
}
|
||||
|
||||
func (e EntityTooLargeError) Is(target error) bool {
|
||||
t, ok := target.(APIError)
|
||||
return ok && e.APIError == t
|
||||
}
|
||||
|
||||
func GetEntityTooLargeErr(proposedSize, maxSizeAllowed int64) EntityTooLargeError {
|
||||
return EntityTooLargeError{
|
||||
APIError: GetAPIError(ErrEntityTooLarge),
|
||||
ProposedSize: proposedSize,
|
||||
MaxSizeAllowed: maxSizeAllowed,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2026 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package s3err
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// EntityTooSmallError is returned when the proposed upload is smaller than the minimum allowed size.
|
||||
// Produces <ProposedSize> and <MinSizeAllowed> fields in the XML response.
|
||||
type EntityTooSmallError struct {
|
||||
APIError
|
||||
ProposedSize int64
|
||||
MinSizeAllowed int64
|
||||
}
|
||||
|
||||
func (e EntityTooSmallError) XMLBody(requestID, hostID string) []byte {
|
||||
return encodeResponse(struct {
|
||||
XMLName xml.Name `xml:"Error"`
|
||||
Code string
|
||||
Message string
|
||||
ProposedSize int64 `xml:",omitempty"`
|
||||
MinSizeAllowed int64 `xml:",omitempty"`
|
||||
RequestID string `xml:"RequestId,omitempty"`
|
||||
HostID string `xml:"HostId,omitempty"`
|
||||
}{
|
||||
Code: e.Code,
|
||||
Message: e.Description,
|
||||
ProposedSize: e.ProposedSize,
|
||||
MinSizeAllowed: e.MinSizeAllowed,
|
||||
RequestID: requestID,
|
||||
HostID: hostID,
|
||||
})
|
||||
}
|
||||
|
||||
func (e EntityTooSmallError) Is(target error) bool {
|
||||
t, ok := target.(APIError)
|
||||
return ok && e.APIError == t
|
||||
}
|
||||
|
||||
func GetEntityTooSmallErr(proposedSize, minSizeAllowed int64) EntityTooSmallError {
|
||||
return EntityTooSmallError{
|
||||
APIError: GetAPIError(ErrEntityTooSmall),
|
||||
ProposedSize: proposedSize,
|
||||
MinSizeAllowed: minSizeAllowed,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright 2026 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package s3err
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// ExpiredPresignedURLError is returned when the presigned url is expired
|
||||
// Produces <ServerTime>, <X-Amz-Expires> and <Expires> fields in the XML response.
|
||||
type ExpiredPresignedURLError struct {
|
||||
APIError
|
||||
ServerTime string
|
||||
XAmzExpires int
|
||||
Expires string
|
||||
}
|
||||
|
||||
func (e ExpiredPresignedURLError) XMLBody(requestID, hostID string) []byte {
|
||||
return encodeResponse(struct {
|
||||
XMLName xml.Name `xml:"Error"`
|
||||
Code string
|
||||
Message string
|
||||
ServerTime string `xml:",omitempty"`
|
||||
XAmzExpires int `xml:"X-Amz-Expires,omitempty"`
|
||||
Expires string `xml:",omitempty"`
|
||||
RequestID string `xml:"RequestId,omitempty"`
|
||||
HostID string `xml:"HostId,omitempty"`
|
||||
}{
|
||||
Code: e.Code,
|
||||
Message: e.Description,
|
||||
ServerTime: e.ServerTime,
|
||||
XAmzExpires: e.XAmzExpires,
|
||||
Expires: e.Expires,
|
||||
RequestID: requestID,
|
||||
HostID: hostID,
|
||||
})
|
||||
}
|
||||
|
||||
func (e ExpiredPresignedURLError) Is(target error) bool {
|
||||
t, ok := target.(APIError)
|
||||
return ok && e.APIError == t
|
||||
}
|
||||
|
||||
func GetExpiredPresignedURLError(XAmzExpires int, expires, serverTime string) ExpiredPresignedURLError {
|
||||
return ExpiredPresignedURLError{
|
||||
APIError: GetAPIError(ErrExpiredPresignRequest),
|
||||
XAmzExpires: XAmzExpires,
|
||||
Expires: expires,
|
||||
ServerTime: serverTime,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2026 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package s3err
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// InvalidAccessKeyIdError is returned when the provided AWS access key ID does not exist.
|
||||
// Produces an <AWSAccessKeyId> field in the XML response.
|
||||
type InvalidAccessKeyIdError struct {
|
||||
APIError
|
||||
AWSAccessKeyId string
|
||||
}
|
||||
|
||||
func (e InvalidAccessKeyIdError) XMLBody(requestID, hostID string) []byte {
|
||||
return encodeResponse(struct {
|
||||
XMLName xml.Name `xml:"Error"`
|
||||
Code string
|
||||
Message string
|
||||
AWSAccessKeyId string `xml:",omitempty"`
|
||||
RequestID string `xml:"RequestId,omitempty"`
|
||||
HostID string `xml:"HostId,omitempty"`
|
||||
}{
|
||||
Code: e.Code,
|
||||
Message: e.Description,
|
||||
AWSAccessKeyId: e.AWSAccessKeyId,
|
||||
RequestID: requestID,
|
||||
HostID: hostID,
|
||||
})
|
||||
}
|
||||
|
||||
func (e InvalidAccessKeyIdError) Is(target error) bool {
|
||||
t, ok := target.(APIError)
|
||||
return ok && e.APIError == t
|
||||
}
|
||||
|
||||
func GetInvalidAccessKeyIdErr(accessKeyId string) InvalidAccessKeyIdError {
|
||||
return InvalidAccessKeyIdError{
|
||||
APIError: GetAPIError(ErrInvalidAccessKeyID),
|
||||
AWSAccessKeyId: accessKeyId,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
// Copyright 2026 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package s3err
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type InvalidArgErrorCode int
|
||||
|
||||
const (
|
||||
InvalidArgMaxBuckets InvalidArgErrorCode = iota
|
||||
InvalidArgNegativeMaxKeys
|
||||
InvalidArgObjectAttributes
|
||||
InvalidArgPartNumber
|
||||
InvalidArgCompleteMpPartNumber
|
||||
InvalidArgCopySourceRange
|
||||
InvalidArgCopySourceBucket
|
||||
InvalidArgCopySourceObject
|
||||
InvalidArgCopySourceEncoding
|
||||
InvalidArgURLEncodedTagging
|
||||
InvalidArgAuthHeader
|
||||
InvalidArgAuthorizationType
|
||||
InvalidArgPOSTFileRequired
|
||||
InvalidArgSHA256Payload
|
||||
InvalidArgCopySource
|
||||
InvalidArgRetainUntilDate
|
||||
InvalidArgPastObjectLockRetainDate
|
||||
InvalidArgObjectLockRetentionDays
|
||||
InvalidArgObjectLockRetentionYears
|
||||
InvalidArgMissingObjectLockRetainDate
|
||||
InvalidArgMissingObjectLockMode
|
||||
InvalidArgLegalHoldStatus
|
||||
InvalidArgObjectLockMode
|
||||
InvalidArgMetadataDirective
|
||||
InvalidArgTaggingDirective
|
||||
InvalidArgVersionId
|
||||
InvalidArgChecksumPart
|
||||
InvalidArgMissingUploadId
|
||||
InvalidArgUploadIdMarker
|
||||
InvalidArgCannedAcl
|
||||
InvalidArgOnlyAws4HmacSha256
|
||||
InvalidArgDateHeader
|
||||
)
|
||||
|
||||
var invalidArgErrResponses = map[InvalidArgErrorCode]InvalidArgumentError{
|
||||
InvalidArgMaxBuckets: {
|
||||
Description: "Argument max-buckets must be an integer between 1 and 10000.",
|
||||
ArgumentName: "max-buckets",
|
||||
},
|
||||
InvalidArgNegativeMaxKeys: {
|
||||
Description: "max-keys cannot be negative",
|
||||
ArgumentName: "maxKeys",
|
||||
},
|
||||
InvalidArgObjectAttributes: {
|
||||
Description: "Invalid attribute name specified.",
|
||||
ArgumentName: "x-amz-object-attributes",
|
||||
},
|
||||
InvalidArgPartNumber: {
|
||||
Description: "Part number must be an integer between 1 and 10000, inclusive.",
|
||||
ArgumentName: "partNumber",
|
||||
},
|
||||
InvalidArgCompleteMpPartNumber: {
|
||||
Description: "PartNumber must be >= 1",
|
||||
ArgumentName: "PartNumber",
|
||||
},
|
||||
InvalidArgCopySourceRange: {
|
||||
Description: "The x-amz-copy-source-range value must be of the form bytes=first-last where first and last are the zero-based offsets of the first and last bytes to copy",
|
||||
ArgumentName: "x-amz-copy-source-range",
|
||||
},
|
||||
InvalidArgCopySourceBucket: {
|
||||
Description: "Invalid copy source bucket name",
|
||||
ArgumentName: "x-amz-copy-source",
|
||||
},
|
||||
InvalidArgCopySourceObject: {
|
||||
Description: "Invalid copy source object key",
|
||||
ArgumentName: "x-amz-copy-source",
|
||||
},
|
||||
InvalidArgCopySourceEncoding: {
|
||||
Description: "Invalid copy source encoding",
|
||||
ArgumentName: "x-amz-copy-source",
|
||||
},
|
||||
InvalidArgURLEncodedTagging: {
|
||||
Description: "The header 'x-amz-tagging' shall be encoded as UTF-8 then URLEncoded URL query parameters without tag name duplicates.",
|
||||
ArgumentName: "x-amz-tagging",
|
||||
},
|
||||
InvalidArgAuthHeader: {
|
||||
Description: "Authorization header is invalid -- one and only one ' ' (space) required.",
|
||||
ArgumentName: "Authorization",
|
||||
},
|
||||
InvalidArgAuthorizationType: {
|
||||
Description: "Unsupported Authorization Type",
|
||||
ArgumentName: "Authorization",
|
||||
},
|
||||
InvalidArgPOSTFileRequired: {
|
||||
Description: "POST requires exactly one file upload per request.",
|
||||
ArgumentName: "file",
|
||||
},
|
||||
InvalidArgSHA256Payload: {
|
||||
Description: "x-amz-content-sha256 must be UNSIGNED-PAYLOAD, STREAMING-UNSIGNED-PAYLOAD-TRAILER, STREAMING-AWS4-HMAC-SHA256-PAYLOAD, STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER, STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD, STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD-TRAILER or a valid sha256 value.",
|
||||
ArgumentName: "x-amz-content-sha256",
|
||||
},
|
||||
InvalidArgCopySource: {
|
||||
Description: "You can only specify a copy source header for copy requests.",
|
||||
ArgumentName: "x-amz-copy-source",
|
||||
},
|
||||
InvalidArgRetainUntilDate: {
|
||||
Description: "The retain until date must be provided in ISO 8601 format",
|
||||
ArgumentName: "x-amz-object-lock-retain-until-date",
|
||||
},
|
||||
InvalidArgPastObjectLockRetainDate: {
|
||||
Description: "The retain until date must be in the future!",
|
||||
ArgumentName: "x-amz-object-lock-retain-until-date",
|
||||
},
|
||||
InvalidArgMissingObjectLockRetainDate: {
|
||||
Description: "x-amz-object-lock-retain-until-date and x-amz-object-lock-mode must both be supplied.",
|
||||
ArgumentName: "x-amz-object-lock-retain-until-date",
|
||||
},
|
||||
InvalidArgMissingObjectLockMode: {
|
||||
Description: "x-amz-object-lock-retain-until-date and x-amz-object-lock-mode must both be supplied.",
|
||||
ArgumentName: "x-amz-object-lock-mode",
|
||||
},
|
||||
InvalidArgObjectLockRetentionDays: {
|
||||
Description: "Default retention period must be a positive integer value.",
|
||||
ArgumentName: "Days",
|
||||
},
|
||||
InvalidArgObjectLockRetentionYears: {
|
||||
Description: "Default retention period must be a positive integer value.",
|
||||
ArgumentName: "Years",
|
||||
},
|
||||
InvalidArgLegalHoldStatus: {
|
||||
Description: "Legal Hold must be either of 'ON' or 'OFF'",
|
||||
ArgumentName: "x-amz-object-lock-legal-hold",
|
||||
},
|
||||
InvalidArgObjectLockMode: {
|
||||
Description: "Unknown wormMode directive.",
|
||||
ArgumentName: "x-amz-object-lock-mode",
|
||||
},
|
||||
InvalidArgMetadataDirective: {
|
||||
Description: "Unknown metadata directive.",
|
||||
ArgumentName: "x-amz-metadata-directive",
|
||||
},
|
||||
InvalidArgTaggingDirective: {
|
||||
Description: "Unknown tagging directive.",
|
||||
ArgumentName: "x-amz-tagging-directive",
|
||||
},
|
||||
InvalidArgVersionId: {
|
||||
Description: "Invalid version id specified",
|
||||
ArgumentName: "versionId",
|
||||
},
|
||||
InvalidArgChecksumPart: {
|
||||
Description: "Invalid Base64 or multiple checksums present in request",
|
||||
ArgumentName: "Checksum",
|
||||
},
|
||||
InvalidArgMissingUploadId: {
|
||||
Description: "This operation does not accept partNumber without uploadId",
|
||||
ArgumentName: "partNumber",
|
||||
},
|
||||
InvalidArgUploadIdMarker: {
|
||||
Description: "Invalid uploadId marker",
|
||||
ArgumentName: "upload-id-marker",
|
||||
},
|
||||
InvalidArgCannedAcl: {
|
||||
Description: "",
|
||||
ArgumentName: "x-amz-acl",
|
||||
},
|
||||
InvalidArgOnlyAws4HmacSha256: {
|
||||
Description: "Only AWS4-HMAC-SHA256 is supported",
|
||||
ArgumentName: "X-Amz-Algorithm",
|
||||
},
|
||||
InvalidArgDateHeader: {
|
||||
Description: "X-Amz-Date must be formated via ISO8601 Long format",
|
||||
ArgumentName: "X-Amz-Date",
|
||||
},
|
||||
}
|
||||
|
||||
// InvalidArgumentError is returned when a request argument is invalid.
|
||||
// Produces <ArgumentName> and <ArgumentValue> fields in the XML response.
|
||||
type InvalidArgumentError struct {
|
||||
Description string
|
||||
ArgumentName string
|
||||
ArgumentValue string
|
||||
}
|
||||
|
||||
func (e InvalidArgumentError) BaseError() APIError {
|
||||
return APIError{
|
||||
Code: "InvalidArgument",
|
||||
Description: e.Description,
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
}
|
||||
}
|
||||
|
||||
// InvalidArgumentError http status code is always 400
|
||||
func (e InvalidArgumentError) StatusCode() int { return http.StatusBadRequest }
|
||||
|
||||
func (e InvalidArgumentError) Error() string {
|
||||
var bytesBuffer bytes.Buffer
|
||||
bytesBuffer.WriteString(xml.Header)
|
||||
enc := xml.NewEncoder(&bytesBuffer)
|
||||
_ = enc.Encode(e)
|
||||
return bytesBuffer.String()
|
||||
}
|
||||
|
||||
func (e InvalidArgumentError) XMLBody(requestID, hostID string) []byte {
|
||||
return encodeResponse(struct {
|
||||
XMLName xml.Name `xml:"Error"`
|
||||
Code string
|
||||
Message string
|
||||
ArgumentName string `xml:"ArgumentName,omitempty"`
|
||||
ArgumentValue string `xml:"ArgumentValue,omitempty"`
|
||||
RequestId string `xml:"RequestId,omitempty"`
|
||||
HostId string `xml:"HostId,omitempty"`
|
||||
}{
|
||||
Code: "InvalidArgument",
|
||||
Message: e.Description,
|
||||
ArgumentName: e.ArgumentName,
|
||||
ArgumentValue: e.ArgumentValue,
|
||||
RequestId: requestID,
|
||||
HostId: hostID,
|
||||
})
|
||||
}
|
||||
|
||||
func GetInvalidArgumentErr(code InvalidArgErrorCode, value string) InvalidArgumentError {
|
||||
err := invalidArgErrResponses[code]
|
||||
err.ArgumentValue = value
|
||||
return err
|
||||
}
|
||||
|
||||
func GetInvalidArgMaxLimiter(name, value string) InvalidArgumentError {
|
||||
return InvalidArgumentError{
|
||||
ArgumentName: name,
|
||||
ArgumentValue: value,
|
||||
Description: fmt.Sprintf("Provided %s not an integer or within integer range", value),
|
||||
}
|
||||
}
|
||||
|
||||
func GetInvalidArgNegativeMaxLimiter(name, value string) InvalidArgumentError {
|
||||
return InvalidArgumentError{
|
||||
ArgumentName: name,
|
||||
ArgumentValue: value,
|
||||
Description: fmt.Sprintf("Argument %s must be an integer between 0 and 2147483647", value),
|
||||
}
|
||||
}
|
||||
|
||||
func GetInvalidArgExceedingRange(size int64) InvalidArgumentError {
|
||||
return InvalidArgumentError{
|
||||
ArgumentName: "x-amz-copy-source-range",
|
||||
ArgumentValue: fmt.Sprint(size),
|
||||
Description: fmt.Sprintf("Range specified is not valid for source object of size: %d", size),
|
||||
}
|
||||
}
|
||||
|
||||
func GetInvalidArgObjectOwnership(value string) InvalidArgumentError {
|
||||
return InvalidArgumentError{
|
||||
ArgumentName: "x-amz-object-ownership",
|
||||
// no ArgumentValue is returned for this error
|
||||
Description: fmt.Sprintf("Invalid x-amz-object-ownership header: %s", value),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2026 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package s3err
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// InvalidChunkSizeError is returned when a chunk size in a streaming upload is invalid.
|
||||
// Produces <Chunk> and <BadChunkSize> fields in the XML response.
|
||||
type InvalidChunkSizeError struct {
|
||||
APIError
|
||||
Chunk int
|
||||
BadChunkSize int64
|
||||
}
|
||||
|
||||
func (e InvalidChunkSizeError) XMLBody(requestID, hostID string) []byte {
|
||||
return encodeResponse(struct {
|
||||
XMLName xml.Name `xml:"Error"`
|
||||
Code string
|
||||
Message string
|
||||
Chunk int `xml:",omitempty"`
|
||||
BadChunkSize int64 `xml:",omitempty"`
|
||||
RequestID string `xml:"RequestId,omitempty"`
|
||||
HostID string `xml:"HostId,omitempty"`
|
||||
}{
|
||||
Code: e.Code,
|
||||
Message: e.Description,
|
||||
Chunk: e.Chunk,
|
||||
BadChunkSize: e.BadChunkSize,
|
||||
RequestID: requestID,
|
||||
HostID: hostID,
|
||||
})
|
||||
}
|
||||
|
||||
func (e InvalidChunkSizeError) Is(target error) bool {
|
||||
t, ok := target.(APIError)
|
||||
return ok && e.APIError == t
|
||||
}
|
||||
|
||||
func GetInvalidChunkSizeErr(chunk int, badChunkSize int64) InvalidChunkSizeError {
|
||||
return InvalidChunkSizeError{
|
||||
APIError: GetAPIError(ErrInvalidChunkSize),
|
||||
Chunk: chunk,
|
||||
BadChunkSize: badChunkSize,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2026 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package s3err
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// InvalidDigestError is returned when the Content-MD5 header value is invalid.
|
||||
// Produces a <Content-MD5> field in the XML response.
|
||||
type InvalidDigestError struct {
|
||||
APIError
|
||||
ContentMD5 string
|
||||
}
|
||||
|
||||
func (e InvalidDigestError) XMLBody(requestID, hostID string) []byte {
|
||||
return encodeResponse(struct {
|
||||
XMLName xml.Name `xml:"Error"`
|
||||
Code string
|
||||
Message string
|
||||
ContentMD5 string `xml:"Content-MD5"`
|
||||
RequestID string `xml:"RequestId,omitempty"`
|
||||
HostID string `xml:"HostId,omitempty"`
|
||||
}{
|
||||
Code: e.Code,
|
||||
Message: e.Description,
|
||||
ContentMD5: e.ContentMD5,
|
||||
RequestID: requestID,
|
||||
HostID: hostID,
|
||||
})
|
||||
}
|
||||
|
||||
func (e InvalidDigestError) Is(target error) bool {
|
||||
t, ok := target.(APIError)
|
||||
return ok && e.APIError == t
|
||||
}
|
||||
|
||||
func GetInvalidDigestErr(contentMD5 string) InvalidDigestError {
|
||||
return InvalidDigestError{
|
||||
APIError: GetAPIError(ErrInvalidDigest),
|
||||
ContentMD5: contentMD5,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2026 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package s3err
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// InvalidLocationConstraintError is returned when an invalid location constraint is provided.
|
||||
// Produces a <LocationConstraint> field in the XML response.
|
||||
type InvalidLocationConstraintError struct {
|
||||
APIError
|
||||
LocationConstraint string
|
||||
}
|
||||
|
||||
func (e InvalidLocationConstraintError) XMLBody(requestID, hostID string) []byte {
|
||||
return encodeResponse(struct {
|
||||
XMLName xml.Name `xml:"Error"`
|
||||
Code string
|
||||
Message string
|
||||
LocationConstraint string
|
||||
RequestID string `xml:"RequestId,omitempty"`
|
||||
HostID string `xml:"HostId,omitempty"`
|
||||
}{
|
||||
Code: e.Code,
|
||||
Message: e.Description,
|
||||
LocationConstraint: e.LocationConstraint,
|
||||
RequestID: requestID,
|
||||
HostID: hostID,
|
||||
})
|
||||
}
|
||||
|
||||
func (e InvalidLocationConstraintError) Is(target error) bool {
|
||||
t, ok := target.(APIError)
|
||||
return ok && e.APIError == t
|
||||
}
|
||||
|
||||
func GetInvalidLocationConstraintErr(constraint string) InvalidLocationConstraintError {
|
||||
return InvalidLocationConstraintError{
|
||||
APIError: GetAPIError(ErrInvalidLocationConstraint),
|
||||
LocationConstraint: constraint,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2026 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package s3err
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
// InvalidPartError is returned when one or more specified parts cannot be found.
|
||||
// Produces <UploadId>, <PartNumber>, and <ETag> fields in the XML response.
|
||||
type InvalidPartError struct {
|
||||
APIError
|
||||
UploadId string
|
||||
PartNumber int32
|
||||
ETag string
|
||||
}
|
||||
|
||||
func (e InvalidPartError) XMLBody(requestID, hostID string) []byte {
|
||||
return encodeResponse(struct {
|
||||
XMLName xml.Name `xml:"Error"`
|
||||
Code string
|
||||
Message string
|
||||
UploadId string `xml:",omitempty"`
|
||||
PartNumber int32 `xml:",omitempty"`
|
||||
ETag string `xml:",omitempty"`
|
||||
RequestID string `xml:"RequestId,omitempty"`
|
||||
HostID string `xml:"HostId,omitempty"`
|
||||
}{
|
||||
Code: e.Code,
|
||||
Message: e.Description,
|
||||
UploadId: e.UploadId,
|
||||
PartNumber: e.PartNumber,
|
||||
ETag: e.ETag,
|
||||
RequestID: requestID,
|
||||
HostID: hostID,
|
||||
})
|
||||
}
|
||||
|
||||
func (e InvalidPartError) Is(target error) bool {
|
||||
t, ok := target.(APIError)
|
||||
return ok && e.APIError == t
|
||||
}
|
||||
|
||||
func GetInvalidPartErr(uploadId string, partNumber int32, etag string) InvalidPartError {
|
||||
return InvalidPartError{
|
||||
APIError: GetAPIError(ErrInvalidPart),
|
||||
UploadId: uploadId,
|
||||
PartNumber: partNumber,
|
||||
ETag: etag,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright 2026 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package s3err
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// InvalidPartNumberRangeError is returned when the requested part number exceeds
|
||||
// the number of parts available for the object.
|
||||
// Produces <ActualPartCount> and <PartNumberRequested> fields in the XML response.
|
||||
type InvalidPartNumberRangeError struct {
|
||||
APIError
|
||||
ActualPartCount int32
|
||||
PartNumberRequested int32
|
||||
}
|
||||
|
||||
func (e InvalidPartNumberRangeError) XMLBody(requestID, hostID string) []byte {
|
||||
return encodeResponse(struct {
|
||||
XMLName xml.Name `xml:"Error"`
|
||||
Code string
|
||||
Message string
|
||||
ActualPartCount int32
|
||||
PartNumberRequested int32
|
||||
RequestID string `xml:"RequestId,omitempty"`
|
||||
HostID string `xml:"HostId,omitempty"`
|
||||
}{
|
||||
Code: e.Code,
|
||||
Message: e.Description,
|
||||
ActualPartCount: e.ActualPartCount,
|
||||
PartNumberRequested: e.PartNumberRequested,
|
||||
RequestID: requestID,
|
||||
HostID: hostID,
|
||||
})
|
||||
}
|
||||
|
||||
func (e InvalidPartNumberRangeError) Is(target error) bool {
|
||||
t, ok := target.(APIError)
|
||||
return ok && e.APIError == t
|
||||
}
|
||||
|
||||
func GetInvalidPartNumberRangeErr(actualPartCount, partNumberRequested int32) InvalidPartNumberRangeError {
|
||||
return InvalidPartNumberRangeError{
|
||||
APIError: GetAPIError(ErrInvalidPartNumberRange),
|
||||
ActualPartCount: actualPartCount,
|
||||
PartNumberRequested: partNumberRequested,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2026 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package s3err
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// InvalidRangeError is returned when the requested byte range cannot be satisfied.
|
||||
// Produces <RangeRequested> and <ActualObjectSize> fields in the XML response.
|
||||
type InvalidRangeError struct {
|
||||
APIError
|
||||
RangeRequested string
|
||||
ActualObjectSize int64
|
||||
}
|
||||
|
||||
func (e InvalidRangeError) XMLBody(requestID, hostID string) []byte {
|
||||
return encodeResponse(struct {
|
||||
XMLName xml.Name `xml:"Error"`
|
||||
Code string
|
||||
Message string
|
||||
RangeRequested string `xml:",omitempty"`
|
||||
ActualObjectSize int64 `xml:",omitempty"`
|
||||
RequestID string `xml:"RequestId,omitempty"`
|
||||
HostID string `xml:"HostId,omitempty"`
|
||||
}{
|
||||
Code: e.Code,
|
||||
Message: e.Description,
|
||||
RangeRequested: e.RangeRequested,
|
||||
ActualObjectSize: e.ActualObjectSize,
|
||||
RequestID: requestID,
|
||||
HostID: hostID,
|
||||
})
|
||||
}
|
||||
|
||||
func (e InvalidRangeError) Is(target error) bool {
|
||||
t, ok := target.(APIError)
|
||||
return ok && e.APIError == t
|
||||
}
|
||||
|
||||
func GetInvalidRangeErr(rangeRequested string, actualObjectSize int64) InvalidRangeError {
|
||||
return InvalidRangeError{
|
||||
APIError: GetAPIError(ErrInvalidRange),
|
||||
RangeRequested: rangeRequested,
|
||||
ActualObjectSize: actualObjectSize,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright 2026 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package s3err
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// InvalidTagError is returned when a tag key or value is invalid.
|
||||
// Produces <TagKey> and optionally <TagValue> fields in the XML response.
|
||||
type InvalidTagError struct {
|
||||
APIError
|
||||
TagKey string
|
||||
TagValue string
|
||||
}
|
||||
|
||||
func (e InvalidTagError) XMLBody(requestID, hostID string) []byte {
|
||||
return encodeResponse(struct {
|
||||
XMLName xml.Name `xml:"Error"`
|
||||
Code string
|
||||
Message string
|
||||
TagKey string `xml:",omitempty"`
|
||||
TagValue string `xml:",omitempty"`
|
||||
RequestID string `xml:"RequestId,omitempty"`
|
||||
HostID string `xml:"HostId,omitempty"`
|
||||
}{
|
||||
Code: e.Code,
|
||||
Message: e.Description,
|
||||
TagKey: e.TagKey,
|
||||
TagValue: e.TagValue,
|
||||
RequestID: requestID,
|
||||
HostID: hostID,
|
||||
})
|
||||
}
|
||||
|
||||
func (e InvalidTagError) Is(target error) bool {
|
||||
t, ok := target.(APIError)
|
||||
return ok && e.APIError == t
|
||||
}
|
||||
|
||||
// GetInvalidTagErr creates an InvalidTagError for the given error code, tag key, and optional tag value.
|
||||
// code should be ErrInvalidTagKey or ErrInvalidTagValue.
|
||||
func GetInvalidTagErr(code ErrorCode, tagKey, tagValue string) InvalidTagError {
|
||||
return InvalidTagError{
|
||||
APIError: GetAPIError(code),
|
||||
TagKey: tagKey,
|
||||
TagValue: tagValue,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2026 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package s3err
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// KeyTooLongError is returned when the object key exceeds the maximum allowed length.
|
||||
// Produces <Size> and <MaxSizeAllowed> fields in the XML response.
|
||||
type KeyTooLongError struct {
|
||||
APIError
|
||||
Size int64
|
||||
MaxSizeAllowed int64
|
||||
}
|
||||
|
||||
func (e KeyTooLongError) XMLBody(requestID, hostID string) []byte {
|
||||
return encodeResponse(struct {
|
||||
XMLName xml.Name `xml:"Error"`
|
||||
Code string
|
||||
Message string
|
||||
Size int64 `xml:",omitempty"`
|
||||
MaxSizeAllowed int64 `xml:",omitempty"`
|
||||
RequestID string `xml:"RequestId,omitempty"`
|
||||
HostID string `xml:"HostId,omitempty"`
|
||||
}{
|
||||
Code: e.Code,
|
||||
Message: e.Description,
|
||||
Size: e.Size,
|
||||
MaxSizeAllowed: e.MaxSizeAllowed,
|
||||
RequestID: requestID,
|
||||
HostID: hostID,
|
||||
})
|
||||
}
|
||||
|
||||
func (e KeyTooLongError) Is(target error) bool {
|
||||
t, ok := target.(APIError)
|
||||
return ok && e.APIError == t
|
||||
}
|
||||
|
||||
func GetKeyTooLongErr(size, maxSizeAllowed int64) KeyTooLongError {
|
||||
return KeyTooLongError{
|
||||
APIError: GetAPIError(ErrKeyTooLong),
|
||||
Size: size,
|
||||
MaxSizeAllowed: maxSizeAllowed,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2026 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package s3err
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// MetadataTooLargeError is returned when the request metadata headers exceed the allowed size.
|
||||
// Produces <Size> and <MaxSizeAllowed> fields in the XML response.
|
||||
type MetadataTooLargeError struct {
|
||||
APIError
|
||||
Size int
|
||||
MaxSizeAllowed int
|
||||
}
|
||||
|
||||
func (e MetadataTooLargeError) XMLBody(requestID, hostID string) []byte {
|
||||
return encodeResponse(struct {
|
||||
XMLName xml.Name `xml:"Error"`
|
||||
Code string
|
||||
Message string
|
||||
Size int `xml:",omitempty"`
|
||||
MaxSizeAllowed int `xml:",omitempty"`
|
||||
RequestID string `xml:"RequestId,omitempty"`
|
||||
HostID string `xml:"HostId,omitempty"`
|
||||
}{
|
||||
Code: e.Code,
|
||||
Message: e.Description,
|
||||
Size: e.Size,
|
||||
MaxSizeAllowed: e.MaxSizeAllowed,
|
||||
RequestID: requestID,
|
||||
HostID: hostID,
|
||||
})
|
||||
}
|
||||
|
||||
func (e MetadataTooLargeError) Is(target error) bool {
|
||||
t, ok := target.(APIError)
|
||||
return ok && e.APIError == t
|
||||
}
|
||||
|
||||
func GetMetadataTooLargeErr(size, maxSizeAllowed int) MetadataTooLargeError {
|
||||
return MetadataTooLargeError{
|
||||
APIError: GetAPIError(ErrMetadataTooLarge),
|
||||
Size: size,
|
||||
MaxSizeAllowed: maxSizeAllowed,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright 2026 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package s3err
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MethodNotAllowedError is returned when an HTTP method is not permitted on a resource.
|
||||
// Produces <Method> and <ResourceType> fields in the XML response.
|
||||
// AllowedMethods is used to populate the HTTP Allow: response header.
|
||||
type MethodNotAllowedError struct {
|
||||
APIError
|
||||
Method string
|
||||
ResourceType ResourceType
|
||||
AllowedMethods []string
|
||||
}
|
||||
|
||||
func (mna *MethodNotAllowedError) AllowedMethodsString() string {
|
||||
return strings.Join(mna.AllowedMethods, ", ")
|
||||
}
|
||||
|
||||
func (e MethodNotAllowedError) XMLBody(requestID, hostID string) []byte {
|
||||
return encodeResponse(struct {
|
||||
XMLName xml.Name `xml:"Error"`
|
||||
Code string
|
||||
Message string
|
||||
Method string
|
||||
ResourceType ResourceType
|
||||
RequestID string `xml:"RequestId,omitempty"`
|
||||
HostID string `xml:"HostId,omitempty"`
|
||||
}{
|
||||
Code: e.Code,
|
||||
Message: e.Description,
|
||||
Method: e.Method,
|
||||
ResourceType: e.ResourceType,
|
||||
RequestID: requestID,
|
||||
HostID: hostID,
|
||||
})
|
||||
}
|
||||
|
||||
func (e MethodNotAllowedError) Is(target error) bool {
|
||||
t, ok := target.(APIError)
|
||||
return ok && e.APIError == t
|
||||
}
|
||||
|
||||
func GetMethodNotAllowedErr(method string, resourceType ResourceType, allowed []string) MethodNotAllowedError {
|
||||
return MethodNotAllowedError{
|
||||
APIError: GetAPIError(ErrMethodNotAllowed),
|
||||
Method: method,
|
||||
ResourceType: resourceType,
|
||||
AllowedMethods: allowed,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2026 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package s3err
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// NoSuchUploadError is returned when the specified multipart upload does not exist.
|
||||
// Produces an <UploadId> field in the XML response.
|
||||
type NoSuchUploadError struct {
|
||||
APIError
|
||||
UploadId string
|
||||
}
|
||||
|
||||
func (e NoSuchUploadError) XMLBody(requestID, hostID string) []byte {
|
||||
return encodeResponse(struct {
|
||||
XMLName xml.Name `xml:"Error"`
|
||||
Code string
|
||||
Message string
|
||||
UploadId string `xml:",omitempty"`
|
||||
RequestID string `xml:"RequestId,omitempty"`
|
||||
HostID string `xml:"HostId,omitempty"`
|
||||
}{
|
||||
Code: e.Code,
|
||||
Message: e.Description,
|
||||
UploadId: e.UploadId,
|
||||
RequestID: requestID,
|
||||
HostID: hostID,
|
||||
})
|
||||
}
|
||||
|
||||
func (e NoSuchUploadError) Is(target error) bool {
|
||||
t, ok := target.(APIError)
|
||||
return ok && e.APIError == t
|
||||
}
|
||||
|
||||
func GetNoSuchUploadErr(uploadId string) NoSuchUploadError {
|
||||
return NoSuchUploadError{
|
||||
APIError: GetAPIError(ErrNoSuchUpload),
|
||||
UploadId: uploadId,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2026 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package s3err
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// NoSuchVersionError is returned when the specified version does not exist.
|
||||
// Produces <Key> and <VersionId> fields in the XML response.
|
||||
type NoSuchVersionError struct {
|
||||
APIError
|
||||
Key string
|
||||
VersionId string
|
||||
}
|
||||
|
||||
func (e NoSuchVersionError) XMLBody(requestID, hostID string) []byte {
|
||||
return encodeResponse(struct {
|
||||
XMLName xml.Name `xml:"Error"`
|
||||
Code string
|
||||
Message string
|
||||
Key string `xml:",omitempty"`
|
||||
VersionId string `xml:",omitempty"`
|
||||
RequestID string `xml:"RequestId,omitempty"`
|
||||
HostID string `xml:"HostId,omitempty"`
|
||||
}{
|
||||
Code: e.Code,
|
||||
Message: e.Description,
|
||||
Key: e.Key,
|
||||
VersionId: e.VersionId,
|
||||
RequestID: requestID,
|
||||
HostID: hostID,
|
||||
})
|
||||
}
|
||||
|
||||
func (e NoSuchVersionError) Is(target error) bool {
|
||||
t, ok := target.(APIError)
|
||||
return ok && e.APIError == t
|
||||
}
|
||||
|
||||
func GetNoSuchVersionErr(key, versionId string) NoSuchVersionError {
|
||||
return NoSuchVersionError{
|
||||
APIError: GetAPIError(ErrNoSuchVersion),
|
||||
Key: key,
|
||||
VersionId: versionId,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2026 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package s3err
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
type NmpAdditionalMessage string
|
||||
|
||||
const (
|
||||
NmpAdditionalMessageIfNoneMatch NmpAdditionalMessage = "We don\\'t accept the provided value of If-None-Match header for this API"
|
||||
NmpAdditionalMessageMultipleCondHeaders NmpAdditionalMessage = "Multiple conditional request headers present in the request"
|
||||
)
|
||||
|
||||
// NotImplementedError is returned when a header implies unsupported functionality.
|
||||
// Produces <Header> and <additionalMessage> fields in the XML response.
|
||||
type NotImplementedError struct {
|
||||
APIError
|
||||
Header string
|
||||
AdditionalMessage NmpAdditionalMessage
|
||||
}
|
||||
|
||||
func (e NotImplementedError) XMLBody(requestID, hostID string) []byte {
|
||||
return encodeResponse(struct {
|
||||
XMLName xml.Name `xml:"Error"`
|
||||
Code string
|
||||
Message string
|
||||
Header string `xml:",omitempty"`
|
||||
AdditionalMessage NmpAdditionalMessage `xml:"additionalMessage,omitempty"`
|
||||
RequestID string `xml:"RequestId,omitempty"`
|
||||
HostID string `xml:"HostId,omitempty"`
|
||||
}{
|
||||
Code: e.Code,
|
||||
Message: e.Description,
|
||||
Header: e.Header,
|
||||
AdditionalMessage: e.AdditionalMessage,
|
||||
RequestID: requestID,
|
||||
HostID: hostID,
|
||||
})
|
||||
}
|
||||
|
||||
func (e NotImplementedError) Is(target error) bool {
|
||||
t, ok := target.(APIError)
|
||||
return ok && e.APIError == t
|
||||
}
|
||||
|
||||
func GetNotImplementedErr(header string, additionalMessage NmpAdditionalMessage) NotImplementedError {
|
||||
return NotImplementedError{
|
||||
APIError: GetAPIError(ErrNotImplemented),
|
||||
Header: header,
|
||||
AdditionalMessage: additionalMessage,
|
||||
}
|
||||
}
|
||||
+58
-33
@@ -20,43 +20,68 @@ import (
|
||||
)
|
||||
|
||||
// Factory for building s3 object POST authentication errors.
|
||||
func invalidPOSTObjectAuthErr(format string, args ...any) APIError {
|
||||
return APIError{
|
||||
Code: "InvalidArgument",
|
||||
Description: fmt.Sprintf(format, args...),
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
func invalidPOSTObjectAuthErr(argName, argValue, format string, args ...any) S3Error {
|
||||
return InvalidArgumentError{
|
||||
ArgumentName: argName,
|
||||
ArgumentValue: argValue,
|
||||
Description: fmt.Sprintf(format, args...),
|
||||
}
|
||||
}
|
||||
|
||||
type invalidPostAuthErr struct{}
|
||||
|
||||
func (invalidPostAuthErr) InvalidDateFormat(s string) APIError {
|
||||
func (invalidPostAuthErr) InvalidDateFormat(creds, date string) S3Error {
|
||||
return invalidPOSTObjectAuthErr(
|
||||
"x-amz-credential",
|
||||
creds,
|
||||
"incorrect date format %q. This date in the credential must be in the format \"yyyyMMdd\".",
|
||||
s,
|
||||
date,
|
||||
)
|
||||
}
|
||||
|
||||
func (invalidPostAuthErr) MalformedCredential() APIError {
|
||||
func (invalidPostAuthErr) MalformedCredential(creds string) S3Error {
|
||||
return invalidPOSTObjectAuthErr(
|
||||
"x-amz-credential",
|
||||
creds,
|
||||
"the Credential is mal-formed; expecting \"<YOUR-AKID>/YYYYMMDD/REGION/SERVICE/aws4_request\".",
|
||||
)
|
||||
}
|
||||
|
||||
func (invalidPostAuthErr) IncorrectTerminal(s string) APIError {
|
||||
return invalidPOSTObjectAuthErr("incorrect terminal %q. This endpoint uses \"aws4_request\".", s)
|
||||
func (invalidPostAuthErr) IncorrectTerminal(creds, terminal string) S3Error {
|
||||
return invalidPOSTObjectAuthErr(
|
||||
"x-amz-credential",
|
||||
creds,
|
||||
"incorrect terminal %q. This endpoint uses \"aws4_request\".",
|
||||
terminal,
|
||||
)
|
||||
}
|
||||
|
||||
func (invalidPostAuthErr) IncorrectRegion(expected, actual string) APIError {
|
||||
return invalidPOSTObjectAuthErr("the region %q is wrong; expecting %q", actual, expected)
|
||||
func (invalidPostAuthErr) IncorrectRegion(creds, expected, actual string) S3Error {
|
||||
return invalidPOSTObjectAuthErr(
|
||||
"x-amz-credential",
|
||||
creds,
|
||||
"the region %q is wrong; expecting %q",
|
||||
actual,
|
||||
expected,
|
||||
)
|
||||
}
|
||||
|
||||
func (invalidPostAuthErr) IncorrectService(s string) APIError {
|
||||
return invalidPOSTObjectAuthErr("incorrect service %q. This endpoint belongs to \"s3\".", s)
|
||||
func (invalidPostAuthErr) IncorrectService(creds, service string) S3Error {
|
||||
return invalidPOSTObjectAuthErr(
|
||||
"x-amz-credential",
|
||||
creds,
|
||||
"incorrect service %q. This endpoint belongs to \"s3\".",
|
||||
service,
|
||||
)
|
||||
}
|
||||
|
||||
func (invalidPostAuthErr) MissingField(field string) APIError {
|
||||
return invalidPOSTObjectAuthErr("Bucket POST must contain a field named '%s'. If it is specified, please check the order of the fields.", field)
|
||||
func (invalidPostAuthErr) MissingField(field string) S3Error {
|
||||
return invalidPOSTObjectAuthErr(
|
||||
field,
|
||||
"",
|
||||
"Bucket POST must contain a field named '%s'. If it is specified, please check the order of the fields.",
|
||||
field,
|
||||
)
|
||||
}
|
||||
|
||||
var PostAuth invalidPostAuthErr
|
||||
@@ -80,71 +105,71 @@ func invalidAccordingToPolicyErr(format string, args ...any) APIError {
|
||||
|
||||
type invalidPolicyDocument struct{}
|
||||
|
||||
func (invalidPolicyDocument) EmptyPolicy() APIError {
|
||||
func (invalidPolicyDocument) EmptyPolicy() S3Error {
|
||||
return invalidPolicyDocumentErr("Invalid Policy: Expecting '{' but found End-of-Input")
|
||||
}
|
||||
|
||||
func (invalidPolicyDocument) InvalidBase64Encoding() APIError {
|
||||
func (invalidPolicyDocument) InvalidBase64Encoding() S3Error {
|
||||
return invalidPolicyDocumentErr("Invalid Policy: invalid Base64 encoding.")
|
||||
}
|
||||
|
||||
func (invalidPolicyDocument) InvalidJSON() APIError {
|
||||
func (invalidPolicyDocument) InvalidJSON() S3Error {
|
||||
return invalidPolicyDocumentErr("Invalid Policy: Invalid JSON.")
|
||||
}
|
||||
|
||||
func (invalidPolicyDocument) UnexpectedField(field string) APIError {
|
||||
func (invalidPolicyDocument) UnexpectedField(field string) S3Error {
|
||||
return invalidPolicyDocumentErr("Invalid Policy: Unexpected: %q", field)
|
||||
}
|
||||
|
||||
func (invalidPolicyDocument) MissingExpiration() APIError {
|
||||
func (invalidPolicyDocument) MissingExpiration() S3Error {
|
||||
return invalidPolicyDocumentErr("Invalid Policy: Policy missing expiration.")
|
||||
}
|
||||
|
||||
func (invalidPolicyDocument) InvalidExpiration(exp string) APIError {
|
||||
func (invalidPolicyDocument) InvalidExpiration(exp string) S3Error {
|
||||
return invalidPolicyDocumentErr("Invalid Policy: Invalid 'expiration' value: '%s'", exp)
|
||||
}
|
||||
|
||||
func (invalidPolicyDocument) InvalidConditions() APIError {
|
||||
func (invalidPolicyDocument) InvalidConditions() S3Error {
|
||||
return invalidPolicyDocumentErr("Invalid Policy: Invalid 'conditions' value: must be a List.")
|
||||
}
|
||||
|
||||
func (invalidPolicyDocument) InvalidCondition() APIError {
|
||||
func (invalidPolicyDocument) InvalidCondition() S3Error {
|
||||
return invalidPolicyDocumentErr("Invalid Policy: Invalid condition test: must be a List or Object.")
|
||||
}
|
||||
|
||||
func (invalidPolicyDocument) MissingConditionOperationIdentifier() APIError {
|
||||
func (invalidPolicyDocument) MissingConditionOperationIdentifier() S3Error {
|
||||
return invalidPolicyDocumentErr("Invalid Policy: Invalid Condition: missing operation identifier.")
|
||||
}
|
||||
|
||||
func (invalidPolicyDocument) UnknownConditionOperation(op string) APIError {
|
||||
func (invalidPolicyDocument) UnknownConditionOperation(op string) S3Error {
|
||||
return invalidPolicyDocumentErr("Invalid Policy: Invalid Condition: unknown operation '%s'.", op)
|
||||
}
|
||||
|
||||
func (invalidPolicyDocument) IncorrectConditionArgumentsNumber(op string) APIError {
|
||||
func (invalidPolicyDocument) IncorrectConditionArgumentsNumber(op string) S3Error {
|
||||
return invalidPolicyDocumentErr("Invalid Policy: Invalid %s: wrong number of arguments.", op)
|
||||
}
|
||||
|
||||
func (invalidPolicyDocument) MissingConditions() APIError {
|
||||
func (invalidPolicyDocument) MissingConditions() S3Error {
|
||||
return invalidPolicyDocumentErr("Invalid Policy: Policy missing conditions.")
|
||||
}
|
||||
|
||||
func (invalidPolicyDocument) OnePropSimpleCondition() APIError {
|
||||
func (invalidPolicyDocument) OnePropSimpleCondition() S3Error {
|
||||
return invalidPolicyDocumentErr("Invalid Policy: Invalid Simple-Condition: Simple-Conditions must have exactly one property specified.")
|
||||
}
|
||||
|
||||
func (invalidPolicyDocument) InvalidSimpleCondition() APIError {
|
||||
func (invalidPolicyDocument) InvalidSimpleCondition() S3Error {
|
||||
return invalidPolicyDocumentErr("Invalid Policy: Invalid Simple-Condition: value must be a string.")
|
||||
}
|
||||
|
||||
func (invalidPolicyDocument) ConditionFailed(condition string) APIError {
|
||||
func (invalidPolicyDocument) ConditionFailed(condition string) S3Error {
|
||||
return invalidAccordingToPolicyErr("Invalid according to Policy: Policy Condition failed: %s", condition)
|
||||
}
|
||||
|
||||
func (invalidPolicyDocument) ExtraInputField(field string) APIError {
|
||||
func (invalidPolicyDocument) ExtraInputField(field string) S3Error {
|
||||
return invalidAccordingToPolicyErr("Invalid according to Policy: Extra input fields: %s", field)
|
||||
}
|
||||
|
||||
func (invalidPolicyDocument) PolicyExpired() APIError {
|
||||
func (invalidPolicyDocument) PolicyExpired() S3Error {
|
||||
return invalidAccordingToPolicyErr("Invalid according to Policy: Policy expired.")
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright 2026 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package s3err
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
type Condition string
|
||||
|
||||
const (
|
||||
ConditionIfMatch Condition = "If-Match"
|
||||
ConditionIfNoneMatch Condition = "If-None-Match"
|
||||
ConditionIfUnmodifiedSince Condition = "If-Unmodified-Since"
|
||||
ConditionIfMatchSize Condition = "If-Match-Size"
|
||||
ConditionIfMatchInitiatedTime Condition = "If-Match-Initiated-Time"
|
||||
ConditionIfMatchLastModTime Condition = "If-Match-Last-Mod-Time"
|
||||
ConditionPostBucket Condition = "Bucket POST must be of the enclosure-type multipart/form-data"
|
||||
)
|
||||
|
||||
// PreconditionFailedError is returned when a conditional request precondition is not met.
|
||||
// Produces a <Condition> field in the XML response.
|
||||
type PreconditionFailedError struct {
|
||||
APIError
|
||||
Condition Condition
|
||||
}
|
||||
|
||||
func (e PreconditionFailedError) XMLBody(requestID, hostID string) []byte {
|
||||
return encodeResponse(struct {
|
||||
XMLName xml.Name `xml:"Error"`
|
||||
Code string
|
||||
Message string
|
||||
Condition Condition `xml:",omitempty"`
|
||||
RequestID string `xml:"RequestId,omitempty"`
|
||||
HostID string `xml:"HostId,omitempty"`
|
||||
}{
|
||||
Code: e.Code,
|
||||
Message: e.Description,
|
||||
Condition: e.Condition,
|
||||
RequestID: requestID,
|
||||
HostID: hostID,
|
||||
})
|
||||
}
|
||||
|
||||
func (e PreconditionFailedError) Is(target error) bool {
|
||||
t, ok := target.(APIError)
|
||||
return ok && e.APIError == t
|
||||
}
|
||||
|
||||
func GetPreconditionFailedErr(condition Condition) PreconditionFailedError {
|
||||
return PreconditionFailedError{
|
||||
APIError: GetAPIError(ErrPreconditionFailed),
|
||||
Condition: condition,
|
||||
}
|
||||
}
|
||||
+15
-15
@@ -20,7 +20,7 @@ import (
|
||||
)
|
||||
|
||||
// Factory for building AuthorizationQueryParametersError errors.
|
||||
func authQueryParamError(format string, args ...any) APIError {
|
||||
func authQueryParamError(format string, args ...any) S3Error {
|
||||
return APIError{
|
||||
Code: "AuthorizationQueryParametersError",
|
||||
Description: fmt.Sprintf(format, args...),
|
||||
@@ -30,60 +30,60 @@ func authQueryParamError(format string, args ...any) APIError {
|
||||
|
||||
type queryAuthErrors struct{}
|
||||
|
||||
func (queryAuthErrors) UnsupportedAlgorithm() APIError {
|
||||
func (queryAuthErrors) UnsupportedAlgorithm() S3Error {
|
||||
return authQueryParamError(`X-Amz-Algorithm only supports "AWS4-HMAC-SHA256 and AWS4-ECDSA-P256-SHA256"`)
|
||||
}
|
||||
|
||||
func (queryAuthErrors) MalformedCredential() APIError {
|
||||
func (queryAuthErrors) MalformedCredential(_ string) S3Error {
|
||||
return authQueryParamError(`Error parsing the X-Amz-Credential parameter; the Credential is mal-formed; expecting "<YOUR-AKID>/YYYYMMDD/REGION/SERVICE/aws4_request".`)
|
||||
}
|
||||
|
||||
func (queryAuthErrors) IncorrectService(s string) APIError {
|
||||
func (queryAuthErrors) IncorrectService(_, s string) S3Error {
|
||||
return authQueryParamError(`Error parsing the X-Amz-Credential parameter; incorrect service %q. This endpoint belongs to "s3".`, s)
|
||||
}
|
||||
|
||||
func (queryAuthErrors) IncorrectRegion(expected, actual string) APIError {
|
||||
func (queryAuthErrors) IncorrectRegion(expected, actual string) S3Error {
|
||||
return authQueryParamError(`Error parsing the X-Amz-Credential parameter; the region %q is wrong; expecting %q`, actual, expected)
|
||||
}
|
||||
|
||||
func (queryAuthErrors) IncorrectTerminal(s string) APIError {
|
||||
func (queryAuthErrors) IncorrectTerminal(_, s string) S3Error {
|
||||
return authQueryParamError(`Error parsing the X-Amz-Credential parameter; incorrect terminal %q. This endpoint uses "aws4_request".`, s)
|
||||
}
|
||||
|
||||
func (queryAuthErrors) InvalidDateFormat(s string) APIError {
|
||||
func (queryAuthErrors) InvalidDateFormat(_, s string) S3Error {
|
||||
return authQueryParamError(`Error parsing the X-Amz-Credential parameter; incorrect date format %q. This date in the credential must be in the format "yyyyMMdd".`, s)
|
||||
}
|
||||
|
||||
func (queryAuthErrors) DateMismatch(expected, actual string) APIError {
|
||||
func (queryAuthErrors) DateMismatch(expected, actual string) S3Error {
|
||||
return authQueryParamError(`Invalid credential date %q. This date is not the same as X-Amz-Date: %q.`, expected, actual)
|
||||
}
|
||||
|
||||
func (queryAuthErrors) ExpiresTooLarge() APIError {
|
||||
func (queryAuthErrors) ExpiresTooLarge() S3Error {
|
||||
return authQueryParamError("X-Amz-Expires must be less than a week (in seconds); that is, the given X-Amz-Expires must be less than 604800 seconds")
|
||||
}
|
||||
|
||||
func (queryAuthErrors) ExpiresNegative() APIError {
|
||||
func (queryAuthErrors) ExpiresNegative() S3Error {
|
||||
return authQueryParamError("X-Amz-Expires must be non-negative")
|
||||
}
|
||||
|
||||
func (queryAuthErrors) ExpiresNumber() APIError {
|
||||
func (queryAuthErrors) ExpiresNumber() S3Error {
|
||||
return authQueryParamError("X-Amz-Expires should be a number")
|
||||
}
|
||||
|
||||
func (queryAuthErrors) MissingRequiredParams() APIError {
|
||||
func (queryAuthErrors) MissingRequiredParams() S3Error {
|
||||
return authQueryParamError("Query-string authentication version 4 requires the X-Amz-Algorithm, X-Amz-Credential, X-Amz-Signature, X-Amz-Date, X-Amz-SignedHeaders, and X-Amz-Expires parameters.")
|
||||
}
|
||||
|
||||
func (queryAuthErrors) InvalidXAmzDateFormat() APIError {
|
||||
func (queryAuthErrors) InvalidXAmzDateFormat() S3Error {
|
||||
return authQueryParamError(`X-Amz-Date must be in the ISO8601 Long Format "yyyyMMdd'T'HHmmss'Z'"`)
|
||||
}
|
||||
|
||||
// a custom non-AWS error
|
||||
func (queryAuthErrors) OnlyHMACSupported() APIError {
|
||||
func (queryAuthErrors) OnlyHMACSupported() S3Error {
|
||||
return authQueryParamError("X-Amz-Algorithm only supports \"AWS4-HMAC-SHA256\"")
|
||||
}
|
||||
|
||||
func (queryAuthErrors) SecurityTokenNotSupported() APIError {
|
||||
func (queryAuthErrors) SecurityTokenNotSupported() S3Error {
|
||||
return authQueryParamError("Authorization with X-Amz-Security-Token is not supported")
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2026 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package s3err
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
// RequestTimeTooSkewedError is returned when the request timestamp is too far from server time.
|
||||
// Produces <RequestTime>, <ServerTime>, and <MaxAllowedSkewMilliseconds> fields.
|
||||
type RequestTimeTooSkewedError struct {
|
||||
APIError
|
||||
RequestTime string
|
||||
ServerTime string
|
||||
MaxAllowedSkewMilliseconds int
|
||||
}
|
||||
|
||||
func (e RequestTimeTooSkewedError) XMLBody(requestID, hostID string) []byte {
|
||||
return encodeResponse(struct {
|
||||
XMLName xml.Name `xml:"Error"`
|
||||
Code string
|
||||
Message string
|
||||
RequestTime string
|
||||
ServerTime string
|
||||
MaxAllowedSkewMilliseconds int
|
||||
RequestID string `xml:"RequestId,omitempty"`
|
||||
HostID string `xml:"HostId,omitempty"`
|
||||
}{
|
||||
Code: e.Code,
|
||||
Message: e.Description,
|
||||
RequestTime: e.RequestTime,
|
||||
ServerTime: e.ServerTime,
|
||||
MaxAllowedSkewMilliseconds: e.MaxAllowedSkewMilliseconds,
|
||||
RequestID: requestID,
|
||||
HostID: hostID,
|
||||
})
|
||||
}
|
||||
|
||||
func (e RequestTimeTooSkewedError) Is(target error) bool {
|
||||
t, ok := target.(APIError)
|
||||
return ok && e.APIError == t
|
||||
}
|
||||
|
||||
func GetRequestTimeTooSkewedErr(requestTime, serverTime string, maxAllowedMilliseconds int) RequestTimeTooSkewedError {
|
||||
return RequestTimeTooSkewedError{
|
||||
APIError: GetAPIError(ErrRequestTimeTooSkewed),
|
||||
RequestTime: requestTime,
|
||||
ServerTime: serverTime,
|
||||
MaxAllowedSkewMilliseconds: maxAllowedMilliseconds,
|
||||
}
|
||||
}
|
||||
+38
-267
@@ -24,6 +24,15 @@ import (
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||
)
|
||||
|
||||
// S3Error is the interface implemented by all S3 error types.
|
||||
// It allows centralized error handling while supporting per-error-type XML fields.
|
||||
type S3Error interface {
|
||||
error
|
||||
StatusCode() int
|
||||
BaseError() APIError
|
||||
XMLBody(requestID, hostID string) []byte
|
||||
}
|
||||
|
||||
// APIError structure
|
||||
type APIError struct {
|
||||
Code string
|
||||
@@ -31,17 +40,8 @@ type APIError struct {
|
||||
HTTPStatusCode int
|
||||
}
|
||||
|
||||
// APIErrorResponse - error response format
|
||||
type APIErrorResponse struct {
|
||||
XMLName xml.Name `xml:"Error" json:"-"`
|
||||
Code string
|
||||
Message string
|
||||
Key string `xml:"Key,omitempty" json:"Key,omitempty"`
|
||||
BucketName string `xml:"BucketName,omitempty" json:"BucketName,omitempty"`
|
||||
Resource string
|
||||
Region string `xml:"Region,omitempty" json:"Region,omitempty"`
|
||||
RequestID string `xml:"RequestId" json:"RequestId"`
|
||||
HostID string `xml:"HostId" json:"HostId"`
|
||||
func (e APIError) BaseError() APIError {
|
||||
return e
|
||||
}
|
||||
|
||||
func (A APIError) Error() string {
|
||||
@@ -52,6 +52,23 @@ func (A APIError) Error() string {
|
||||
return bytesBuffer.String()
|
||||
}
|
||||
|
||||
func (e APIError) StatusCode() int { return e.HTTPStatusCode }
|
||||
|
||||
func (e APIError) XMLBody(requestID, hostID string) []byte {
|
||||
return encodeResponse(struct {
|
||||
XMLName xml.Name `xml:"Error"`
|
||||
Code string
|
||||
Message string
|
||||
RequestID string `xml:"RequestId,omitempty"`
|
||||
HostID string `xml:"HostId,omitempty"`
|
||||
}{
|
||||
Code: e.Code,
|
||||
Message: e.Description,
|
||||
RequestID: requestID,
|
||||
HostID: hostID,
|
||||
})
|
||||
}
|
||||
|
||||
// ErrorCode type of error status.
|
||||
type ErrorCode int
|
||||
|
||||
@@ -76,48 +93,28 @@ const (
|
||||
ErrInvalidBucketName
|
||||
ErrInvalidDigest
|
||||
ErrBadDigest
|
||||
ErrInvalidMaxBuckets
|
||||
ErrNegativeMaxKeys
|
||||
ErrInvalidObjectAttributes
|
||||
ErrInvalidPart
|
||||
ErrInvalidPartNumber
|
||||
ErrInvalidPartNumberRange
|
||||
ErrRangeAndPartNumber
|
||||
ErrInvalidPartOrder
|
||||
ErrInvalidCompleteMpPartNumber
|
||||
ErrInternalError
|
||||
ErrNonEmptyRequestBody
|
||||
ErrIncompleteBody
|
||||
ErrInvalidCopyDest
|
||||
ErrInvalidCopySourceRange
|
||||
ErrInvalidCopySourceBucket
|
||||
ErrInvalidCopySourceObject
|
||||
ErrInvalidCopySourceEncoding
|
||||
ErrInvalidTagKey
|
||||
ErrInvalidTagValue
|
||||
ErrDuplicateTagKey
|
||||
ErrBucketTaggingLimited
|
||||
ErrObjectTaggingLimited
|
||||
ErrCannotParseHTTPRequest
|
||||
ErrInvalidURLEncodedTagging
|
||||
ErrInvalidAuthHeader
|
||||
ErrUnsupportedAuthorizationType
|
||||
ErrMalformedPOSTRequest
|
||||
ErrPOSTFileRequired
|
||||
ErrPostPolicyConditionInvalidFormat
|
||||
ErrEntityTooSmall
|
||||
ErrEntityTooLarge
|
||||
ErrMissingFields
|
||||
ErrMissingCredTag
|
||||
ErrMalformedXML
|
||||
ErrMalformedCredentialDate
|
||||
ErrMissingSignHeadersTag
|
||||
ErrMissingSignTag
|
||||
ErrUnsignedHeaders
|
||||
ErrExpiredPresignRequest
|
||||
ErrSignatureDoesNotMatch
|
||||
ErrContentSHA256Mismatch
|
||||
ErrInvalidSHA256Paylod
|
||||
ErrInvalidSHA256PayloadUsage
|
||||
ErrUnsupportedAnonymousSignedStreaming
|
||||
ErrMissingContentLength
|
||||
@@ -127,7 +124,6 @@ const (
|
||||
ErrMissingDateHeader
|
||||
ErrGetUploadsWithKey
|
||||
ErrVersionsWithKey
|
||||
ErrCopySourceNotAllowed
|
||||
ErrInvalidRequest
|
||||
ErrAuthNotSetup
|
||||
ErrNotImplemented
|
||||
@@ -141,14 +137,8 @@ const (
|
||||
ErrMissingObjectLockConfigurationNoSpaces
|
||||
ErrObjectLockConfigurationNotAllowed
|
||||
ErrObjectLocked
|
||||
ErrInvalidRetainUntilDate
|
||||
ErrPastObjectLockRetainDate
|
||||
ErrObjectLockInvalidRetentionPeriod
|
||||
ErrInvalidLegalHoldStatus
|
||||
ErrInvalidObjectLockMode
|
||||
ErrNoSuchBucketPolicy
|
||||
ErrBucketTaggingNotFound
|
||||
ErrObjectLockInvalidHeaders
|
||||
ErrObjectAttributesInvalidHeader
|
||||
ErrRequestTimeTooSkewed
|
||||
ErrInvalidBucketAclWithObjectOwnership
|
||||
@@ -158,10 +148,7 @@ const (
|
||||
ErrMalformedACL
|
||||
ErrUnexpectedContent
|
||||
ErrMissingSecurityHeader
|
||||
ErrInvalidMetadataDirective
|
||||
ErrInvalidTaggingDirective
|
||||
ErrKeyTooLong
|
||||
ErrInvalidVersionId
|
||||
ErrNoSuchVersion
|
||||
ErrSuspendedVersioningNotAllowed
|
||||
ErrMissingRequestBody
|
||||
@@ -170,26 +157,20 @@ const (
|
||||
ErrChecksumRequired
|
||||
ErrMissingContentSha256
|
||||
ErrInvalidChecksumAlgorithm
|
||||
ErrInvalidChecksumPart
|
||||
ErrChecksumTypeWithAlgo
|
||||
ErrInvalidChecksumHeader
|
||||
ErrTrailerHeaderNotSupported
|
||||
ErrBadRequest
|
||||
ErrMissingUploadId
|
||||
ErrInvalidUploadIdMarker
|
||||
ErrNoSuchCORSConfiguration
|
||||
ErrCORSForbidden
|
||||
ErrMissingCORSOrigin
|
||||
ErrCORSIsNotEnabled
|
||||
ErrNotModified
|
||||
ErrInvalidLocationConstraint
|
||||
ErrInvalidArgument
|
||||
ErrMalformedTrailer
|
||||
ErrInvalidChunkSize
|
||||
ErrSlowDown
|
||||
ErrMetadataTooLarge
|
||||
ErrOnlyAws4HmacSha256
|
||||
ErrInvalidDateHeader
|
||||
ErrUnsupportedAuthorizationMechanism
|
||||
|
||||
// Non-AWS errors
|
||||
@@ -288,21 +269,6 @@ var errorCodeResponse = map[ErrorCode]APIError{
|
||||
Description: "The Content-MD5 you specified did not match what we received.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidMaxBuckets: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Argument max-buckets must be an integer between 1 and 10000.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrNegativeMaxKeys: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "max-keys cannot be negative",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidObjectAttributes: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Invalid attribute name specified.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrNoSuchBucket: {
|
||||
Code: "NoSuchBucket",
|
||||
Description: "The specified bucket does not exist.",
|
||||
@@ -338,11 +304,6 @@ var errorCodeResponse = map[ErrorCode]APIError{
|
||||
Description: "One or more of the specified parts could not be found. The part may not have been uploaded, or the specified entity tag may not match the part's entity tag.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidPartNumber: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Part number must be an integer between 1 and 10000, inclusive.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidPartNumberRange: {
|
||||
Code: "InvalidPartNumber",
|
||||
Description: "The requested partnumber is not satisfiable.",
|
||||
@@ -358,36 +319,11 @@ var errorCodeResponse = map[ErrorCode]APIError{
|
||||
Description: "The list of parts was not in ascending order. Parts must be ordered by part number.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidCompleteMpPartNumber: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "PartNumber must be >= 1",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidCopyDest: {
|
||||
Code: "InvalidRequest",
|
||||
Description: "This copy request is illegal because it is trying to copy an object to itself without changing the object's metadata, storage class, website redirect location or encryption attributes.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidCopySourceRange: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "The x-amz-copy-source-range value must be of the form bytes=first-last where first and last are the zero-based offsets of the first and last bytes to copy",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidCopySourceBucket: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Invalid copy source bucket name",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidCopySourceObject: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Invalid copy source object key",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidCopySourceEncoding: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Invalid copy source encoding",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidTagKey: {
|
||||
Code: "InvalidTag",
|
||||
Description: "The TagKey you have provided is invalid",
|
||||
@@ -418,41 +354,16 @@ var errorCodeResponse = map[ErrorCode]APIError{
|
||||
Description: "An error occurred when parsing the HTTP request.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidURLEncodedTagging: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "The header 'x-amz-tagging' shall be encoded as UTF-8 then URLEncoded URL query parameters without tag name duplicates.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrMalformedXML: {
|
||||
Code: "MalformedXML",
|
||||
Description: "The XML you provided was not well-formed or did not validate against our published schema.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidAuthHeader: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Authorization header is invalid -- one and only one ' ' (space) required.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrUnsupportedAuthorizationType: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Unsupported Authorization Type",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrMalformedPOSTRequest: {
|
||||
Code: "MalformedPOSTRequest",
|
||||
Description: "The body of your POST request is not well-formed multipart/form-data.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrPOSTFileRequired: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "POST requires exactly one file upload per request.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrPostPolicyConditionInvalidFormat: {
|
||||
Code: "PostPolicyInvalidKeyName",
|
||||
Description: "Invalid according to Policy: Policy Condition failed.",
|
||||
HTTPStatusCode: http.StatusForbidden,
|
||||
},
|
||||
ErrEntityTooSmall: {
|
||||
Code: "EntityTooSmall",
|
||||
Description: "Your proposed upload is smaller than the minimum allowed size",
|
||||
@@ -463,31 +374,6 @@ var errorCodeResponse = map[ErrorCode]APIError{
|
||||
Description: "Your proposed upload exceeds the maximum allowed object size.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrMissingFields: {
|
||||
Code: "MissingFields",
|
||||
Description: "Missing fields in request.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrMissingCredTag: {
|
||||
Code: "InvalidRequest",
|
||||
Description: "Missing Credential field for this request.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrMissingSignHeadersTag: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Signature header missing SignedHeaders field.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrMissingSignTag: {
|
||||
Code: "AccessDenied",
|
||||
Description: "Signature header missing Signature field.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrUnsignedHeaders: {
|
||||
Code: "AccessDenied",
|
||||
Description: "There were headers present in the request which were not signed.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrExpiredPresignRequest: {
|
||||
Code: "AccessDenied",
|
||||
Description: "Request has expired.",
|
||||
@@ -513,11 +399,6 @@ var errorCodeResponse = map[ErrorCode]APIError{
|
||||
Description: "The provided 'x-amz-content-sha256' header does not match what was computed.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidSHA256Paylod: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "x-amz-content-sha256 must be UNSIGNED-PAYLOAD, STREAMING-UNSIGNED-PAYLOAD-TRAILER, STREAMING-AWS4-HMAC-SHA256-PAYLOAD, STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER, STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD, STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD-TRAILER or a valid sha256 value.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidSHA256PayloadUsage: {
|
||||
Code: "InvalidRequest",
|
||||
Description: "The value of x-amz-content-sha256 header is invalid.",
|
||||
@@ -553,11 +434,6 @@ var errorCodeResponse = map[ErrorCode]APIError{
|
||||
Description: "There is no such thing as the ?versions sub-resource for a key",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrCopySourceNotAllowed: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "You can only specify a copy source header for copy requests.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidRequest: {
|
||||
Code: "InvalidRequest",
|
||||
Description: "Invalid Request.",
|
||||
@@ -623,31 +499,6 @@ var errorCodeResponse = map[ErrorCode]APIError{
|
||||
Description: "Access Denied because object protected by object lock.",
|
||||
HTTPStatusCode: http.StatusForbidden,
|
||||
},
|
||||
ErrInvalidRetainUntilDate: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "The retain until date must be provided in ISO 8601 format",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrPastObjectLockRetainDate: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "The retain until date must be in the future!",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrObjectLockInvalidRetentionPeriod: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Default retention period must be a positive integer value.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidLegalHoldStatus: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Legal Hold must be either of 'ON' or 'OFF'",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidObjectLockMode: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Unknown wormMode directive.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrNoSuchBucketPolicy: {
|
||||
Code: "NoSuchBucketPolicy",
|
||||
Description: "The bucket policy does not exist.",
|
||||
@@ -658,11 +509,6 @@ var errorCodeResponse = map[ErrorCode]APIError{
|
||||
Description: "The TagSet does not exist.",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
ErrObjectLockInvalidHeaders: {
|
||||
Code: "InvalidRequest",
|
||||
Description: "x-amz-object-lock-retain-until-date and x-amz-object-lock-mode must both be supplied.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrObjectAttributesInvalidHeader: {
|
||||
Code: "InvalidRequest",
|
||||
Description: "The x-amz-object-attributes header specifying the attributes to be retrieved is either missing or empty",
|
||||
@@ -708,21 +554,6 @@ var errorCodeResponse = map[ErrorCode]APIError{
|
||||
Description: "Your request was missing a required header.",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
ErrInvalidMetadataDirective: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Unknown metadata directive.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidTaggingDirective: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Unknown tagging directive.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidVersionId: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Invalid version id specified",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrKeyTooLong: {
|
||||
Code: "KeyTooLongError",
|
||||
Description: "Your key is too long.",
|
||||
@@ -768,11 +599,6 @@ var errorCodeResponse = map[ErrorCode]APIError{
|
||||
Description: "Checksum algorithm provided is unsupported. Please try again with any of the valid types: [CRC32, CRC32C, CRC64NVME, MD5, SHA1, SHA256, SHA512, XXHASH128, XXHASH3, XXHASH64]",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidChecksumPart: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Invalid Base64 or multiple checksums present in request",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrChecksumTypeWithAlgo: {
|
||||
Code: "InvalidRequest",
|
||||
Description: "The x-amz-checksum-type header can only be used with the x-amz-checksum-algorithm header.",
|
||||
@@ -793,16 +619,6 @@ var errorCodeResponse = map[ErrorCode]APIError{
|
||||
Description: "Bad Request",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrMissingUploadId: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "This operation does not accept partNumber without uploadId",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidUploadIdMarker: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Invalid uploadId marker",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrNoSuchCORSConfiguration: {
|
||||
Code: "NoSuchCORSConfiguration",
|
||||
Description: "The CORS configuration does not exist",
|
||||
@@ -833,11 +649,6 @@ var errorCodeResponse = map[ErrorCode]APIError{
|
||||
Description: "The specified location-constraint is not valid",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidArgument: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrMalformedTrailer: {
|
||||
Code: "MalformedTrailerError",
|
||||
Description: "The request contained trailing data that was not well-formed or did not conform to our published schema.",
|
||||
@@ -854,21 +665,10 @@ var errorCodeResponse = map[ErrorCode]APIError{
|
||||
HTTPStatusCode: http.StatusServiceUnavailable,
|
||||
},
|
||||
ErrMetadataTooLarge: {
|
||||
// TODO: should have 'Size' and 'MaxSizeAllowed' properties
|
||||
Code: "MetadataTooLarge",
|
||||
Description: "Your metadata headers exceed the maximum allowed metadata size",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrOnlyAws4HmacSha256: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Only AWS4-HMAC-SHA256 is supported",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidDateHeader: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "X-Amz-Date must be formated via ISO8601 Long format",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrUnsupportedAuthorizationMechanism: {
|
||||
Code: "InvalidRequest",
|
||||
Description: "The authorization mechanism you have provided is not supported. Please use AWS4-HMAC-SHA256.",
|
||||
@@ -960,21 +760,6 @@ func GetAPIError(code ErrorCode) APIError {
|
||||
return errorCodeResponse[code]
|
||||
}
|
||||
|
||||
// getErrorResponse gets in standard error and resource value and
|
||||
// provides a encodable populated response values
|
||||
func GetAPIErrorResponse(err APIError, resource, requestID, hostID string) []byte {
|
||||
return encodeResponse(APIErrorResponse{
|
||||
Code: err.Code,
|
||||
Message: err.Description,
|
||||
BucketName: "",
|
||||
Key: "",
|
||||
Resource: resource,
|
||||
Region: "",
|
||||
RequestID: requestID,
|
||||
HostID: hostID,
|
||||
})
|
||||
}
|
||||
|
||||
// Encodes the response headers into XML format.
|
||||
func encodeResponse(response any) []byte {
|
||||
var bytesBuffer bytes.Buffer
|
||||
@@ -1061,14 +846,6 @@ func GetInvalidMpObjectSizeErr(val string) APIError {
|
||||
}
|
||||
}
|
||||
|
||||
func CreateExceedingRangeErr(objSize int64) APIError {
|
||||
return APIError{
|
||||
Code: "InvalidArgument",
|
||||
Description: fmt.Sprintf("Range specified is not valid for source object of size: %d", objSize),
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
}
|
||||
}
|
||||
|
||||
func GetInvalidCORSHeaderErr(header string) APIError {
|
||||
return APIError{
|
||||
Code: "InvalidRequest",
|
||||
@@ -1109,22 +886,6 @@ func GetInvalidCORSMethodErr(method string) APIError {
|
||||
}
|
||||
}
|
||||
|
||||
func GetInvalidMaxLimiterErr(limiter string) APIError {
|
||||
return APIError{
|
||||
Code: "InvalidArgument",
|
||||
Description: fmt.Sprintf("Provided %s not an integer or within integer range", limiter),
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
}
|
||||
}
|
||||
|
||||
func GetNegativeMaxLimiterErr(limiter string) APIError {
|
||||
return APIError{
|
||||
Code: "InvalidArgument",
|
||||
Description: fmt.Sprintf("Argument %s must be an integer between 0 and 2147483647", limiter),
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
}
|
||||
}
|
||||
|
||||
func GetCopySourceObjectTooLargeErr(limit int64) APIError {
|
||||
return APIError{
|
||||
Code: "InvalidRequest",
|
||||
@@ -1132,3 +893,13 @@ func GetCopySourceObjectTooLargeErr(limit int64) APIError {
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
}
|
||||
}
|
||||
|
||||
type ResourceType string
|
||||
|
||||
const (
|
||||
ResourceTypeBucket ResourceType = "BUCKET"
|
||||
ResourceTypeObject ResourceType = "OBJECT"
|
||||
ResourceTypeService ResourceType = "SERVICE"
|
||||
ResourceTypeBucketPolicy ResourceType = "BUCKETPOLICY"
|
||||
ResourceTypeUpload ResourceType = "UPLOAD"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright 2026 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package s3err
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// SignatureDoesNotMatchError is returned when request signature verification fails.
|
||||
// Produces diagnostic fields to help callers debug the mismatch.
|
||||
type SignatureDoesNotMatchError struct {
|
||||
APIError
|
||||
AWSAccessKeyId string
|
||||
StringToSign string
|
||||
SignatureProvided string
|
||||
StringToSignBytes string
|
||||
CanonicalRequest string
|
||||
CanonicalRequestBytes string
|
||||
}
|
||||
|
||||
func (e SignatureDoesNotMatchError) XMLBody(requestID, hostID string) []byte {
|
||||
return encodeResponse(struct {
|
||||
XMLName xml.Name `xml:"Error"`
|
||||
Code string
|
||||
Message string
|
||||
AWSAccessKeyId string `xml:",omitempty"`
|
||||
StringToSign string `xml:",omitempty"`
|
||||
SignatureProvided string `xml:",omitempty"`
|
||||
StringToSignBytes string `xml:",omitempty"`
|
||||
CanonicalRequest string `xml:",omitempty"`
|
||||
CanonicalRequestBytes string `xml:",omitempty"`
|
||||
RequestID string `xml:"RequestId,omitempty"`
|
||||
HostID string `xml:"HostId,omitempty"`
|
||||
}{
|
||||
Code: e.Code,
|
||||
Message: e.Description,
|
||||
AWSAccessKeyId: e.AWSAccessKeyId,
|
||||
StringToSign: e.StringToSign,
|
||||
SignatureProvided: e.SignatureProvided,
|
||||
StringToSignBytes: e.StringToSignBytes,
|
||||
CanonicalRequest: e.CanonicalRequest,
|
||||
CanonicalRequestBytes: e.CanonicalRequestBytes,
|
||||
RequestID: requestID,
|
||||
HostID: hostID,
|
||||
})
|
||||
}
|
||||
|
||||
func (e SignatureDoesNotMatchError) Is(target error) bool {
|
||||
t, ok := target.(APIError)
|
||||
return ok && e.APIError == t
|
||||
}
|
||||
|
||||
func GetSignatureDoesNotMatchErr(accessKeyId, stringToSign, signatureProvided, stringToSignBytes, canonicalRequest, canonicalRequestBytes string) SignatureDoesNotMatchError {
|
||||
return SignatureDoesNotMatchError{
|
||||
APIError: GetAPIError(ErrSignatureDoesNotMatch),
|
||||
AWSAccessKeyId: accessKeyId,
|
||||
StringToSign: stringToSign,
|
||||
SignatureProvided: signatureProvided,
|
||||
StringToSignBytes: stringToSignBytes,
|
||||
CanonicalRequest: canonicalRequest,
|
||||
CanonicalRequestBytes: canonicalRequestBytes,
|
||||
}
|
||||
}
|
||||
+51
-17
@@ -15,69 +15,103 @@
|
||||
package s3err
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// MalformedAuthError is returned when a Signature V4 authorization header is malformed.
|
||||
// Produces a <Region> field in the XML response when the expected gateway region is known.
|
||||
type MalformedAuthError struct {
|
||||
APIError
|
||||
Region string
|
||||
}
|
||||
|
||||
func (e MalformedAuthError) XMLBody(requestID, hostID string) []byte {
|
||||
return encodeResponse(struct {
|
||||
XMLName xml.Name `xml:"Error"`
|
||||
Code string
|
||||
Message string
|
||||
Region string `xml:",omitempty"`
|
||||
RequestID string `xml:"RequestId,omitempty"`
|
||||
HostID string `xml:"HostId,omitempty"`
|
||||
}{
|
||||
Code: e.Code,
|
||||
Message: e.Description,
|
||||
Region: e.Region,
|
||||
RequestID: requestID,
|
||||
HostID: hostID,
|
||||
})
|
||||
}
|
||||
|
||||
func (e MalformedAuthError) Is(target error) bool {
|
||||
t, ok := target.(APIError)
|
||||
return ok && e.APIError == t
|
||||
}
|
||||
|
||||
// Factory for building AuthorizationHeaderMalformed errors.
|
||||
func malformedAuthError(format string, args ...any) APIError {
|
||||
return APIError{
|
||||
Code: "AuthorizationHeaderMalformed",
|
||||
Description: fmt.Sprintf("The authorization header is malformed; %s", fmt.Sprintf(format, args...)),
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
func malformedAuthError(format string, args ...any) MalformedAuthError {
|
||||
return MalformedAuthError{
|
||||
APIError: APIError{
|
||||
Code: "AuthorizationHeaderMalformed",
|
||||
Description: fmt.Sprintf("The authorization header is malformed; %s", fmt.Sprintf(format, args...)),
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type malformedAuthErrors struct{}
|
||||
|
||||
func (malformedAuthErrors) InvalidDateFormat(s string) APIError {
|
||||
func (malformedAuthErrors) InvalidDateFormat(_, s string) S3Error {
|
||||
return malformedAuthError(
|
||||
"incorrect date format %q. This date in the credential must be in the format \"yyyyMMdd\".",
|
||||
s,
|
||||
)
|
||||
}
|
||||
|
||||
func (malformedAuthErrors) MalformedCredential() APIError {
|
||||
func (malformedAuthErrors) MalformedCredential(_ string) S3Error {
|
||||
return malformedAuthError(
|
||||
"the Credential is mal-formed; expecting \"<YOUR-AKID>/YYYYMMDD/REGION/SERVICE/aws4_request\".",
|
||||
)
|
||||
}
|
||||
|
||||
func (malformedAuthErrors) MissingCredential() APIError {
|
||||
func (malformedAuthErrors) MissingCredential() S3Error {
|
||||
return malformedAuthError("missing Credential.")
|
||||
}
|
||||
|
||||
func (malformedAuthErrors) MissingSignature() APIError {
|
||||
func (malformedAuthErrors) MissingSignature() S3Error {
|
||||
return malformedAuthError("missing Signature.")
|
||||
}
|
||||
|
||||
func (malformedAuthErrors) MissingSignedHeaders() APIError {
|
||||
func (malformedAuthErrors) MissingSignedHeaders() S3Error {
|
||||
return malformedAuthError("missing SignedHeaders.")
|
||||
}
|
||||
|
||||
func (malformedAuthErrors) IncorrectTerminal(s string) APIError {
|
||||
func (malformedAuthErrors) IncorrectTerminal(_, s string) S3Error {
|
||||
return malformedAuthError("incorrect terminal %q. This endpoint uses \"aws4_request\".", s)
|
||||
}
|
||||
|
||||
func (malformedAuthErrors) IncorrectRegion(expected, actual string) APIError {
|
||||
return malformedAuthError("the region %q is wrong; expecting %q", actual, expected)
|
||||
func (malformedAuthErrors) IncorrectRegion(expected, actual string) S3Error {
|
||||
err := malformedAuthError("the region %q is wrong; expecting %q", actual, expected)
|
||||
err.Region = expected
|
||||
return err
|
||||
}
|
||||
|
||||
func (malformedAuthErrors) IncorrectService(s string) APIError {
|
||||
func (malformedAuthErrors) IncorrectService(_, s string) S3Error {
|
||||
return malformedAuthError("incorrect service %q. This endpoint belongs to \"s3\".", s)
|
||||
}
|
||||
|
||||
func (malformedAuthErrors) MalformedComponent(s string) APIError {
|
||||
func (malformedAuthErrors) MalformedComponent(s string) S3Error {
|
||||
return malformedAuthError("the authorization component %q is malformed.", s)
|
||||
}
|
||||
|
||||
func (malformedAuthErrors) MissingComponents() APIError {
|
||||
func (malformedAuthErrors) MissingComponents() S3Error {
|
||||
return malformedAuthError(
|
||||
"the authorization header requires three components: Credential, SignedHeaders, and Signature.",
|
||||
)
|
||||
}
|
||||
|
||||
func (malformedAuthErrors) DateMismatch() APIError {
|
||||
func (malformedAuthErrors) DateMismatch() S3Error {
|
||||
return malformedAuthError(
|
||||
"The authorization header is malformed; Invalid credential date. Date is not the same as X-Amz-Date.",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user