Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions src/DTO/MFilesError.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace CodebarAg\MFiles\DTO;

use Illuminate\Support\Arr;

final class MFilesError
{
public function __construct(
public readonly string $errorCode,
public readonly int $status,
public readonly string $url,
public readonly string $method,
public readonly string $exceptionName,
public readonly string $exceptionMessage,
public readonly ?string $stack,
) {}

public static function fromArray(array $data): self
{
$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', ''),
exceptionName: Arr::get($exceptionData, 'Name', ''),
exceptionMessage: Arr::get($exceptionData, 'Message', ''),
stack: Arr::get($data, 'Stack'),
);
}

public function toArray(): array
{
return [
'errorCode' => $this->errorCode,
'status' => $this->status,
'url' => $this->url,
'method' => $this->method,
'exceptionName' => $this->exceptionName,
'exceptionMessage' => $this->exceptionMessage,
'stack' => $this->stack,
];
}
}
17 changes: 17 additions & 0 deletions src/Exceptions/MFilesErrorException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace CodebarAg\MFiles\Exceptions;

use CodebarAg\MFiles\DTO\MFilesError;
use Exception as BaseException;

final class MFilesErrorException extends BaseException
{
public function __construct(
public readonly MFilesError $error
) {
parent::__construct($error->exceptionMessage, $error->status);
}
}
4 changes: 4 additions & 0 deletions src/Responses/DownloadFileResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Missing blank line after the error check block. For consistency with other files in this PR (UploadFileResponse.php, ObjectPropertiesResponse.php, LogInToVaultResponse.php), add a blank line after line 17 before line 18.

Suggested change
}
}

Copilot uses AI. Check for mistakes.
$headers = $response->headers();
$fileContentType = $headers->get('Content-Type');
$fileSize = (int) $headers->get('Content-Length', 0);
Expand Down
16 changes: 16 additions & 0 deletions src/Responses/ErrorResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace CodebarAg\MFiles\Responses;

use CodebarAg\MFiles\DTO\MFilesError;
use Saloon\Http\Response;

final class ErrorResponse
{
public static function createDtoFromResponse(Response $response): MFilesError
{
return MFilesError::fromArray($response->json());
}
}
5 changes: 5 additions & 0 deletions src/Responses/LogInToVaultResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@

namespace CodebarAg\MFiles\Responses;

use CodebarAg\MFiles\Exceptions\MFilesErrorException;
use Illuminate\Support\Arr;
use Saloon\Http\Response;

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');
}
}
5 changes: 5 additions & 0 deletions src/Responses/ObjectPropertiesResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
5 changes: 5 additions & 0 deletions src/Responses/UploadFileResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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, '.'));
Expand Down
141 changes: 141 additions & 0 deletions tests/Unit/DTO/MFilesErrorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
<?php

declare(strict_types=1);

use CodebarAg\MFiles\DTO\MFilesError;

it('creates instance with all properties', function () {
$error = new MFilesError(
errorCode: 'ERROR_001',
status: 403,
url: '/session/vaults',
method: 'GET',
exceptionName: 'UnauthorizedAccessException',
exceptionMessage: 'Login to application failed',
stack: 'Stack trace here'
);

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('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();
});
62 changes: 62 additions & 0 deletions tests/Unit/Exceptions/MFilesErrorExceptionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

declare(strict_types=1);

use CodebarAg\MFiles\DTO\MFilesError;
use CodebarAg\MFiles\Exceptions\MFilesErrorException;

it('creates exception with MFilesError', function () {
$error = new MFilesError(
errorCode: 'ERROR_001',
status: 403,
url: '/session/vaults',
method: 'GET',
exceptionName: 'UnauthorizedAccessException',
exceptionMessage: 'Login to application failed',
stack: 'Stack trace here'
);

$exception = new MFilesErrorException($error);

expect($exception->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');
});
Loading