Passed
Pull Request — master (#84)
by Joao
01:47
created

Schema::fromFile()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
c 0
b 0
f 0
dl 0
loc 10
rs 10
cc 3
nc 3
nop 2
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
     * Create schema from JSON string.
37
     *
38
     * @param string $jsonString JSON-encoded OpenAPI/Swagger specification
39
     * @param bool $allowNullValues Whether to allow null values (Swagger 2.0 only)
40
     * @return SwaggerSchema|OpenApiSchema
41
     * @throws InvalidArgumentException
42
     */
43
    public static function fromJson(string $jsonString, bool $allowNullValues = false): SwaggerSchema|OpenApiSchema
44
    {
45
        $data = json_decode($jsonString, true);
46
        if ($data === null) {
47
            throw new InvalidArgumentException('Invalid JSON provided to fromJson()');
48
        }
49
        return self::fromArray($data, $allowNullValues);
50
    }
51
52
    /**
53
     * Create schema from file path.
54
     *
55
     * @param string $filePath Path to JSON file containing OpenAPI/Swagger specification
56
     * @param bool $allowNullValues Whether to allow null values (Swagger 2.0 only)
57
     * @return SwaggerSchema|OpenApiSchema
58
     * @throws InvalidArgumentException
59
     */
60
    public static function fromFile(string $filePath, bool $allowNullValues = false): SwaggerSchema|OpenApiSchema
61
    {
62
        if (!file_exists($filePath)) {
63
            throw new InvalidArgumentException("File not found: $filePath");
64
        }
65
        $jsonString = file_get_contents($filePath);
66
        if ($jsonString === false) {
67
            throw new InvalidArgumentException("Failed to read file: $filePath");
68
        }
69
        return self::fromJson($jsonString, $allowNullValues);
70
    }
71
72
    /**
73
     * Create schema from array.
74
     *
75
     * @param array $data PHP array containing OpenAPI/Swagger specification
76
     * @param bool $allowNullValues Whether to allow null values (Swagger 2.0 only)
77
     * @return SwaggerSchema|OpenApiSchema
78
     * @throws InvalidArgumentException
79
     */
80
    public static function fromArray(array $data, bool $allowNullValues = false): SwaggerSchema|OpenApiSchema
81
    {
82
        // check which type of schema we have and dispatch to derived class constructor
83
        if (isset($data['swagger'])) {
84
            return new SwaggerSchema($data, $allowNullValues);
85
        }
86
        if (isset($data['openapi'])) {
87
            return new OpenApiSchema($data);
88
        }
89
90
        throw new InvalidArgumentException('Failed to determine schema type from data. Expected "swagger" or "openapi" property.');
91
    }
92
93
    /**
94
     * Factory function for schemata.
95
     *
96
     * Initialize with schema data, which can be a PHP array or encoded as JSON.
97
     * This determines the type of the schema from the given data.
98
     *
99
     * @param array|string $data
100
     * @param bool $extraArgs
101
     * @return SwaggerSchema|OpenApiSchema
102
     * @deprecated Since version 6.0, use fromJson(), fromArray(), or fromFile() instead. Will be removed in version 7.0
103
     */
104
    public static function getInstance(array|string $data, bool $extraArgs = false): SwaggerSchema|OpenApiSchema
105
    {
106
        // when given a string, decode from JSON
107
        if (is_string($data)) {
0 ignored issues
show
introduced by
The condition is_string($data) is always false.
Loading history...
108
            return self::fromJson($data, $extraArgs);
109
        }
110
        return self::fromArray($data, $extraArgs);
111
    }
112
113
    /**
114
     * @param string $path
115
     * @param string $method
116
     * @return mixed
117
     * @throws DefinitionNotFoundException
118
     * @throws HttpMethodNotFoundException
119
     * @throws InvalidDefinitionException
120
     * @throws InvalidRequestException
121
     * @throws NotMatchedException
122
     * @throws PathNotFoundException
123
     */
124
    public function getPathDefinition(string $path, string $method): mixed
125
    {
126
        $method = strtolower($method);
127
128
        $path = preg_replace('~^' . $this->getBasePath() . '~', '', $path);
129
130
        $uri = new Uri($path);
131
132
        // Try direct match
133
        if (isset($this->jsonFile[self::SWAGGER_PATHS][$uri->getPath()])) {
134
            if (isset($this->jsonFile[self::SWAGGER_PATHS][$uri->getPath()][$method])) {
135
                return $this->jsonFile[self::SWAGGER_PATHS][$uri->getPath()][$method];
136
            }
137
            throw new HttpMethodNotFoundException("The http method '$method' not found in '$path'");
138
        }
139
140
        // Try inline parameter
141
        foreach (array_keys($this->jsonFile[self::SWAGGER_PATHS]) as $pathItem) {
142
            if (!str_contains($pathItem, '{')) {
143
                continue;
144
            }
145
146
            $pathItemPattern = '~^' . preg_replace('~{(.*?)}~', '(?<\1>[^/]+)', $pathItem) . '$~';
147
148
            $matches = [];
149
            if (empty($uri->getPath())) {
150
                throw new InvalidRequestException('The path is empty');
151
            }
152
            if (preg_match($pathItemPattern, $uri->getPath(), $matches)) {
153
                $pathDef = $this->jsonFile[self::SWAGGER_PATHS][$pathItem];
154
                if (!isset($pathDef[$method])) {
155
                    throw new HttpMethodNotFoundException("The http method '$method' not found in '$path'");
156
                }
157
158
                $parametersPathMethod = [];
159
                $parametersPath = [];
160
161
                if (isset($pathDef[$method][self::SWAGGER_PARAMETERS])) {
162
                    $parametersPathMethod = $pathDef[$method][self::SWAGGER_PARAMETERS];
163
                }
164
165
                if (isset($pathDef[self::SWAGGER_PARAMETERS])) {
166
                    $parametersPath = $pathDef[self::SWAGGER_PARAMETERS];
167
                }
168
169
                $this->validateArguments('path', array_merge($parametersPathMethod, $parametersPath), $matches);
170
171
                return $pathDef[$method];
172
            }
173
        }
174
175
        throw new PathNotFoundException('Path "' . $path . '" not found');
176
    }
177
178
    /**
179
     * @param string $path
180
     * @param string $method
181
     * @param int $status
182
     * @return Body
183
     * @throws DefinitionNotFoundException
184
     * @throws HttpMethodNotFoundException
185
     * @throws InvalidDefinitionException
186
     * @throws InvalidRequestException
187
     * @throws NotMatchedException
188
     * @throws PathNotFoundException
189
     */
190
    public function getResponseParameters(string $path, string $method, int $status): Body
191
    {
192
        $structure = $this->getPathDefinition($path, $method);
193
194
        if (!isset($structure['responses']["200"])) {
195
            $structure['responses']["200"] = ["description" => "Auto Generated OK"];
196
        }
197
198
        $verifyStatus = $status;
199
        if (!isset($structure['responses'][$verifyStatus])) {
200
            $verifyStatus = 'default';
201
            if (!isset($structure['responses'][$verifyStatus])) {
202
                throw new InvalidDefinitionException("Could not found status code '$status' in '$path' and '$method'");
203
            }
204
        }
205
206
        return $this->getResponseBody($this, "$method $status $path", $structure['responses'][$verifyStatus]);
207
    }
208
209
    /**
210
     * OpenApi 2.0 doesn't describe null values, so this flag defines,
211
     * if match is ok when one of property
212
     *
213
     * @return bool
214
     */
215
    public function isAllowNullValues(): bool
216
    {
217
        return $this->allowNullValues;
218
    }
219
220
    /**
221
     * @return string
222
     */
223
    abstract public function getServerUrl(): string;
224
225
    /**
226
     * @param string $parameterIn
227
     * @param array $parameters
228
     * @param array $arguments
229
     * @throws DefinitionNotFoundException
230
     * @throws InvalidDefinitionException
231
     * @throws NotMatchedException
232
     */
233
    abstract protected function validateArguments(string $parameterIn, array $parameters, array $arguments): void;
234
235
    abstract public function getBasePath(): string;
236
237
    /**
238
     * @param string $name
239
     * @return mixed
240
     * @throws DefinitionNotFoundException
241
     * @throws InvalidDefinitionException
242
     */
243
    abstract public function getDefinition(string $name): mixed;
244
245
    /**
246
     * @param string $path
247
     * @param string $method
248
     * @return Body
249
     * @throws HttpMethodNotFoundException
250
     * @throws PathNotFoundException
251
     * @throws DefinitionNotFoundException
252
     * @throws HttpMethodNotFoundException
253
     * @throws InvalidDefinitionException
254
     * @throws NotMatchedException
255
     * @throws PathNotFoundException
256
     */
257
    abstract public function getRequestParameters(string $path, string $method): Body;
258
259
    /**
260
     * @param Schema $schema
261
     * @param string $name
262
     * @param array $structure
263
     * @param bool $allowNullValues
264
     * @return Body
265
     */
266
    abstract public function getResponseBody(Schema $schema, string $name, array $structure, bool $allowNullValues = false): Body;
267
}
268