MonzoException   A
last analyzed

Complexity

Total Complexity 8

Size/Duplication

Total Lines 139
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 8
dl 0
loc 139
c 0
b 0
f 0
rs 10

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A getStatusCode() 0 3 2
A fromResponse() 0 8 1
A response() 0 8 1
A getExceptionType() 0 19 3
1
<?php
2
3
namespace Amelia\Monzo\Exceptions;
4
5
use RuntimeException;
6
use Psr\Http\Message\ResponseInterface;
7
use function Amelia\Monzo\json_decode_response;
8
9
class MonzoException extends RuntimeException
10
{
11
    /**
12
     * The JSON body of this exception, if any.
13
     *
14
     * @var array|null
15
     */
16
    public $body;
17
18
    /**
19
     * The response given, if any.
20
     *
21
     * @var \Psr\Http\Message\ResponseInterface|null
22
     */
23
    public $response;
24
25
    /**
26
     * An array of error codes mapped to exception types.
27
     *
28
     * @var array
29
     */
30
    protected static $errors = [
31
        'unauthorized' => AuthenticationException::class,
32
        'unauthorized.bad_access_token' => InvalidTokenException::class,
33
        'forbidden*' => AccessDeniedException::class,
34
        'bad_request*' => InvalidRequestException::class,
35
    ];
36
37
    /**
38
     * An array of status codes mapped to exception types.
39
     *
40
     * @var array
41
     */
42
    protected static $codes = [
43
        400 => InvalidRequestException::class,
44
        401 => InvalidTokenException::class,
45
        403 => AccessDeniedException::class,
46
        404 => NotFoundException::class,
47
        405 => MethodNotAllowedException::class,
48
        429 => RateLimitException::class,
49
        500 => self::class,
50
        504 => GatewayTimeoutException::class,
51
    ];
52
53
    /**
54
     * The HTTP Status code for this error.
55
     *
56
     * @var int
57
     */
58
    protected static $status = 500;
59
60
    /**
61
     * The message for this exception.
62
     *
63
     * @var string
64
     */
65
    protected static $statusMessage = 'Internal error from Monzo. Check https://status.monzo.com for more info.';
66
67
    /**
68
     * AuthenticationException constructor.
69
     *
70
     * @param string|null $message
71
     */
72
    public function __construct(string $message = null)
73
    {
74
        parent::__construct($message ?? static::$statusMessage, static::$status);
75
    }
76
77
    /**
78
     * Get the HTTP Status code for this error.
79
     *
80
     * @return int
81
     */
82
    public function getStatusCode()
83
    {
84
        return $this->response ? $this->response->getStatusCode() : static::$status;
85
    }
86
87
    /**
88
     * Set the response on this exception.
89
     *
90
     * @param \Psr\Http\Message\ResponseInterface $response
91
     * @param string $body
92
     * @return $this
93
     */
94
    public function response(ResponseInterface $response, string $body)
95
    {
96
        $this->response = $response;
97
        $this->body = json_decode_response($response, $body);
98
99
        $this->message = "{$this->body['message']} ({$this->body['code']})";
100
101
        return $this;
102
    }
103
104
    /**
105
     * Generate an exception from a response.
106
     *
107
     * @param \Psr\Http\Message\ResponseInterface $response
108
     * @param string $body
109
     * @param string $errorCode
110
     * @return static
111
     */
112
    public static function fromResponse(ResponseInterface $response, string $body, ?string $errorCode)
113
    {
114
        $class = static::getExceptionType($errorCode, $response->getStatusCode());
115
116
        /** @var \Amelia\Monzo\Exceptions\MonzoException $exception */
117
        $exception = new $class;
118
119
        return $exception->response($response, $body);
120
    }
121
122
    /**
123
     * Get the exception class from an underlying error.
124
     *
125
     * @param null|string $errorCode
126
     * @param int $statusCode
127
     * @return string
128
     */
129
    protected static function getExceptionType(?string $errorCode, int $statusCode)
130
    {
131
        // first, check error code.
132
        if ($errorCode !== null) {
133
            $errors = collect(static::$errors);
134
135
            // exact match first, followed by pattern match.
136
            $class = $errors->get($errorCode)
137
                ?? $errors->first(function ($value, string $key) use ($errorCode) {
138
                    return str_is($key, $errorCode);
139
                });
140
141
            if ($class !== null) {
142
                return $class;
143
            }
144
        }
145
146
        // now, check for code directly.
147
        return static::$codes[$statusCode] ?? self::class;
148
    }
149
}
150