Completed
Push — master ( c7116d...4968a9 )
by Anton
03:06
created

PublicAPIClient   B

Complexity

Total Complexity 26

Size/Duplication

Total Lines 190
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 16

Importance

Changes 0
Metric Value
wmc 26
c 0
b 0
f 0
lcom 1
cbo 16
dl 0
loc 190
rs 8.4614

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 2
D send() 0 45 9
B readJson() 0 21 5
A ping() 0 9 3
A sendEvent() 0 14 4
B makeDecision() 0 24 3
1
<?php
2
3
namespace Covery\Client;
4
5
use Covery\Client\Envelopes\ValidatorV1;
6
use Covery\Client\Requests\Decision;
7
use Covery\Client\Requests\Event;
8
use Covery\Client\Requests\Ping;
9
use Psr\Http\Message\RequestInterface;
10
use Psr\Log\LoggerInterface;
11
use Psr\Log\NullLogger;
12
13
class PublicAPIClient
14
{
15
    /**
16
     * @var CredentialsInterface
17
     */
18
    private $credentials;
19
20
    /**
21
     * @var TransportInterface
22
     */
23
    private $transport;
24
25
    /**
26
     * @var LoggerInterface
27
     */
28
    private $logger;
29
30
    /**
31
     * @var ValidatorV1
32
     */
33
    private $validator;
34
35
    /**
36
     * Client constructor.
37
     * @param CredentialsInterface $credentials
38
     * @param TransportInterface $transport
39
     * @param LoggerInterface|null $logger
40
     */
41
    public function __construct(
42
        CredentialsInterface $credentials,
43
        TransportInterface $transport,
44
        LoggerInterface $logger = null
45
    ) {
46
        $this->credentials = $credentials;
47
        $this->transport = $transport;
48
        $this->logger = $logger === null ? new NullLogger() : $logger;
49
        $this->validator = new ValidatorV1();
50
    }
51
52
    private function send(RequestInterface $request)
53
    {
54
        $request = $this->credentials->signRequest($request);
55
        try {
56
            $this->logger->info('Sending request to ' . $request->getUri());
57
            $before = microtime(true);
58
            $response = $this->transport->send($request);
59
            $this->logger->info(sprintf('Request done in %.2f', microtime(true) - $before));
60
        } catch (\Exception $inner) {
61
            $this->logger->error($inner->getMessage(), ['exception' => $inner]);
62
            // Wrapping exception
63
            throw new IoException('Error sending request', 0, $inner);
64
        }
65
        $code = $response->getStatusCode();
66
        $this->logger->debug('Received status code ' . $code);
67
68
        if ($code >= 400) {
69
            // Analyzing response
70
            if ($response->hasHeader('X-Maxwell-Status') && $response->hasHeader('X-Maxwell-Error-Message')) {
71
                // Extended data available
72
                $message = $response->getHeaderLine('X-Maxwell-Error-Message');
73
                $type = $response->getHeaderLine('X-Maxwell-Error-Type');
74
                if (strpos($type, 'AuthorizationRequiredException') !== false) {
75
                    $this->logger->error('Authentication failure ' . $message);
76
                    throw new AuthException($message, $code);
77
                }
78
79
                switch ($message) {
80
                    case 'Empty auth token':
81
                    case 'Empty signature':
82
                    case 'Empty nonce':
83
                        $this->logger->error('Authentication failure ' . $message);
84
                        throw new AuthException($message, $code);
85
                }
86
87
                $this->logger->error('Covery error ' . $message);
88
                throw new DeliveredException($message, $code);
89
            }
90
91
92
            throw new Exception("Communication failed with status code {$code}");
93
        }
94
95
        return $response->getBody()->getContents();
96
    }
97
98
    /**
99
     * Utility method, that reads JSON data
100
     *
101
     * @param $string
102
     * @return mixed|null
103
     * @throws Exception
104
     */
105
    private function readJson($string)
106
    {
107
        if (!is_string($string)) {
108
            throw new Exception("Unable to read JSON - not a string received");
109
        }
110
        if (strlen($string) === 0) {
111
            return null;
112
        }
113
114
        $data = json_decode($string, true);
115
        if ($data === null) {
116
            $message = 'Unable to decode JSON';
117
            if (function_exists('json_last_error_msg')) {
118
                $message = json_last_error_msg();
119
            }
120
121
            throw new Exception($message);
122
        }
123
124
        return $data;
125
    }
126
127
    /**
128
     * Sends request to Covery and returns access level, associated with
129
     * used credentials
130
     *
131
     * This method can be used for Covery health check and availability
132
     * On any problem (network, credentials, server side) this method
133
     * will throw an exception
134
     *
135
     * @return string
136
     * @throws Exception
137
     */
138
    public function ping()
139
    {
140
        $data = $this->readJson($this->send(new Ping()));
141
        if (!is_array($data) || !isset($data['level'])) {
142
            throw new Exception("Malformed response");
143
        }
144
145
        return $data['level'];
146
    }
147
148
    /**
149
     * Sends envelope to Covery and returns it's ID on Covery side
150
     * Before sending, validation is performed
151
     *
152
     * @param EnvelopeInterface $envelope
153
     * @return int
154
     * @throws Exception
155
     */
156
    public function sendEvent(EnvelopeInterface $envelope)
157
    {
158
        // Validating
159
        $this->validator->validate($envelope);
160
161
        // Sending
162
        $data = $this->readJson($this->send(new Event($envelope)));
163
164
        if (!is_array($data) || !isset($data['requestId']) || !is_int($data['requestId'])) {
165
            throw new Exception("Malformed response");
166
        }
167
168
        return $data['requestId'];
169
    }
170
171
    /**
172
     * Sends envelope to Covery for analysis
173
     *
174
     * @param EnvelopeInterface $envelope
175
     * @return Result
176
     * @throws Exception
177
     */
178
    public function makeDecision(EnvelopeInterface $envelope)
179
    {
180
        // Validating
181
        $this->validator->validate($envelope);
182
183
        // Sending
184
        $data = $this->readJson($this->send(new Decision($envelope)));
185
186
        if (!is_array($data)) {
187
            throw new Exception("Malformed response");
188
        }
189
190
        try {
191
            return new Result(
192
                $data['requestId'],
193
                $data['score'],
194
                $data['accept'],
195
                $data['reject'],
196
                $data['manual']
197
            );
198
        } catch (\Exception $error) {
199
            throw new Exception('Malformed response', 0, $error);
200
        }
201
    }
202
}
203