Passed
Pull Request — master (#82)
by Joao
01:44
created

Schema::prepareToValidateArguments()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 8
c 0
b 0
f 0
dl 0
loc 16
rs 10
cc 3
nc 4
nop 4
1
<?php
2
3
namespace ByJG\ApiTools\Base;
4
5
use ByJG\ApiTools\Exception\DefinitionNotFoundException;
6
use ByJG\ApiTools\Exception\HttpMethodNotFoundException;
7
use ByJG\ApiTools\Exception\InvalidDefinitionException;
8
use ByJG\ApiTools\Exception\InvalidRequestException;
9
use ByJG\ApiTools\Exception\NotMatchedException;
10
use ByJG\ApiTools\Exception\PathNotFoundException;
11
use ByJG\ApiTools\OpenApi\OpenApiSchema;
12
use ByJG\ApiTools\Swagger\SwaggerSchema;
13
use ByJG\Util\Uri;
14
use InvalidArgumentException;
15
16
abstract class Schema
17
{
18
    protected array $jsonFile;
19
    protected bool $allowNullValues = false;
20
    protected string $specificationVersion;
21
22
    const SWAGGER_PATHS = "paths";
23
    const SWAGGER_PARAMETERS = "parameters";
24
    const SWAGGER_COMPONENTS = "components";
25
26
    /**
27
     * Returns the major specification version
28
     * @return string
29
     */
30
    public function getSpecificationVersion(): string
31
    {
32
        return $this->specificationVersion;
33
    }
34
35
    /**
36
     * Factory function for schemata.
37
     *
38
     * Initialize with schema data, which can be a PHP array or encoded as JSON.
39
     * This determines the type of the schema from the given data.
40
     *
41
     * @param array|string $data
42
     * @param bool $extraArgs
43
     * @return Schema
44
     */
45
    public static function getInstance(array|string $data, bool $extraArgs = false): Schema
46
    {
47
        // when given a string, decode from JSON
48
        if (is_string($data)) {
0 ignored issues
show
introduced by
The condition is_string($data) is always false.
Loading history...
49
            $data = json_decode($data, true);
50
        }
51
        // check which type of file we got and dispatch to derived class constructor
52
        if (isset($data['swagger'])) {
53
            return new SwaggerSchema($data, $extraArgs);
54
        }
55
        if (isset($data['openapi'])) {
56
            return new OpenApiSchema($data);
57
        }
58
59
        throw new InvalidArgumentException('failed to determine schema type from data');
60
    }
61
62
    /**
63
     * @param string $path
64
     * @param string $method
65
     * @return mixed
66
     * @throws DefinitionNotFoundException
67
     * @throws HttpMethodNotFoundException
68
     * @throws InvalidDefinitionException
69
     * @throws InvalidRequestException
70
     * @throws NotMatchedException
71
     * @throws PathNotFoundException
72
     */
73
    protected function parsePathRequest(string $path, string $method, ?string $queryString = null): mixed
74
    {
75
        $method = strtolower($method);
76
77
        $path = preg_replace('~^' . $this->getBasePath() . '~', '', $path);
78
79
        $uri = new Uri($path);
80
81
        // Try direct match
82
        if (isset($this->jsonFile[self::SWAGGER_PATHS][$uri->getPath()])) {
83
            if (isset($this->jsonFile[self::SWAGGER_PATHS][$uri->getPath()][$method])) {
84
85
                if (!is_null($queryString)) {
86
                    parse_str($queryString, $matches);
87
                    $this->prepareToValidateArguments($uri->getPath(), $method, 'query', $matches);
88
                }
89
90
                return $this->jsonFile[self::SWAGGER_PATHS][$uri->getPath()][$method];
91
            }
92
            throw new HttpMethodNotFoundException("The http method '$method' not found in '$path'");
93
        }
94
95
        // Try inline parameter
96
        foreach (array_keys($this->jsonFile[self::SWAGGER_PATHS]) as $pathItem) {
97
            if (!str_contains($pathItem, '{')) {
98
                continue;
99
            }
100
101
            $pathItemPattern = '~^' . preg_replace('~{(.*?)}~', '(?<\1>[^/]+)', $pathItem) . '$~';
102
103
            $matches = [];
104
            if (empty($uri->getPath())) {
105
                throw new InvalidRequestException('The path is empty');
106
            }
107
            if (preg_match($pathItemPattern, $uri->getPath(), $matches)) {
108
                $pathDef = $this->jsonFile[self::SWAGGER_PATHS][$pathItem];
109
                if (!isset($pathDef[$method])) {
110
                    throw new HttpMethodNotFoundException("The http method '$method' not found in '$path'");
111
                }
112
113
                $this->prepareToValidateArguments($pathItem, $method, 'path', $matches);
114
115
                if (!is_null($queryString)) {
116
                    parse_str($queryString, $queryParsed);
117
                    $this->prepareToValidateArguments($pathItem, $method, 'query', $queryParsed);
118
                }
119
120
                return $pathDef[$method];
121
            }
122
        }
123
124
        throw new PathNotFoundException('Path "' . $path . '" not found');
125
    }
126
127
    public function getPathDefinition(string $path, string $method): mixed
128
    {
129
        return $this->parsePathRequest($path, $method);
130
    }
131
132
    /**
133
     * @throws DefinitionNotFoundException
134
     * @throws NotMatchedException
135
     * @throws InvalidDefinitionException
136
     */
137
    protected function prepareToValidateArguments(string $path, string $method, string $parameterIn, $matches): void
138
    {
139
        $pathDef = $this->jsonFile[self::SWAGGER_PATHS][$path];
140
141
        $parametersPathMethod = [];
142
        $parametersPath = [];
143
144
        if (isset($pathDef[$method][self::SWAGGER_PARAMETERS])) {
145
            $parametersPathMethod = $pathDef[$method][self::SWAGGER_PARAMETERS];
146
        }
147
148
        if (isset($pathDef[self::SWAGGER_PARAMETERS])) {
149
            $parametersPath = $pathDef[self::SWAGGER_PARAMETERS];
150
        }
151
152
        $this->validateArguments($parameterIn, array_merge($parametersPathMethod, $parametersPath), $matches);
153
    }
154
155
    /**
156
     * @param string $path
157
     * @param string $method
158
     * @param int $status
159
     * @return Body
160
     * @throws HttpMethodNotFoundException
161
     * @throws InvalidDefinitionException
162
     * @throws InvalidRequestException
163
     * @throws PathNotFoundException
164
     */
165
    public function getResponseParameters(string $path, string $method, int $status): Body
166
    {
167
        $structure = $this->getPathDefinition($path, $method);
168
169
        if (!isset($structure['responses']["200"])) {
170
            $structure['responses']["200"] = ["description" => "Auto Generated OK"];
171
        }
172
173
        $verifyStatus = $status;
174
        if (!isset($structure['responses'][$verifyStatus])) {
175
            $verifyStatus = 'default';
176
            if (!isset($structure['responses'][$verifyStatus])) {
177
                throw new InvalidDefinitionException("Could not found status code '$status' in '$path' and '$method'");
178
            }
179
        }
180
181
        return $this->getResponseBody($this, "$method $status $path", $structure['responses'][$verifyStatus]);
182
    }
183
184
    /**
185
     * OpenApi 2.0 doesn't describe null values, so this flag defines,
186
     * if match is ok when one of property
187
     *
188
     * @return bool
189
     */
190
    public function isAllowNullValues(): bool
191
    {
192
        return $this->allowNullValues;
193
    }
194
195
    /**
196
     * @return string
197
     */
198
    abstract public function getServerUrl(): string;
199
200
    /**
201
     * @param string $parameterIn
202
     * @param array $parameters
203
     * @param array $arguments
204
     * @throws DefinitionNotFoundException
205
     * @throws InvalidDefinitionException
206
     * @throws NotMatchedException
207
     */
208
    abstract protected function validateArguments(string $parameterIn, array $parameters, array $arguments): void;
209
210
    abstract public function getBasePath(): string;
211
212
    /**
213
     * @param $name
214
     * @return mixed
215
     * @throws DefinitionNotFoundException
216
     * @throws InvalidDefinitionException
217
     */
218
    abstract public function getDefinition($name): mixed;
219
220
    /**
221
     * @param string $path
222
     * @param string $method
223
     * @return Body
224
     * @throws HttpMethodNotFoundException
225
     * @throws PathNotFoundException
226
     * @throws DefinitionNotFoundException
227
     * @throws HttpMethodNotFoundException
228
     * @throws InvalidDefinitionException
229
     * @throws NotMatchedException
230
     * @throws PathNotFoundException
231
     */
232
    abstract public function getRequestParameters(string $path, string $method, ?string $queryString = null): Body;
233
234
    /**
235
     * @param Schema $schema
236
     * @param string $name
237
     * @param array $structure
238
     * @param bool $allowNullValues
239
     * @return Body
240
     */
241
    abstract public function getResponseBody(Schema $schema, string $name, array $structure, bool $allowNullValues = false): Body;
242
}
243