Completed
Push — revert-160-fake_json ( 3c4280 )
by Akihito
06:09 queued 04:40
created

JsonSchemaInterceptor::fakeResponse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 5
cts 5
cp 1
rs 9.9666
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BEAR\Resource\Interceptor;
6
7
use BEAR\Resource\Annotation\JsonSchema;
8
use BEAR\Resource\Code;
9
use BEAR\Resource\Exception\JsonSchemaErrorException;
10
use BEAR\Resource\Exception\JsonSchemaException;
11
use BEAR\Resource\Exception\JsonSchemaNotFoundException;
12
use BEAR\Resource\ResourceObject;
13
use function is_string;
14
use JsonSchema\Constraints\Constraint;
15
use JsonSchema\Validator;
16
use Ray\Aop\MethodInterceptor;
17
use Ray\Aop\MethodInvocation;
18
use Ray\Aop\ReflectionMethod;
19
use Ray\Aop\WeavedInterface;
20
use Ray\Di\Di\Named;
21
22
final class JsonSchemaInterceptor implements MethodInterceptor
23
{
24
    /**
25
     * @var string
26
     */
27
    private $schemaDir;
28
29
    /**
30
     * @var string
31
     */
32
    private $validateDir;
33
34
    /**
35
     * @var null|string
36
     */
37
    private $schemaHost;
38
39
    /**
40
     * @Named("schemaDir=json_schema_dir,validateDir=json_validate_dir,schemaHost=json_schema_host")
41
     */
42
    public function __construct(string $schemaDir, string $validateDir, string $schemaHost = null)
43
    {
44
        $this->schemaDir = $schemaDir;
45
        $this->validateDir = $validateDir;
46
        $this->schemaHost = $schemaHost;
47
    }
48
49
    /**
50
     * {@inheritdoc}
51
     */
52
    public function invoke(MethodInvocation $invocation)
53
    {
54
        /** @var ReflectionMethod $method */
55
        $method = $invocation->getMethod();
56
        /** @var JsonSchema $jsonSchema */
57
        $jsonSchema = $method->getAnnotation(JsonSchema::class);
58
        if ($jsonSchema->params) {
59
            $arguments = $this->getNamedArguments($invocation);
60
            $this->validateRequest($jsonSchema, $arguments);
61
        }
62
        /** @var ResourceObject $ro */
63
        $ro = $invocation->proceed();
64
        if ($ro->code === 200 || $ro->code == 201) {
65
            $this->validateResponse($jsonSchema, $ro);
66
        }
67
        if (is_string($this->schemaHost)) {
68
            $ro->headers['Link'] = sprintf('<%s%s>; rel="describedby"', $this->schemaHost, $jsonSchema->schema);
69
        }
70
71
        return $ro;
72
    }
73
74
    private function validateRequest(JsonSchema $jsonSchema, array $arguments)
75
    {
76
        $schemaFile = $this->validateDir . '/' . $jsonSchema->params;
77
        $this->validateFileExists($schemaFile);
78
        $this->validate($arguments, $schemaFile);
79
    }
80
81
    private function validateResponse(JsonSchema $jsonSchema, ResourceObject $ro)
82
    {
83
        $schemaFile = $this->getSchemaFile($jsonSchema, $ro);
84
        $body = isset($ro->body[$jsonSchema->key]) ? $ro->body[$jsonSchema->key] : $ro->body;
0 ignored issues
show
Unused Code introduced by
$body is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
85
        $this->validateRo($ro, $schemaFile);
86
    }
87
88
    private function validateRo(ResourceObject $ro, string $schemaFile)
89
    {
90
        $validator = new Validator;
91
        $schema = (object) ['$ref' => 'file://' . $schemaFile];
92
        $view = (string) $ro;
93
        $data = json_decode($view);
94
        $validator->validate($data, $schema, Constraint::CHECK_MODE_TYPE_CAST);
95
        $isValid = $validator->isValid();
96
        if ($isValid) {
97
            return;
98
        }
99
        $e = null;
100
        $msgList = '';
101
        foreach ($validator->getErrors() as $error) {
102
            $msg = sprintf('[%s] %s', $error['property'], $error['message']);
103
            $msgList .= $msg . '; ';
104
            $e = $e ? new JsonSchemaErrorException($msg, 0, $e) : new JsonSchemaErrorException($msg);
105
        }
106
107
        throw new JsonSchemaException("{$msgList} in {$schemaFile}", Code::ERROR, $e);
108
    }
109
110
    private function validate($scanObject, $schemaFile)
111
    {
112
        $validator = new Validator;
113
        $schema = (object) ['$ref' => 'file://' . $schemaFile];
114
115
        $validator->validate($scanObject, $schema, Constraint::CHECK_MODE_TYPE_CAST);
116
        $isValid = $validator->isValid();
117
        if ($isValid) {
118
            return;
119
        }
120
        $e = null;
121
        foreach ($validator->getErrors() as $error) {
122
            $msg = sprintf('[%s] %s', $error['property'], $error['message']);
123
            $e = $e ? new JsonSchemaErrorException($msg, 0, $e) : new JsonSchemaErrorException($msg);
124
        }
125
126
        throw new JsonSchemaException($schemaFile, Code::ERROR, $e);
127
    }
128
129
    private function getSchemaFile(JsonSchema $jsonSchema, ResourceObject $ro) : string
130
    {
131
        if (! $jsonSchema->schema) {
132
            // for BC only
133
            $ref = new \ReflectionClass($ro);
134
            if (! $ref instanceof \ReflectionClass) {
135
                throw new \ReflectionException(get_class($ro)); // @codeCoverageIgnore
136
            }
137
            $roFileName = $ro instanceof WeavedInterface ? $roFileName = $ref->getParentClass()->getFileName() : $ref->getFileName();
138
            $bcFile = str_replace('.php', '.json', $roFileName);
139
            if (file_exists($bcFile)) {
140
                return $bcFile;
141
            }
142
        }
143
        $schemaFile = $this->schemaDir . '/' . $jsonSchema->schema;
144
        $this->validateFileExists($schemaFile);
145
146
        return $schemaFile;
147
    }
148
149
    private function validateFileExists(string $schemaFile)
150
    {
151
        if (! file_exists($schemaFile) || is_dir($schemaFile)) {
152
            throw new JsonSchemaNotFoundException($schemaFile);
153
        }
154
    }
155
156
    private function getNamedArguments(MethodInvocation $invocation)
157
    {
158
        $parameters = $invocation->getMethod()->getParameters();
159
        $values = $invocation->getArguments();
160
        $arguments = [];
161
        foreach ($parameters as $index => $parameter) {
162
            $arguments[$parameter->name] = $values[$index] ?? $parameter->getDefaultValue();
163
        }
164
165
        return $arguments;
166
    }
167
}
168