Passed
Push — master ( 5620bf...f38109 )
by Eric
01:56
created

Utils::raw()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
ccs 2
cts 2
cp 1
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * This file is part of Esi\LibrariesIO.
7
 *
8
 * (c) 2023-2024 Eric Sizemore <https://github.com/ericsizemore>
9
 *
10
 * For the full copyright and license information, please view
11
 * the LICENSE file that was distributed with this source code.
12
 */
13
14
namespace Esi\LibrariesIO;
15
16
use Esi\LibrariesIO\Exception\InvalidEndpointException;
17
use Esi\LibrariesIO\Exception\InvalidEndpointOptionsException;
18
use InvalidArgumentException;
19
use JsonException;
20
use Psr\Http\Message\ResponseInterface;
21
use stdClass;
22
23
use function implode;
24
use function in_array;
25
use function json_decode;
26
use function ltrim;
27
use function str_contains;
28
use function str_ends_with;
29
use function str_replace;
30
use function strtoupper;
31
32
use const JSON_THROW_ON_ERROR;
33
34
/**
35
 * Utility class.
36
 */
37
final class Utils
38
{
39
    /**
40
     * @var array<string, array{format: string, options: array{}|array<string>, method: string}>
41
     */
42
    public static array $projectParameters = [
43
        'contributors'           => ['format' => ':platform/:name/contributors', 'options' => ['platform', 'name'], 'method' => 'get'],
44
        'dependencies'           => ['format' => ':platform/:name/:version/dependencies', 'options' => ['platform', 'name', 'version'], 'method' => 'get'],
45
        'dependent_repositories' => ['format' => ':platform/:name/dependent_repositories', 'options' => ['platform', 'name'], 'method' => 'get'],
46
        'dependents'             => ['format' => ':platform/:name/dependents', 'options' => ['platform', 'name'], 'method' => 'get'],
47
        'search'                 => ['format' => 'search', 'options' => ['query', 'sort'], 'method' => 'get'],
48
        'sourcerank'             => ['format' => ':platform/:name/sourcerank', 'options' => ['platform', 'name'], 'method' => 'get'],
49
        'project'                => ['format' => ':platform/:name', 'options' => ['platform', 'name'], 'method' => 'get'],
50
    ];
51
52
    /**
53
     * @var array<string, array{format: string, options: array{}|array<string>, method: string}>
54
     */
55
    public static array $repositoryParameters = [
56
        'dependencies' => ['format' => 'github/:owner/:name/dependencies', 'options' => ['owner', 'name'], 'method' => 'get'],
57
        'projects'     => ['format' => 'github/:owner/:name/projects', 'options' => ['owner', 'name'], 'method' => 'get'],
58
        'repository'   => ['format' => 'github/:owner/:name', 'options' => ['owner', 'name'], 'method' => 'get'],
59
    ];
60
61
    /**
62
     * @var array<string, array{format: string, options: array{}|array<string>, method: string}>
63
     */
64
    public static array $subscriptionParameters = [
65
        'subscribe'   => ['format' => 'subscriptions/:platform/:name', 'options' => ['platform', 'name', 'include_prerelease'], 'method' => 'post'],
66
        'check'       => ['format' => 'subscriptions/:platform/:name', 'options' => ['platform', 'name'], 'method' => 'get'],
67
        'update'      => ['format' => 'subscriptions/:platform/:name', 'options' => ['platform', 'name', 'include_prerelease'], 'method' => 'put'],
68
        'unsubscribe' => ['format' => 'subscriptions/:platform/:name', 'options' => ['platform', 'name'], 'method' => 'delete'],
69
    ];
70
71
    /**
72
     * @var array<string, array{format: string, options: array{}|array<string>, method: string}>
73
     */
74
    public static array $userParameters = [
75
        'dependencies'             => ['format' => 'github/:login/dependencies', 'options' => ['login'], 'method' => 'get'],
76
        'package_contributions'    => ['format' => 'github/:login/project-contributions', 'options' => ['login'], 'method' => 'get'],
77
        'packages'                 => ['format' => 'github/:login/projects', 'options' => ['login'], 'method' => 'get'],
78
        'repositories'             => ['format' => 'github/:login/repositories', 'options' => ['login'], 'method' => 'get'],
79
        'repository_contributions' => ['format' => 'github/:login/repository-contributions', 'options' => ['login'], 'method' => 'get'],
80
        'subscriptions'            => ['format' => 'subscriptions', 'options' => [], 'method' => 'get'],
81
        'user'                     => ['format' => 'github/:login', 'options' => ['login'], 'method' => 'get'],
82
    ];
83
84
    /**
85
     * @param array<string, int|string> $options
86
     *
87
     * @return array{0: string, 1: array{}|array<string>, 2: string}
88
     *
89
     * @throws InvalidEndpointException
90
     * @throws InvalidEndpointOptionsException
91
     */
92 35
    public static function endpointParameters(string $endpoint, string $subset, array $options): array
93
    {
94 35
        $parameters = match($endpoint) {
95 35
            'project'      => Utils::$projectParameters[$subset] ?? throw new InvalidEndpointException('Invalid endpoint subset specified.'),
96 35
            'repository'   => Utils::$repositoryParameters[$subset] ?? throw new InvalidEndpointException('Invalid endpoint subset specified.'),
97 35
            'user'         => Utils::$userParameters[$subset] ?? throw new InvalidEndpointException('Invalid endpoint subset specified.'),
98 35
            'subscription' => Utils::$subscriptionParameters[$subset] ?? throw new InvalidEndpointException('Invalid endpoint subset specified.'),
99 35
            default        => throw new InvalidEndpointException('Invalid endpoint subset specified.')
100 35
        };
101
102 31
        foreach ($parameters['options'] as $endpointOption) {
103 30
            if (!isset($options[$endpointOption])) {
104 4
                throw new InvalidEndpointOptionsException(
105 4
                    '$options has not specified all required parameters. Parameters needed: ' . implode(', ', $parameters['options'])
106 4
                );
107
            }
108
        }
109
110 27
        $parameters['format'] = Utils::processEndpointFormat($parameters['format'], $options);
111
112 27
        return [$parameters['format'], $parameters['options'], $parameters['method']];
113
    }
114
115 35
    public static function normalizeEndpoint(string | null $endpoint, string $apiUrl): string
116
    {
117 35
        $endpoint = ltrim((string) $endpoint, '/');
118
119 35
        if (!str_ends_with($apiUrl, '/')) {
120 2
            $endpoint = '/' . $endpoint;
121
        }
122
123 35
        return $endpoint;
124
    }
125
126 38
    public static function normalizeMethod(string $method): string
127
    {
128 38
        static $availableMethods = ['GET', 'DELETE', 'POST', 'PUT',];
129
130 38
        $method = strtoupper($method);
131
132
        // Check for a valid method
133 38
        if (!in_array($method, $availableMethods, true)) {
134 3
            $method = 'GET';
135
        }
136
137 38
        return $method;
138
    }
139
140 3
    public static function raw(ResponseInterface $response): string
141
    {
142 3
        return $response->getBody()->getContents();
143
    }
144
145
    /**
146
     * @param array<string, int|string> $options
147
     *
148
     * @return array<string, int|string>
149
     */
150 2
    public static function searchAdditionalParams(array $options): array
151
    {
152 2
        $additionalParams = [];
153
154 2
        foreach (['languages', 'licenses', 'keywords', 'platforms'] as $option) {
155 2
            if (isset($options[$option])) {
156 2
                $additionalParams[$option] = $options[$option];
157
            }
158
        }
159
160 2
        return $additionalParams;
161
    }
162
163 2
    public static function searchVerifySortOption(string $sort): string
164
    {
165 2
        static $sortOptions = [
166 2
            'rank', 'stars', 'dependents_count',
167 2
            'dependent_repos_count', 'latest_release_published_at',
168 2
            'contributions_count', 'created_at',
169 2
        ];
170
171 2
        if (!in_array($sort, $sortOptions, true)) {
172 1
            return 'rank';
173
        }
174
175 1
        return $sort;
176
    }
177
178
    /**
179
     * @return array<mixed>
180
     *
181
     * @throws JsonException
182
     */
183 1
    public static function toArray(ResponseInterface $response): array
184
    {
185
        /** @var array<mixed> $json * */
186 1
        $json = json_decode(Utils::raw($response), true, flags: JSON_THROW_ON_ERROR);
187
188 1
        return $json;
189
    }
190
191
    /**
192
     * @throws JsonException
193
     */
194 1
    public static function toObject(ResponseInterface $response): stdClass
195
    {
196
        /** @var stdClass $json * */
197 1
        $json = json_decode(Utils::raw($response), false, flags: JSON_THROW_ON_ERROR);
198
199 1
        return $json;
200
    }
201
202
    /**
203
     * Each endpoint class will have a 'subset' of endpoints that fall under it. This
204
     * function handles returning a formatted endpoint for the Client.
205
     *
206
     * @param array<string, int|string> $options
207
     */
208 27
    private static function processEndpointFormat(string $format, array $options): string
209
    {
210 27
        if (str_contains($format, ':') === false) {
211 3
            return $format;
212
        }
213
214 24
        foreach ($options as $key => $val) {
215 24
            if (in_array($key, ['page', 'per_page'], true)) {
216 3
                continue;
217
            }
218
219
            /** @var string $val * */
220 24
            $format = str_replace(':' . $key, $val, $format);
221
        }
222
223 24
        return $format;
224
    }
225
}
226