From 67bfd18acbf93d09abd5ec024d3a207ed6694434 Mon Sep 17 00:00:00 2001 From: Stephan Vock Date: Fri, 27 Sep 2024 10:22:44 +0100 Subject: [PATCH] AsyncS3: throw FilesystemOperationFailed exceptions in case upload or listContent fails --- src/AsyncAwsS3/AsyncAwsS3Adapter.php | 88 +++++++++++++++--------- src/AsyncAwsS3/AsyncAwsS3AdapterTest.php | 27 ++++++++ 2 files changed, 82 insertions(+), 33 deletions(-) diff --git a/src/AsyncAwsS3/AsyncAwsS3Adapter.php b/src/AsyncAwsS3/AsyncAwsS3Adapter.php index ab52be10d..23890f534 100644 --- a/src/AsyncAwsS3/AsyncAwsS3Adapter.php +++ b/src/AsyncAwsS3/AsyncAwsS3Adapter.php @@ -27,14 +27,18 @@ use League\Flysystem\UnableToCheckDirectoryExistence; use League\Flysystem\UnableToCheckFileExistence; use League\Flysystem\UnableToCopyFile; +use League\Flysystem\UnableToCreateDirectory; +use League\Flysystem\UnableToDeleteDirectory; use League\Flysystem\UnableToDeleteFile; use League\Flysystem\UnableToGeneratePublicUrl; use League\Flysystem\UnableToGenerateTemporaryUrl; +use League\Flysystem\UnableToListContents; use League\Flysystem\UnableToMoveFile; use League\Flysystem\UnableToProvideChecksum; use League\Flysystem\UnableToReadFile; use League\Flysystem\UnableToRetrieveMetadata; use League\Flysystem\UnableToSetVisibility; +use League\Flysystem\UnableToWriteFile; use League\Flysystem\UrlGeneration\PublicUrlGenerator; use League\Flysystem\UrlGeneration\TemporaryUrlGenerator; use League\Flysystem\Visibility; @@ -177,24 +181,29 @@ public function deleteDirectory(string $path): void $objects = []; $params = ['Bucket' => $this->bucket, 'Prefix' => $prefix]; - $result = $this->client->listObjectsV2($params); - /** @var AwsObject $item */ - foreach ($result->getContents() as $item) { - $key = $item->getKey(); - if (null !== $key) { - $objects[] = new ObjectIdentifier(['Key' => $key]); + + try { + $result = $this->client->listObjectsV2($params); + /** @var AwsObject $item */ + foreach ($result->getContents() as $item) { + $key = $item->getKey(); + if (null !== $key) { + $objects[] = new ObjectIdentifier(['Key' => $key]); + } } - } - if (empty($objects)) { - return; - } + if (empty($objects)) { + return; + } - foreach (array_chunk($objects, 1000) as $chunk) { - $this->client->deleteObjects([ - 'Bucket' => $this->bucket, - 'Delete' => ['Objects' => $chunk], - ]); + foreach (array_chunk($objects, 1000) as $chunk) { + $this->client->deleteObjects([ + 'Bucket' => $this->bucket, + 'Delete' => ['Objects' => $chunk], + ]); + } + } catch (\Throwable $e) { + throw UnableToDeleteDirectory::atLocation($path, $e->getMessage(), $e); } } @@ -202,7 +211,12 @@ public function createDirectory(string $path, Config $config): void { $defaultVisibility = $config->get(Config::OPTION_DIRECTORY_VISIBILITY, $this->visibility->defaultForDirectories()); $config = $config->withDefaults([Config::OPTION_VISIBILITY => $defaultVisibility]); - $this->upload(rtrim($path, '/') . '/', '', $config); + + try { + $this->upload(rtrim($path, '/') . '/', '', $config); + } catch (Throwable $e) { + throw UnableToCreateDirectory::dueToFailure($path, $e); + } } public function setVisibility(string $path, string $visibility): void @@ -292,16 +306,20 @@ public function listContents(string $path, bool $deep): iterable $options['Delimiter'] = '/'; } - $listing = $this->retrievePaginatedListing($options); + try { + $listing = $this->retrievePaginatedListing($options); - foreach ($listing as $item) { - $item = $this->mapS3ObjectMetadata($item); + foreach ($listing as $item) { + $item = $this->mapS3ObjectMetadata($item); - if ($item->path() === $path) { - continue; - } + if ($item->path() === $path) { + continue; + } - yield $item; + yield $item; + } + } catch (\Throwable $e) { + throw UnableToListContents::atLocation($path, $deep, $e); } } @@ -363,16 +381,20 @@ private function upload(string $path, $body, Config $config): void $options['ContentType'] = $mimeType; } - if ($this->client instanceof SimpleS3Client) { - // Supports upload of files larger than 5GB - $this->client->upload($this->bucket, $key, $body, array_merge($options, ['ACL' => $acl])); - } else { - $this->client->putObject(array_merge($options, [ - 'Bucket' => $this->bucket, - 'Key' => $key, - 'Body' => $body, - 'ACL' => $acl, - ])); + try { + if ($this->client instanceof SimpleS3Client) { + // Supports upload of files larger than 5GB + $this->client->upload($this->bucket, $key, $body, array_merge($options, ['ACL' => $acl])); + } else { + $this->client->putObject(array_merge($options, [ + 'Bucket' => $this->bucket, + 'Key' => $key, + 'Body' => $body, + 'ACL' => $acl, + ])); + } + } catch (Throwable $exception) { + throw UnableToWriteFile::atLocation($path, $exception->getMessage(), $exception); } } diff --git a/src/AsyncAwsS3/AsyncAwsS3AdapterTest.php b/src/AsyncAwsS3/AsyncAwsS3AdapterTest.php index c0143945a..2dc0514af 100644 --- a/src/AsyncAwsS3/AsyncAwsS3AdapterTest.php +++ b/src/AsyncAwsS3/AsyncAwsS3AdapterTest.php @@ -22,8 +22,10 @@ use League\Flysystem\StorageAttributes; use League\Flysystem\UnableToCheckFileExistence; use League\Flysystem\UnableToDeleteFile; +use League\Flysystem\UnableToListContents; use League\Flysystem\UnableToMoveFile; use League\Flysystem\UnableToRetrieveMetadata; +use League\Flysystem\UnableToWriteFile; use League\Flysystem\Visibility; use function getenv; use function iterator_to_array; @@ -324,6 +326,18 @@ public function write_with_simple_s3_client(): void $filesystem->write($file, $contents, new Config()); } + /** + * @test + */ + public function failing_to_write_a_file(): void + { + $adapter = $this->adapter(); + static::$stubS3Client->throwExceptionWhenExecutingCommand('PutObject'); + $this->expectException(UnableToWriteFile::class); + + $adapter->write('foo/bar.txt', 'contents', new Config()); + } + /** * @test */ @@ -392,6 +406,19 @@ public function top_level_directory_excluded_from_listing(): void }); } + /** + * @test + */ + public function failing_to_list_contents(): void + { + $adapter = $this->adapter(); + static::$stubS3Client->throwExceptionWhenExecutingCommand('ListObjectsV2'); + + $this->expectException(UnableToListContents::class); + + iterator_to_array($adapter->listContents('/path', false)); + } + protected static function createFilesystemAdapter(): FilesystemAdapter { static::$stubS3Client = new S3ClientStub(static::s3Client(), self::awsConfig());