diff --git a/.changes/nextrelease/awsexception-throwable.json b/.changes/nextrelease/awsexception-throwable.json new file mode 100644 index 0000000000..2287c1ddcf --- /dev/null +++ b/.changes/nextrelease/awsexception-throwable.json @@ -0,0 +1,7 @@ +[ + { + "type": "enhancement", + "category": "Exception", + "description": "Loosens requirements for `$previous` `AwsException` parameter from `Exception` to `Throwable`." + } +] diff --git a/src/Exception/AwsException.php b/src/Exception/AwsException.php index 05b7b955f0..54d7549f01 100644 --- a/src/Exception/AwsException.php +++ b/src/Exception/AwsException.php @@ -11,6 +11,7 @@ use JmesPath\Env as JmesPath; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\RequestInterface; +use Throwable; /** * Represents an AWS exception that is thrown when a command fails. @@ -39,35 +40,29 @@ class AwsException extends \RuntimeException implements /** - * @param string $message Exception message + * @param string $message Exception message * @param CommandInterface $command - * @param array $context Exception context - * @param \Exception $previous Previous exception (if any) + * @param array $context Exception context + * @param Throwable|null $previous Previous exception (if any) */ public function __construct( $message, CommandInterface $command, array $context = [], - ?\Exception $previous = null + ?Throwable $previous = null ) { - $this->data = isset($context['body']) ? $context['body'] : []; + $this->data = $context['body'] ?? []; $this->command = $command; - $this->response = isset($context['response']) ? $context['response'] : null; - $this->request = isset($context['request']) ? $context['request'] : null; - $this->requestId = isset($context['request_id']) - ? $context['request_id'] - : null; - $this->errorType = isset($context['type']) ? $context['type'] : null; - $this->errorCode = isset($context['code']) ? $context['code'] : null; - $this->errorShape = isset($context['error_shape']) ? $context['error_shape'] : null; + $this->response = $context['response'] ?? null; + $this->request = $context['request'] ?? null; + $this->requestId = $context['request_id'] ?? null; + $this->errorType = $context['type'] ?? null; + $this->errorCode = $context['code'] ?? null; + $this->errorShape = $context['error_shape'] ?? null; $this->connectionError = !empty($context['connection_error']); - $this->result = isset($context['result']) ? $context['result'] : null; - $this->transferInfo = isset($context['transfer_stats']) - ? $context['transfer_stats'] - : []; - $this->errorMessage = isset($context['message']) - ? $context['message'] - : null; + $this->result = $context['result'] ?? null; + $this->transferInfo = $context['transfer_stats'] ?? []; + $this->errorMessage = $context['message'] ?? null; $this->monitoringEvents = []; $this->maxRetriesExceeded = false; parent::__construct($message, 0, $previous); @@ -220,9 +215,7 @@ public function getTransferInfo($name = null) return $this->transferInfo; } - return isset($this->transferInfo[$name]) - ? $this->transferInfo[$name] - : null; + return $this->transferInfo[$name] ?? null; } /** diff --git a/tests/Exception/AwsExceptionTest.php b/tests/Exception/AwsExceptionTest.php index 8c68af75e4..4f36415cdf 100644 --- a/tests/Exception/AwsExceptionTest.php +++ b/tests/Exception/AwsExceptionTest.php @@ -20,7 +20,7 @@ class AwsExceptionTest extends TestCase { use UsesServiceTrait; - public function testProvidesContextShortcuts() + public function testProvidesContextShortcuts(): void { $ctx = [ 'request_id' => '10', @@ -37,7 +37,7 @@ public function testProvidesContextShortcuts() $this->assertNull($e->getResult()); } - public function testReturnsStatusCode() + public function testReturnsStatusCode(): void { $ctx = ['response' => new Response(400)]; $command = new Command('foo'); @@ -45,7 +45,7 @@ public function testReturnsStatusCode() $this->assertSame(400, $e->getStatusCode()); } - public function testSetsMaxRetriesExceeded() + public function testSetsMaxRetriesExceeded(): void { $command = new Command('foo'); $e = new AwsException('Foo', $command); @@ -54,7 +54,7 @@ public function testSetsMaxRetriesExceeded() $this->assertTrue($e->isMaxRetriesExceeded()); } - public function testProvidesResult() + public function testProvidesResult(): void { $command = new Command('foo'); $result = new Result(); @@ -62,7 +62,7 @@ public function testProvidesResult() $this->assertSame($result, $e->getResult()); } - public function testProvidesResponse() + public function testProvidesResponse(): void { $command = new Command('foo'); $response = new Response(); @@ -70,28 +70,28 @@ public function testProvidesResponse() $this->assertSame($response, $e->getResponse()); } - public function testProvidesErrorMessage() + public function testProvidesErrorMessage(): void { $command = new Command('foo'); $e = new AwsException('Foo', $command, ['message' => "test error message"]); $this->assertSame("test error message", $e->getAwsErrorMessage()); } - public function testProvidesFalseConnectionErrorFlag() + public function testProvidesFalseConnectionErrorFlag(): void { $command = new Command('foo'); $e = new AwsException('Foo', $command, ['connection_error' => false]); $this->assertFalse($e->isConnectionError()); } - public function testProvidesTrueConnectionErrorFlag() + public function testProvidesTrueConnectionErrorFlag(): void { $command = new Command('foo'); $e = new AwsException('Foo', $command, ['connection_error' => true]); $this->assertTrue($e->isConnectionError()); } - public function testProvidesRequest() + public function testProvidesRequest(): void { $command = new Command('foo'); $request = new Request('GET', 'http://www.foo.com'); @@ -99,7 +99,7 @@ public function testProvidesRequest() $this->assertSame($request, $e->getRequest()); } - public function testProvidesExceptionToStringWithNoPrevious() + public function testProvidesExceptionToStringWithNoPrevious(): void { $command = new Command('foo'); $e = new AwsException('Foo', $command); @@ -109,7 +109,7 @@ public function testProvidesExceptionToStringWithNoPrevious() $this->assertStringStartsWith($exceptionString, $e->__toString()); } - public function testProvidesExceptionToStringWithPreviousLast() + public function testProvidesExceptionToStringWithPreviousLast(): void { $prev = new \Exception("Last! '."); $command = new Command('foo'); @@ -120,7 +120,7 @@ public function testProvidesExceptionToStringWithPreviousLast() ); } - public function testHasData() + public function testHasData(): void { $e = new AwsException( 'foo-message', @@ -135,7 +135,7 @@ public function testHasData() $this->assertSame('b', $e->search('a')); } - public function testProvidesErrorShape() + public function testProvidesErrorShape(): void { $command = new Command('foo'); $response = new Response(); @@ -157,7 +157,7 @@ public function testProvidesErrorShape() $this->assertSame($errorShape->toArray(), $e->getAwsErrorShape()->toArray()); } - public function testCanIndirectlyModifyLikeAnArray() + public function testCanIndirectlyModifyLikeAnArray(): void { $e = new AwsException( 'foo-message', @@ -179,4 +179,84 @@ public function testCanIndirectlyModifyLikeAnArray() $q = 100; $this->assertSame(0, $e['qux']); } + + public function testAcceptsExceptionAsPrevious(): void + { + $previous = new \Exception('Previous exception'); + $command = new Command('foo'); + $e = new AwsException('Foo', $command, [], $previous); + + $this->assertSame($previous, $e->getPrevious()); + $this->assertInstanceOf(\Throwable::class, $e->getPrevious()); + } + + public function testAcceptsErrorAsPrevious(): void + { + $previous = new \Error('Previous error'); + $command = new Command('foo'); + $e = new AwsException('Foo', $command, [], $previous); + + $this->assertSame($previous, $e->getPrevious()); + $this->assertInstanceOf(\Throwable::class, $e->getPrevious()); + } + + public function testAcceptsCustomErrorAsPrevious(): void + { + $previous = new class extends \Error { + public function __construct() { + parent::__construct('Custom error'); + } + }; + + $command = new Command('foo'); + $e = new AwsException('Foo', $command, [], $previous); + + $this->assertSame($previous, $e->getPrevious()); + $this->assertInstanceOf(\Throwable::class, $e->getPrevious()); + } + + public function testAcceptsNullAsPrevious(): void + { + $command = new Command('foo'); + $e = new AwsException('Foo', $command, [], null); + + $this->assertNull($e->getPrevious()); + } + + /** + * @dataProvider previousThrowableProvider + */ + public function testAcceptsVariousThrowableTypes(\Throwable $previous): void + { + $command = new Command('foo'); + $e = new AwsException('Foo', $command, [], $previous); + + $this->assertSame($previous, $e->getPrevious()); + $this->assertInstanceOf(\Throwable::class, $e->getPrevious()); + } + + public function previousThrowableProvider(): array + { + return [ + 'standard exception' => [new \Exception('Standard exception')], + 'runtime exception' => [new \RuntimeException('Runtime exception')], + 'error' => [new \Error('Error')], + 'type error' => [new \TypeError('Type error')], + 'arithmetic error' => [new \ArithmeticError('Arithmetic error')], + 'division by zero error' => [new \DivisionByZeroError('Division by zero')] + ]; + } + + public function testExceptionMessageWithErrorAsPrevious(): void + { + $previous = new \Error('Previous error'); + $command = new Command('foo'); + $e = new AwsException('Foo', $command, [], $previous); + + $exceptionString = $e->__toString(); + + $this->assertStringContainsString('Foo', $exceptionString); + $this->assertStringContainsString('Previous error', $exceptionString); + $this->assertStringContainsString('Previous error', $e->__toString()); + } }