fix: fixes the LastModified date formatting in CopyObject result.

Fixes #1276

Creates the custom `s3response.CopyObjectOutput` type to handle the `LastModified` date property formatting correctly. It uses `time.RFC3339` to format the date to match the format that s3 uses.
This commit is contained in:
niksis02
2025-05-12 23:30:47 +04:00
parent 42b03b866c
commit 323717bcf1
7 changed files with 124 additions and 79 deletions

View File

@@ -823,14 +823,14 @@ func (az *Azure) DeleteObjects(ctx context.Context, input *s3.DeleteObjectsInput
}, nil
}
func (az *Azure) CopyObject(ctx context.Context, input s3response.CopyObjectInput) (*s3.CopyObjectOutput, error) {
func (az *Azure) CopyObject(ctx context.Context, input s3response.CopyObjectInput) (s3response.CopyObjectOutput, error) {
dstClient, err := az.getBlobClient(*input.Bucket, *input.Key)
if err != nil {
return nil, err
return s3response.CopyObjectOutput{}, err
}
if strings.Join([]string{*input.Bucket, *input.Key}, "/") == *input.CopySource {
if input.MetadataDirective != types.MetadataDirectiveReplace {
return nil, s3err.GetAPIError(s3err.ErrInvalidCopyDest)
return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrInvalidCopyDest)
}
// Set object meta http headers
@@ -842,7 +842,7 @@ func (az *Azure) CopyObject(ctx context.Context, input s3response.CopyObjectInpu
BlobContentType: input.ContentType,
}, nil)
if err != nil {
return nil, azureErrToS3Err(err)
return s3response.CopyObjectOutput{}, azureErrToS3Err(err)
}
meta := input.Metadata
@@ -857,14 +857,14 @@ func (az *Azure) CopyObject(ctx context.Context, input s3response.CopyObjectInpu
// Set object metadata
_, err = dstClient.SetMetadata(ctx, parseMetadata(meta), nil)
if err != nil {
return nil, azureErrToS3Err(err)
return s3response.CopyObjectOutput{}, azureErrToS3Err(err)
}
// Set object legal hold
if input.ObjectLockLegalHoldStatus != "" {
err = az.PutObjectLegalHold(ctx, *input.Bucket, *input.Key, "", input.ObjectLockLegalHoldStatus == types.ObjectLockLegalHoldStatusOn)
if err != nil {
return nil, azureErrToS3Err(err)
return s3response.CopyObjectOutput{}, azureErrToS3Err(err)
}
}
// Set object retention
@@ -878,11 +878,11 @@ func (az *Azure) CopyObject(ctx context.Context, input s3response.CopyObjectInpu
retParsed, err := json.Marshal(retention)
if err != nil {
return nil, fmt.Errorf("parse object retention: %w", err)
return s3response.CopyObjectOutput{}, fmt.Errorf("parse object retention: %w", err)
}
err = az.PutObjectRetention(ctx, *input.Bucket, *input.Key, "", true, retParsed)
if err != nil {
return nil, azureErrToS3Err(err)
return s3response.CopyObjectOutput{}, azureErrToS3Err(err)
}
}
@@ -890,16 +890,16 @@ func (az *Azure) CopyObject(ctx context.Context, input s3response.CopyObjectInpu
if input.TaggingDirective == types.TaggingDirectiveReplace {
tags, err := backend.ParseObjectTags(getString(input.Tagging))
if err != nil {
return nil, err
return s3response.CopyObjectOutput{}, err
}
_, err = dstClient.SetTags(ctx, tags, nil)
if err != nil {
return nil, azureErrToS3Err(err)
return s3response.CopyObjectOutput{}, azureErrToS3Err(err)
}
}
return &s3.CopyObjectOutput{
CopyObjectResult: &types.CopyObjectResult{
return s3response.CopyObjectOutput{
CopyObjectResult: &s3response.CopyObjectResult{
LastModified: res.LastModified,
ETag: (*string)(res.ETag),
},
@@ -908,13 +908,13 @@ func (az *Azure) CopyObject(ctx context.Context, input s3response.CopyObjectInpu
srcBucket, srcObj, _, err := backend.ParseCopySource(*input.CopySource)
if err != nil {
return nil, err
return s3response.CopyObjectOutput{}, err
}
// Get the source object
downloadResp, err := az.client.DownloadStream(ctx, srcBucket, srcObj, nil)
if err != nil {
return nil, azureErrToS3Err(err)
return s3response.CopyObjectOutput{}, azureErrToS3Err(err)
}
pInput := s3response.PutObjectInput{
@@ -952,28 +952,28 @@ func (az *Azure) CopyObject(ctx context.Context, input s3response.CopyObjectInpu
// Create the destination object
resp, err := az.PutObject(ctx, pInput)
if err != nil {
return nil, err
return s3response.CopyObjectOutput{}, err
}
// Copy the object tagging, if tagging directive is "COPY"
if input.TaggingDirective == types.TaggingDirectiveCopy {
srcClient, err := az.getBlobClient(srcBucket, srcObj)
if err != nil {
return nil, err
return s3response.CopyObjectOutput{}, err
}
res, err := srcClient.GetTags(ctx, nil)
if err != nil {
return nil, azureErrToS3Err(err)
return s3response.CopyObjectOutput{}, azureErrToS3Err(err)
}
_, err = dstClient.SetTags(ctx, parseAzTags(res.BlobTagSet), nil)
if err != nil {
return nil, azureErrToS3Err(err)
return s3response.CopyObjectOutput{}, azureErrToS3Err(err)
}
}
return &s3.CopyObjectOutput{
CopyObjectResult: &types.CopyObjectResult{
return s3response.CopyObjectOutput{
CopyObjectResult: &s3response.CopyObjectResult{
ETag: &resp.ETag,
},
}, nil

View File

@@ -65,7 +65,7 @@ type Backend interface {
GetObject(context.Context, *s3.GetObjectInput) (*s3.GetObjectOutput, error)
GetObjectAcl(context.Context, *s3.GetObjectAclInput) (*s3.GetObjectAclOutput, error)
GetObjectAttributes(context.Context, *s3.GetObjectAttributesInput) (s3response.GetObjectAttributesResponse, error)
CopyObject(context.Context, s3response.CopyObjectInput) (*s3.CopyObjectOutput, error)
CopyObject(context.Context, s3response.CopyObjectInput) (s3response.CopyObjectOutput, error)
ListObjects(context.Context, *s3.ListObjectsInput) (s3response.ListObjectsResult, error)
ListObjectsV2(context.Context, *s3.ListObjectsV2Input) (s3response.ListObjectsV2Result, error)
DeleteObject(context.Context, *s3.DeleteObjectInput) (*s3.DeleteObjectOutput, error)
@@ -200,8 +200,8 @@ func (BackendUnsupported) GetObjectAcl(context.Context, *s3.GetObjectAclInput) (
func (BackendUnsupported) GetObjectAttributes(context.Context, *s3.GetObjectAttributesInput) (s3response.GetObjectAttributesResponse, error) {
return s3response.GetObjectAttributesResponse{}, s3err.GetAPIError(s3err.ErrNotImplemented)
}
func (BackendUnsupported) CopyObject(context.Context, s3response.CopyObjectInput) (*s3.CopyObjectOutput, error) {
return nil, s3err.GetAPIError(s3err.ErrNotImplemented)
func (BackendUnsupported) CopyObject(context.Context, s3response.CopyObjectInput) (s3response.CopyObjectOutput, error) {
return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrNotImplemented)
}
func (BackendUnsupported) ListObjects(context.Context, *s3.ListObjectsInput) (s3response.ListObjectsResult, error) {
return s3response.ListObjectsResult{}, s3err.GetAPIError(s3err.ErrNotImplemented)

View File

@@ -3855,51 +3855,51 @@ func (p *Posix) GetObjectAttributes(ctx context.Context, input *s3.GetObjectAttr
}, nil
}
func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput) (*s3.CopyObjectOutput, error) {
func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput) (s3response.CopyObjectOutput, error) {
if input.Bucket == nil {
return nil, s3err.GetAPIError(s3err.ErrInvalidBucketName)
return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrInvalidBucketName)
}
if input.Key == nil {
return nil, s3err.GetAPIError(s3err.ErrInvalidCopyDest)
return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrInvalidCopyDest)
}
if input.CopySource == nil {
return nil, s3err.GetAPIError(s3err.ErrInvalidCopySource)
return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrInvalidCopySource)
}
if input.ExpectedBucketOwner == nil {
return nil, s3err.GetAPIError(s3err.ErrInvalidRequest)
return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrInvalidRequest)
}
srcBucket, srcObject, srcVersionId, err := backend.ParseCopySource(*input.CopySource)
if err != nil {
return nil, err
return s3response.CopyObjectOutput{}, err
}
dstBucket := *input.Bucket
dstObject := *input.Key
_, err = os.Stat(srcBucket)
if errors.Is(err, fs.ErrNotExist) {
return nil, s3err.GetAPIError(s3err.ErrNoSuchBucket)
return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrNoSuchBucket)
}
if err != nil {
return nil, fmt.Errorf("stat bucket: %w", err)
return s3response.CopyObjectOutput{}, fmt.Errorf("stat bucket: %w", err)
}
vStatus, err := p.getBucketVersioningStatus(ctx, srcBucket)
if err != nil {
return nil, err
return s3response.CopyObjectOutput{}, err
}
vEnabled := p.isBucketVersioningEnabled(vStatus)
if srcVersionId != "" {
if !p.versioningEnabled() || !vEnabled {
return nil, s3err.GetAPIError(s3err.ErrInvalidVersionId)
return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrInvalidVersionId)
}
vId, err := p.meta.RetrieveAttribute(nil, srcBucket, srcObject, versionIdKey)
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
return nil, s3err.GetAPIError(s3err.ErrNoSuchKey)
return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrNoSuchKey)
}
if err != nil && !errors.Is(err, meta.ErrNoSuchKey) {
return nil, fmt.Errorf("get src object version id: %w", err)
return s3response.CopyObjectOutput{}, fmt.Errorf("get src object version id: %w", err)
}
if string(vId) != srcVersionId {
@@ -3910,37 +3910,37 @@ func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput
_, err = os.Stat(dstBucket)
if errors.Is(err, fs.ErrNotExist) {
return nil, s3err.GetAPIError(s3err.ErrNoSuchBucket)
return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrNoSuchBucket)
}
if err != nil {
return nil, fmt.Errorf("stat bucket: %w", err)
return s3response.CopyObjectOutput{}, fmt.Errorf("stat bucket: %w", err)
}
objPath := joinPathWithTrailer(srcBucket, srcObject)
f, err := os.Open(objPath)
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
if p.versioningEnabled() && vEnabled {
return nil, s3err.GetAPIError(s3err.ErrNoSuchVersion)
return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrNoSuchVersion)
}
return nil, s3err.GetAPIError(s3err.ErrNoSuchKey)
return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrNoSuchKey)
}
if errors.Is(err, syscall.ENAMETOOLONG) {
return nil, s3err.GetAPIError(s3err.ErrKeyTooLong)
return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrKeyTooLong)
}
if err != nil {
return nil, fmt.Errorf("open object: %w", err)
return s3response.CopyObjectOutput{}, fmt.Errorf("open object: %w", err)
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
return nil, fmt.Errorf("stat object: %w", err)
return s3response.CopyObjectOutput{}, fmt.Errorf("stat object: %w", err)
}
if strings.HasSuffix(srcObject, "/") && !fi.IsDir() {
return nil, s3err.GetAPIError(s3err.ErrNoSuchKey)
return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrNoSuchKey)
}
if !strings.HasSuffix(srcObject, "/") && fi.IsDir() {
return nil, s3err.GetAPIError(s3err.ErrNoSuchKey)
return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrNoSuchKey)
}
mdmap := make(map[string]string)
@@ -3958,7 +3958,7 @@ func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput
dstObjdPath := joinPathWithTrailer(dstBucket, dstObject)
if dstObjdPath == objPath {
if input.MetadataDirective == types.MetadataDirectiveCopy {
return &s3.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrInvalidCopyDest)
return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrInvalidCopyDest)
}
// Delete the object metadata
@@ -3966,7 +3966,7 @@ func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput
err := p.meta.DeleteAttribute(dstBucket, dstObject,
fmt.Sprintf("%v.%v", metaHdr, k))
if err != nil && !errors.Is(err, meta.ErrNoSuchKey) {
return nil, fmt.Errorf("delete user metadata: %w", err)
return s3response.CopyObjectOutput{}, fmt.Errorf("delete user metadata: %w", err)
}
}
// Store the new metadata
@@ -3974,13 +3974,13 @@ func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput
err := p.meta.StoreAttribute(nil, dstBucket, dstObject,
fmt.Sprintf("%v.%v", metaHdr, k), []byte(v))
if err != nil {
return nil, fmt.Errorf("set user attr %q: %w", k, err)
return s3response.CopyObjectOutput{}, fmt.Errorf("set user attr %q: %w", k, err)
}
}
checksums, err := p.retrieveChecksums(nil, dstBucket, dstObject)
if err != nil && !errors.Is(err, meta.ErrNoSuchKey) {
return nil, fmt.Errorf("get obj checksums: %w", err)
return s3response.CopyObjectOutput{}, fmt.Errorf("get obj checksums: %w", err)
}
chType = checksums.Type
@@ -3991,18 +3991,18 @@ func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput
if checksums.Algorithm != input.ChecksumAlgorithm {
f, err := os.Open(dstObjdPath)
if err != nil {
return nil, fmt.Errorf("open obj file: %w", err)
return s3response.CopyObjectOutput{}, fmt.Errorf("open obj file: %w", err)
}
defer f.Close()
hashReader, err := utils.NewHashReader(f, "", utils.HashType(strings.ToLower(string(input.ChecksumAlgorithm))))
if err != nil {
return nil, fmt.Errorf("initialize hash reader: %w", err)
return s3response.CopyObjectOutput{}, fmt.Errorf("initialize hash reader: %w", err)
}
_, err = hashReader.Read(nil)
if err != nil {
return nil, fmt.Errorf("read err: %w", err)
return s3response.CopyObjectOutput{}, fmt.Errorf("read err: %w", err)
}
checksums = s3response.Checksum{}
@@ -4032,7 +4032,7 @@ func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput
err = p.storeChecksums(f, dstBucket, dstObject, checksums)
if err != nil {
return nil, fmt.Errorf("store checksum: %w", err)
return s3response.CopyObjectOutput{}, fmt.Errorf("store checksum: %w", err)
}
}
}
@@ -4041,7 +4041,7 @@ func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput
etag = string(b)
vId, _ := p.meta.RetrieveAttribute(nil, dstBucket, dstObject, versionIdKey)
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
return nil, s3err.GetAPIError(s3err.ErrNoSuchKey)
return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrNoSuchKey)
}
version = backend.GetPtrFromString(string(vId))
@@ -4056,18 +4056,18 @@ func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput
Expires: input.Expires,
})
if err != nil {
return nil, err
return s3response.CopyObjectOutput{}, err
}
if input.TaggingDirective == types.TaggingDirectiveReplace {
tags, err := backend.ParseObjectTags(getString(input.Tagging))
if err != nil {
return nil, err
return s3response.CopyObjectOutput{}, err
}
err = p.PutObjectTagging(ctx, dstBucket, dstObject, tags)
if err != nil {
return nil, err
return s3response.CopyObjectOutput{}, err
}
}
} else {
@@ -4075,7 +4075,7 @@ func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput
checksums, err := p.retrieveChecksums(f, srcBucket, srcObject)
if err != nil && !errors.Is(err, meta.ErrNoSuchKey) {
return nil, fmt.Errorf("get obj checksum: %w", err)
return s3response.CopyObjectOutput{}, fmt.Errorf("get obj checksum: %w", err)
}
// If any checksum algorithm is provided, replace, otherwise
@@ -4121,7 +4121,7 @@ func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput
res, err := p.PutObject(ctx, putObjectInput)
if err != nil {
return nil, err
return s3response.CopyObjectOutput{}, err
}
// copy the source object tagging after the destination object
@@ -4129,12 +4129,12 @@ func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput
if input.TaggingDirective == types.TaggingDirectiveCopy {
tagging, err := p.meta.RetrieveAttribute(nil, srcBucket, srcObject, tagHdr)
if err != nil && !errors.Is(err, meta.ErrNoSuchKey) {
return nil, fmt.Errorf("get source object tagging: %w", err)
return s3response.CopyObjectOutput{}, fmt.Errorf("get source object tagging: %w", err)
}
if err == nil {
err := p.meta.StoreAttribute(nil, dstBucket, dstObject, tagHdr, tagging)
if err != nil {
return nil, fmt.Errorf("set destination object tagging: %w", err)
return s3response.CopyObjectOutput{}, fmt.Errorf("set destination object tagging: %w", err)
}
}
}
@@ -4151,11 +4151,11 @@ func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput
fi, err = os.Stat(dstObjdPath)
if err != nil {
return nil, fmt.Errorf("stat dst object: %w", err)
return s3response.CopyObjectOutput{}, fmt.Errorf("stat dst object: %w", err)
}
return &s3.CopyObjectOutput{
CopyObjectResult: &types.CopyObjectResult{
return s3response.CopyObjectOutput{
CopyObjectResult: &s3response.CopyObjectResult{
ETag: &etag,
LastModified: backend.GetTimePtr(fi.ModTime()),
ChecksumCRC32: crc32,

View File

@@ -1089,9 +1089,9 @@ func (s *S3Proxy) GetObjectAttributes(ctx context.Context, input *s3.GetObjectAt
}, handleError(err)
}
func (s *S3Proxy) CopyObject(ctx context.Context, input s3response.CopyObjectInput) (*s3.CopyObjectOutput, error) {
func (s *S3Proxy) CopyObject(ctx context.Context, input s3response.CopyObjectInput) (s3response.CopyObjectOutput, error) {
if *input.Bucket == s.metaBucket {
return nil, s3err.GetAPIError(s3err.ErrAccessDenied)
return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrAccessDenied)
}
if input.CacheControl != nil && *input.CacheControl == "" {
input.CacheControl = nil
@@ -1227,7 +1227,33 @@ func (s *S3Proxy) CopyObject(ctx context.Context, input s3response.CopyObjectInp
StorageClass: input.StorageClass,
TaggingDirective: input.TaggingDirective,
})
return out, handleError(err)
if err != nil {
return s3response.CopyObjectOutput{}, handleError(err)
}
if out.CopyObjectResult == nil {
out.CopyObjectResult = &types.CopyObjectResult{}
}
return s3response.CopyObjectOutput{
BucketKeyEnabled: out.BucketKeyEnabled,
CopyObjectResult: &s3response.CopyObjectResult{
ChecksumCRC32: out.CopyObjectResult.ChecksumCRC32,
ChecksumCRC32C: out.CopyObjectResult.ChecksumCRC32C,
ChecksumCRC64NVME: out.CopyObjectResult.ChecksumCRC64NVME,
ChecksumSHA1: out.CopyObjectResult.ChecksumSHA1,
ChecksumSHA256: out.CopyObjectResult.ChecksumSHA256,
ChecksumType: out.CopyObjectResult.ChecksumType,
ETag: out.CopyObjectResult.ETag,
LastModified: out.CopyObjectResult.LastModified,
},
CopySourceVersionId: out.CopySourceVersionId,
Expiration: out.Expiration,
SSECustomerAlgorithm: out.SSECustomerAlgorithm,
SSECustomerKeyMD5: out.SSECustomerKeyMD5,
SSEKMSEncryptionContext: out.SSEKMSEncryptionContext,
SSEKMSKeyId: out.SSEKMSKeyId,
ServerSideEncryption: out.ServerSideEncryption,
VersionId: out.VersionId,
}, handleError(err)
}
func (s *S3Proxy) ListObjects(ctx context.Context, input *s3.ListObjectsInput) (s3response.ListObjectsResult, error) {

View File

@@ -32,7 +32,7 @@ var _ backend.Backend = &BackendMock{}
// CompleteMultipartUploadFunc: func(contextMoqParam context.Context, completeMultipartUploadInput *s3.CompleteMultipartUploadInput) (s3response.CompleteMultipartUploadResult, string, error) {
// panic("mock out the CompleteMultipartUpload method")
// },
// CopyObjectFunc: func(contextMoqParam context.Context, copyObjectInput s3response.CopyObjectInput) (*s3.CopyObjectOutput, error) {
// CopyObjectFunc: func(contextMoqParam context.Context, copyObjectInput s3response.CopyObjectInput) (s3response.CopyObjectOutput, error) {
// panic("mock out the CopyObject method")
// },
// CreateBucketFunc: func(contextMoqParam context.Context, createBucketInput *s3.CreateBucketInput, defaultACL []byte) error {
@@ -202,7 +202,7 @@ type BackendMock struct {
CompleteMultipartUploadFunc func(contextMoqParam context.Context, completeMultipartUploadInput *s3.CompleteMultipartUploadInput) (s3response.CompleteMultipartUploadResult, string, error)
// CopyObjectFunc mocks the CopyObject method.
CopyObjectFunc func(contextMoqParam context.Context, copyObjectInput s3response.CopyObjectInput) (*s3.CopyObjectOutput, error)
CopyObjectFunc func(contextMoqParam context.Context, copyObjectInput s3response.CopyObjectInput) (s3response.CopyObjectOutput, error)
// CreateBucketFunc mocks the CreateBucket method.
CreateBucketFunc func(contextMoqParam context.Context, createBucketInput *s3.CreateBucketInput, defaultACL []byte) error
@@ -940,7 +940,7 @@ func (mock *BackendMock) CompleteMultipartUploadCalls() []struct {
}
// CopyObject calls CopyObjectFunc.
func (mock *BackendMock) CopyObject(contextMoqParam context.Context, copyObjectInput s3response.CopyObjectInput) (*s3.CopyObjectOutput, error) {
func (mock *BackendMock) CopyObject(contextMoqParam context.Context, copyObjectInput s3response.CopyObjectInput) (s3response.CopyObjectOutput, error) {
if mock.CopyObjectFunc == nil {
panic("BackendMock.CopyObjectFunc: method is nil but Backend.CopyObject was just called")
}

View File

@@ -974,9 +974,9 @@ func TestS3ApiController_PutActions(t *testing.T) {
PutObjectAclFunc: func(context.Context, *s3.PutObjectAclInput) error {
return nil
},
CopyObjectFunc: func(context.Context, s3response.CopyObjectInput) (*s3.CopyObjectOutput, error) {
return &s3.CopyObjectOutput{
CopyObjectResult: &types.CopyObjectResult{},
CopyObjectFunc: func(context.Context, s3response.CopyObjectInput) (s3response.CopyObjectOutput, error) {
return s3response.CopyObjectOutput{
CopyObjectResult: &s3response.CopyObjectResult{},
}, nil
},
PutObjectFunc: func(context.Context, s3response.PutObjectInput) (s3response.PutObjectOutput, error) {

View File

@@ -344,23 +344,42 @@ type CanonicalUser struct {
DisplayName string
}
type CopyObjectOutput struct {
BucketKeyEnabled *bool
CopyObjectResult *CopyObjectResult
CopySourceVersionId *string
Expiration *string
SSECustomerAlgorithm *string
SSECustomerKeyMD5 *string
SSEKMSEncryptionContext *string
SSEKMSKeyId *string
ServerSideEncryption types.ServerSideEncryption
VersionId *string
}
type CopyObjectResult struct {
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CopyObjectResult" json:"-"`
LastModified time.Time
ETag string
CopySourceVersionId string `xml:"-"`
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CopyObjectResult" json:"-"`
ChecksumCRC32 *string
ChecksumCRC32C *string
ChecksumCRC64NVME *string
ChecksumSHA1 *string
ChecksumSHA256 *string
ChecksumType types.ChecksumType
ETag *string
LastModified *time.Time
}
func (r CopyObjectResult) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
type Alias CopyObjectResult
aux := &struct {
LastModified string `xml:"LastModified"`
LastModified string `xml:"LastModified,omitempty"`
*Alias
}{
Alias: (*Alias)(&r),
}
aux.LastModified = r.LastModified.UTC().Format(iso8601TimeFormat)
if r.LastModified != nil {
aux.LastModified = r.LastModified.UTC().Format(time.RFC3339)
}
return e.EncodeElement(aux, start)
}