From 4589515a12c1bd5054db8366bae3f3575ce19351 Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Wed, 29 Oct 2025 17:18:08 +0100 Subject: [PATCH 1/5] Implemented Exceptions --- src/DTO/Exception.php | 37 ++++++++++++++ src/DTO/InnerException.php | 37 ++++++++++++++ src/DTO/MFilesError.php | 58 ++++++++++++++++++++++ src/Exceptions/MFilesErrorException.php | 17 +++++++ src/Responses/DownloadFileResponse.php | 4 ++ src/Responses/ErrorResponse.php | 16 ++++++ src/Responses/LogInToVaultResponse.php | 5 ++ src/Responses/ObjectPropertiesResponse.php | 5 ++ src/Responses/UploadFileResponse.php | 5 ++ 9 files changed, 184 insertions(+) create mode 100644 src/DTO/Exception.php create mode 100644 src/DTO/InnerException.php create mode 100644 src/DTO/MFilesError.php create mode 100644 src/Exceptions/MFilesErrorException.php create mode 100644 src/Responses/ErrorResponse.php diff --git a/src/DTO/Exception.php b/src/DTO/Exception.php new file mode 100644 index 0000000..1526652 --- /dev/null +++ b/src/DTO/Exception.php @@ -0,0 +1,37 @@ + $this->name, + 'message' => $this->message, + 'innerException' => $this->innerException?->toArray(), + ]; + } +} diff --git a/src/DTO/InnerException.php b/src/DTO/InnerException.php new file mode 100644 index 0000000..482a611 --- /dev/null +++ b/src/DTO/InnerException.php @@ -0,0 +1,37 @@ + $this->name, + 'message' => $this->message, + 'stackText' => $this->stackText, + 'errorCode' => $this->errorCode, + ]; + } +} diff --git a/src/DTO/MFilesError.php b/src/DTO/MFilesError.php new file mode 100644 index 0000000..cd4583c --- /dev/null +++ b/src/DTO/MFilesError.php @@ -0,0 +1,58 @@ + $this->errorCode, + 'status' => $this->status, + 'url' => $this->url, + 'method' => $this->method, + 'exception' => $this->exception?->toArray(), + 'stack' => $this->stack, + 'message' => $this->message, + 'isLoggedToVault' => $this->isLoggedToVault, + 'isLoggedToApplication' => $this->isLoggedToApplication, + 'exceptionName' => $this->exceptionName, + ]; + } +} diff --git a/src/Exceptions/MFilesErrorException.php b/src/Exceptions/MFilesErrorException.php new file mode 100644 index 0000000..2224f1d --- /dev/null +++ b/src/Exceptions/MFilesErrorException.php @@ -0,0 +1,17 @@ +message, $error->status); + } +} diff --git a/src/Responses/DownloadFileResponse.php b/src/Responses/DownloadFileResponse.php index 006ce2e..4126400 100644 --- a/src/Responses/DownloadFileResponse.php +++ b/src/Responses/DownloadFileResponse.php @@ -5,12 +5,16 @@ namespace CodebarAg\MFiles\Responses; use CodebarAg\MFiles\DTO\DownloadedFile; +use CodebarAg\MFiles\Exceptions\MFilesErrorException; use Saloon\Http\Response; final class DownloadFileResponse { public static function createDtoFromResponse(Response $response): DownloadedFile { + if (! $response->successful()) { + throw new MFilesErrorException(ErrorResponse::createDtoFromResponse($response)); + } $headers = $response->headers(); $fileContentType = $headers->get('Content-Type'); $fileSize = (int) $headers->get('Content-Length', 0); diff --git a/src/Responses/ErrorResponse.php b/src/Responses/ErrorResponse.php new file mode 100644 index 0000000..e24c45a --- /dev/null +++ b/src/Responses/ErrorResponse.php @@ -0,0 +1,16 @@ +json()); + } +} diff --git a/src/Responses/LogInToVaultResponse.php b/src/Responses/LogInToVaultResponse.php index 7312a4e..6d6fba4 100644 --- a/src/Responses/LogInToVaultResponse.php +++ b/src/Responses/LogInToVaultResponse.php @@ -4,6 +4,7 @@ namespace CodebarAg\MFiles\Responses; +use CodebarAg\MFiles\Exceptions\MFilesErrorException; use Illuminate\Support\Arr; use Saloon\Http\Response; @@ -11,6 +12,10 @@ final class LogInToVaultResponse { public static function createDtoFromResponse(Response $response): ?string { + if (! $response->successful()) { + throw new MFilesErrorException(ErrorResponse::createDtoFromResponse($response)); + } + return Arr::get($response->json(), 'Value'); } } diff --git a/src/Responses/ObjectPropertiesResponse.php b/src/Responses/ObjectPropertiesResponse.php index bc4ef0c..6b68dc9 100644 --- a/src/Responses/ObjectPropertiesResponse.php +++ b/src/Responses/ObjectPropertiesResponse.php @@ -5,12 +5,17 @@ namespace CodebarAg\MFiles\Responses; use CodebarAg\MFiles\DTO\ObjectProperties; +use CodebarAg\MFiles\Exceptions\MFilesErrorException; use Saloon\Http\Response; final class ObjectPropertiesResponse { public static function createDtoFromResponse(Response $response): ObjectProperties { + if (! $response->successful()) { + throw new MFilesErrorException(ErrorResponse::createDtoFromResponse($response)); + } + return ObjectProperties::fromArray($response->json()); } } diff --git a/src/Responses/UploadFileResponse.php b/src/Responses/UploadFileResponse.php index 8b799a5..1dd6787 100644 --- a/src/Responses/UploadFileResponse.php +++ b/src/Responses/UploadFileResponse.php @@ -4,6 +4,7 @@ namespace CodebarAg\MFiles\Responses; +use CodebarAg\MFiles\Exceptions\MFilesErrorException; use Illuminate\Support\Arr; use Illuminate\Support\Str; use Saloon\Http\Response; @@ -12,6 +13,10 @@ final class UploadFileResponse { public static function createDtoFromResponse(Response $response, string $fileName): array { + if (! $response->successful()) { + throw new MFilesErrorException(ErrorResponse::createDtoFromResponse($response)); + } + $data = $response->json(); $data = Arr::add($data, 'Title', Str::beforeLast($fileName, '.')); $data = Arr::add($data, 'Extension', Str::afterLast($fileName, '.')); From acc4167843f0f048b3eec5e441b64eca8d9f7600 Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Wed, 29 Oct 2025 17:34:45 +0100 Subject: [PATCH 2/5] Updated Handling --- src/DTO/Exception.php | 37 ------------------------- src/DTO/InnerException.php | 37 ------------------------- src/DTO/MFilesError.php | 24 +++++----------- src/Exceptions/MFilesErrorException.php | 3 +- 4 files changed, 9 insertions(+), 92 deletions(-) delete mode 100644 src/DTO/Exception.php delete mode 100644 src/DTO/InnerException.php diff --git a/src/DTO/Exception.php b/src/DTO/Exception.php deleted file mode 100644 index 1526652..0000000 --- a/src/DTO/Exception.php +++ /dev/null @@ -1,37 +0,0 @@ - $this->name, - 'message' => $this->message, - 'innerException' => $this->innerException?->toArray(), - ]; - } -} diff --git a/src/DTO/InnerException.php b/src/DTO/InnerException.php deleted file mode 100644 index 482a611..0000000 --- a/src/DTO/InnerException.php +++ /dev/null @@ -1,37 +0,0 @@ - $this->name, - 'message' => $this->message, - 'stackText' => $this->stackText, - 'errorCode' => $this->errorCode, - ]; - } -} diff --git a/src/DTO/MFilesError.php b/src/DTO/MFilesError.php index cd4583c..c5205b4 100644 --- a/src/DTO/MFilesError.php +++ b/src/DTO/MFilesError.php @@ -13,30 +13,23 @@ public function __construct( public readonly int $status, public readonly string $url, public readonly string $method, - public readonly ?Exception $exception, - public readonly ?string $stack, - public readonly string $message, - public readonly bool $isLoggedToVault, - public readonly bool $isLoggedToApplication, public readonly string $exceptionName, + public readonly string $exceptionMessage, + public readonly ?string $stack, ) {} public static function fromArray(array $data): self { - $exceptionData = Arr::get($data, 'Exception'); - $exception = $exceptionData ? Exception::fromArray($exceptionData) : null; + $exceptionData = Arr::get($data, 'Exception', []); return new self( errorCode: Arr::get($data, 'ErrorCode', ''), status: Arr::get($data, 'Status', 0), url: Arr::get($data, 'URL', ''), method: Arr::get($data, 'Method', ''), - exception: $exception, + exceptionName: Arr::get($exceptionData, 'Name', ''), + exceptionMessage: Arr::get($exceptionData, 'Message', ''), stack: Arr::get($data, 'Stack'), - message: Arr::get($data, 'Message', ''), - isLoggedToVault: Arr::get($data, 'IsLoggedToVault', false), - isLoggedToApplication: Arr::get($data, 'IsLoggedToApplication', false), - exceptionName: Arr::get($data, 'ExceptionName', ''), ); } @@ -47,12 +40,9 @@ public function toArray(): array 'status' => $this->status, 'url' => $this->url, 'method' => $this->method, - 'exception' => $this->exception?->toArray(), - 'stack' => $this->stack, - 'message' => $this->message, - 'isLoggedToVault' => $this->isLoggedToVault, - 'isLoggedToApplication' => $this->isLoggedToApplication, 'exceptionName' => $this->exceptionName, + 'exceptionMessage' => $this->exceptionMessage, + 'stack' => $this->stack, ]; } } diff --git a/src/Exceptions/MFilesErrorException.php b/src/Exceptions/MFilesErrorException.php index 2224f1d..644d5c3 100644 --- a/src/Exceptions/MFilesErrorException.php +++ b/src/Exceptions/MFilesErrorException.php @@ -12,6 +12,7 @@ final class MFilesErrorException extends BaseException public function __construct( public readonly MFilesError $error ) { - parent::__construct($error->message, $error->status); + parent::__construct($error->exceptionMessage, $error->status); } } + From 3d28dcdf83e27dae44ad3cf2d6defd88a752cc3b Mon Sep 17 00:00:00 2001 From: StanBarrows <10268813+StanBarrows@users.noreply.github.com> Date: Wed, 29 Oct 2025 16:35:12 +0000 Subject: [PATCH 3/5] Fix styling --- src/Exceptions/MFilesErrorException.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Exceptions/MFilesErrorException.php b/src/Exceptions/MFilesErrorException.php index 644d5c3..a6d313e 100644 --- a/src/Exceptions/MFilesErrorException.php +++ b/src/Exceptions/MFilesErrorException.php @@ -15,4 +15,3 @@ public function __construct( parent::__construct($error->exceptionMessage, $error->status); } } - From 0af9c030cce55a8f156763f1f84ae50488cc672e Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Wed, 29 Oct 2025 17:35:16 +0100 Subject: [PATCH 4/5] wip --- src/Exceptions/MFilesErrorException.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Exceptions/MFilesErrorException.php b/src/Exceptions/MFilesErrorException.php index 644d5c3..a6d313e 100644 --- a/src/Exceptions/MFilesErrorException.php +++ b/src/Exceptions/MFilesErrorException.php @@ -15,4 +15,3 @@ public function __construct( parent::__construct($error->exceptionMessage, $error->status); } } - From 2b40059bee406da763218c3035eb2827b303afd4 Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Wed, 29 Oct 2025 17:51:45 +0100 Subject: [PATCH 5/5] wip --- tests/Unit/DTO/MFilesErrorTest.php | 141 ++++++++++++++++++ .../Exceptions/MFilesErrorExceptionTest.php | 62 ++++++++ tests/Unit/Responses/ErrorResponseTest.php | 139 +++++++++++++++++ 3 files changed, 342 insertions(+) create mode 100644 tests/Unit/DTO/MFilesErrorTest.php create mode 100644 tests/Unit/Exceptions/MFilesErrorExceptionTest.php create mode 100644 tests/Unit/Responses/ErrorResponseTest.php diff --git a/tests/Unit/DTO/MFilesErrorTest.php b/tests/Unit/DTO/MFilesErrorTest.php new file mode 100644 index 0000000..ca551cc --- /dev/null +++ b/tests/Unit/DTO/MFilesErrorTest.php @@ -0,0 +1,141 @@ +errorCode)->toBe('ERROR_001'); + expect($error->status)->toBe(403); + expect($error->url)->toBe('/session/vaults'); + expect($error->method)->toBe('GET'); + expect($error->exceptionName)->toBe('UnauthorizedAccessException'); + expect($error->exceptionMessage)->toBe('Login to application failed'); + expect($error->stack)->toBe('Stack trace here'); +}); + +it('creates instance with null stack', function () { + $error = new MFilesError( + errorCode: 'ERROR_002', + status: 404, + url: '/objects/123', + method: 'GET', + exceptionName: 'NotFoundException', + exceptionMessage: 'Object not found', + stack: null + ); + + expect($error->stack)->toBeNull(); +}); + +it('creates instance from array with all properties', function () { + $data = [ + 'ErrorCode' => 'ERROR_003', + 'Status' => 500, + 'URL' => '/api/endpoint', + 'Method' => 'POST', + 'Exception' => [ + 'Name' => 'InternalServerError', + 'Message' => 'Internal server error occurred', + ], + 'Stack' => 'Detailed stack trace', + ]; + + $error = MFilesError::fromArray($data); + + expect($error->errorCode)->toBe('ERROR_003'); + expect($error->status)->toBe(500); + expect($error->url)->toBe('/api/endpoint'); + expect($error->method)->toBe('POST'); + expect($error->exceptionName)->toBe('InternalServerError'); + expect($error->exceptionMessage)->toBe('Internal server error occurred'); + expect($error->stack)->toBe('Detailed stack trace'); +}); + +it('creates instance from array with missing optional fields', function () { + $data = [ + 'Status' => 400, + 'URL' => '/test', + 'Method' => 'GET', + 'Exception' => [ + 'Name' => 'BadRequest', + 'Message' => 'Bad request', + ], + ]; + + $error = MFilesError::fromArray($data); + + expect($error->errorCode)->toBe(''); + expect($error->status)->toBe(400); + expect($error->url)->toBe('/test'); + expect($error->method)->toBe('GET'); + expect($error->exceptionName)->toBe('BadRequest'); + expect($error->exceptionMessage)->toBe('Bad request'); + expect($error->stack)->toBeNull(); +}); + +it('creates instance from array with empty exception object', function () { + $data = [ + 'ErrorCode' => 'ERROR_004', + 'Status' => 401, + 'URL' => '/auth', + 'Method' => 'POST', + 'Exception' => [], + 'Stack' => null, + ]; + + $error = MFilesError::fromArray($data); + + expect($error->exceptionName)->toBe(''); + expect($error->exceptionMessage)->toBe(''); +}); + +it('converts instance to array correctly', function () { + $error = new MFilesError( + errorCode: 'ERROR_005', + status: 422, + url: '/validate', + method: 'PUT', + exceptionName: 'ValidationError', + exceptionMessage: 'Validation failed', + stack: 'Error stack' + ); + + $array = $error->toArray(); + + expect($array)->toBe([ + 'errorCode' => 'ERROR_005', + 'status' => 422, + 'url' => '/validate', + 'method' => 'PUT', + 'exceptionName' => 'ValidationError', + 'exceptionMessage' => 'Validation failed', + 'stack' => 'Error stack', + ]); +}); + +it('converts instance to array with null stack', function () { + $error = new MFilesError( + errorCode: 'ERROR_006', + status: 500, + url: '/test', + method: 'DELETE', + exceptionName: 'ServerError', + exceptionMessage: 'Server error', + stack: null + ); + + $array = $error->toArray(); + + expect($array['stack'])->toBeNull(); +}); diff --git a/tests/Unit/Exceptions/MFilesErrorExceptionTest.php b/tests/Unit/Exceptions/MFilesErrorExceptionTest.php new file mode 100644 index 0000000..b4cf2f2 --- /dev/null +++ b/tests/Unit/Exceptions/MFilesErrorExceptionTest.php @@ -0,0 +1,62 @@ +error)->toBe($error); + expect($exception->getMessage())->toBe('Login to application failed'); + expect($exception->getCode())->toBe(403); +}); + +it('extends base Exception class', function () { + $error = new MFilesError( + errorCode: 'ERROR_002', + status: 404, + url: '/objects/123', + method: 'GET', + exceptionName: 'NotFoundException', + exceptionMessage: 'Object not found', + stack: null + ); + + $exception = new MFilesErrorException($error); + + expect($exception)->toBeInstanceOf(Exception::class); +}); + +it('can access error properties through exception', function () { + $error = new MFilesError( + errorCode: 'ERROR_003', + status: 500, + url: '/api/endpoint', + method: 'POST', + exceptionName: 'InternalServerError', + exceptionMessage: 'Internal server error occurred', + stack: 'Detailed stack trace' + ); + + $exception = new MFilesErrorException($error); + + expect($exception->error->errorCode)->toBe('ERROR_003'); + expect($exception->error->status)->toBe(500); + expect($exception->error->url)->toBe('/api/endpoint'); + expect($exception->error->method)->toBe('POST'); + expect($exception->error->exceptionName)->toBe('InternalServerError'); + expect($exception->error->exceptionMessage)->toBe('Internal server error occurred'); + expect($exception->error->stack)->toBe('Detailed stack trace'); +}); diff --git a/tests/Unit/Responses/ErrorResponseTest.php b/tests/Unit/Responses/ErrorResponseTest.php new file mode 100644 index 0000000..435e55a --- /dev/null +++ b/tests/Unit/Responses/ErrorResponseTest.php @@ -0,0 +1,139 @@ + 'ERROR_001', + 'Status' => 403, + 'URL' => '/session/vaults', + 'Method' => 'GET', + 'Exception' => [ + 'Name' => 'UnauthorizedAccessException', + 'Message' => 'Login to application failed', + ], + 'Stack' => 'Stack trace here', + ]; + + $request = new class extends Request + { + protected Method $method = Method::GET; + + public function resolveEndpoint(): string + { + return '/test'; + } + }; + + Saloon::fake([ + get_class($request) => MockResponse::make(status: 403, body: json_encode($json)), + ]); + + $connector = new class extends Connector + { + public function resolveBaseUrl(): string + { + return 'https://test.example.com'; + } + }; + + $response = $connector->send($request); + + $error = ErrorResponse::createDtoFromResponse($response); + + expect($error)->toBeInstanceOf(MFilesError::class); + expect($error->errorCode)->toBe('ERROR_001'); + expect($error->status)->toBe(403); + expect($error->url)->toBe('/session/vaults'); + expect($error->method)->toBe('GET'); + expect($error->exceptionName)->toBe('UnauthorizedAccessException'); + expect($error->exceptionMessage)->toBe('Login to application failed'); + expect($error->stack)->toBe('Stack trace here'); +}); + +it('handles response with missing optional fields', function () { + $json = [ + 'Status' => 400, + 'URL' => '/test', + 'Method' => 'POST', + 'Exception' => [ + 'Name' => 'BadRequest', + 'Message' => 'Bad request', + ], + ]; + + $request = new class extends Request + { + protected Method $method = Method::GET; + + public function resolveEndpoint(): string + { + return '/test'; + } + }; + + Saloon::fake([ + get_class($request) => MockResponse::make(status: 400, body: json_encode($json)), + ]); + + $connector = new class extends Connector + { + public function resolveBaseUrl(): string + { + return 'https://test.example.com'; + } + }; + + $response = $connector->send($request); + + $error = ErrorResponse::createDtoFromResponse($response); + + expect($error->errorCode)->toBe(''); + expect($error->stack)->toBeNull(); +}); + +it('handles empty exception object', function () { + $json = [ + 'Status' => 500, + 'URL' => '/api', + 'Method' => 'GET', + 'Exception' => [], + ]; + + $request = new class extends Request + { + protected Method $method = Method::GET; + + public function resolveEndpoint(): string + { + return '/test'; + } + }; + + Saloon::fake([ + get_class($request) => MockResponse::make(status: 500, body: json_encode($json)), + ]); + + $connector = new class extends Connector + { + public function resolveBaseUrl(): string + { + return 'https://test.example.com'; + } + }; + + $response = $connector->send($request); + + $error = ErrorResponse::createDtoFromResponse($response); + + expect($error->exceptionName)->toBe(''); + expect($error->exceptionMessage)->toBe(''); +});