Passed
Pull Request — master (#274)
by Christoffer
02:38
created

GraphQLException::resolveLocations()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 13
nc 6
nop 2
dl 0
loc 21
rs 8.8333
c 0
b 0
f 0
1
<?php
2
3
namespace Digia\GraphQL\Error;
4
5
use Digia\GraphQL\Language\Node\NodeInterface;
6
use Digia\GraphQL\Language\Source;
7
use Digia\GraphQL\Language\SourceLocation;
8
use Digia\GraphQL\Util\ArrayToJsonTrait;
9
use Digia\GraphQL\Util\SerializationInterface;
10
11
/**
12
 * An GraphQLException describes an exception thrown during the execute
13
 * phase of performing a GraphQL operation. In addition to a message
14
 * and stack trace, it also includes information about the locations in a
15
 * GraphQL document and/or execution result that correspond to the Error.
16
 */
17
class GraphQLException extends AbstractException implements SerializationInterface
18
{
19
    use ArrayToJsonTrait;
20
21
    /**
22
     * An array of { line, column } locations within the source GraphQL document
23
     * which correspond to this error.
24
     *
25
     * Errors during validation often contain multiple locations, for example to
26
     * point out two things with the same name. Errors during execution include a
27
     * single location, the field which produced the error.
28
     *
29
     * @var array|null
30
     */
31
    protected $locations;
32
33
    /**
34
     * An array describing the JSON-path into the execution response which
35
     * corresponds to this error. Only included for errors during execution.
36
     *
37
     * @var string[]|null
38
     */
39
    protected $path;
40
41
    /**
42
     * An array of GraphQL AST Nodes corresponding to this error.
43
     *
44
     * @var NodeInterface[]|null
45
     */
46
    protected $nodes;
47
48
    /**
49
     * The source GraphQL document for the first location of this error.
50
     *
51
     * Note that if this Error represents more than one node, the source may not
52
     * represent nodes after the first node.
53
     *
54
     * @var Source|null
55
     */
56
    protected $source;
57
58
    /**
59
     * An array of character offsets within the source GraphQL document
60
     * which correspond to this error.
61
     *
62
     * @var int[]|null
63
     */
64
    protected $positions;
65
66
    /**
67
     * Extension fields to add to the formatted error.
68
     *
69
     * @var \Throwable|null
70
     */
71
    protected $originalException;
72
73
    /**
74
     * ExecutionException constructor.
75
     *
76
     * @param string          $message
77
     * @param array|null      $nodes
78
     * @param Source|null     $source
79
     * @param array|null      $positions
80
     * @param array|null      $path
81
     * @param \Throwable|null $originalException
82
     */
83
    public function __construct(
84
        string $message,
85
        ?array $nodes = null,
86
        ?Source $source = null,
87
        ?array $positions = null,
88
        ?array $path = null,
89
        ?\Throwable $originalException = null
90
    ) {
91
        parent::__construct($message);
92
93
        $this->resolveNodes($nodes);
94
        $this->resolveSource($source);
95
        $this->resolvePositions($positions);
96
        $this->resolveLocations($positions, $source);
97
98
        $this->path              = $path;
99
        $this->originalException = $originalException;
100
    }
101
102
    /**
103
     * @return NodeInterface[]
104
     */
105
    public function getNodes(): ?array
106
    {
107
        return $this->nodes;
108
    }
109
110
    /**
111
     * @return bool
112
     */
113
    public function hasSource(): bool
114
    {
115
        return null !== $this->source;
116
    }
117
118
    /**
119
     * @return Source|null
120
     */
121
    public function getSource(): ?Source
122
    {
123
        return $this->source;
124
    }
125
126
    /**
127
     * @return int[]|null
128
     */
129
    public function getPositions(): ?array
130
    {
131
        return $this->positions;
132
    }
133
134
    /**
135
     * @return bool
136
     */
137
    public function hasLocations(): bool
138
    {
139
        return !empty($this->locations);
140
    }
141
142
    /**
143
     * @return array|null
144
     */
145
    public function getLocations(): ?array
146
    {
147
        return $this->locations;
148
    }
149
150
    /**
151
     * @return array|null
152
     */
153
    public function getLocationsAsArray(): ?array
154
    {
155
        return !empty($this->locations) ? \array_map(function (SourceLocation $location) {
156
            return $location->toArray();
157
        }, $this->locations) : null;
158
    }
159
160
    /**
161
     * @return array|null
162
     */
163
    public function getPath(): ?array
164
    {
165
        return $this->path;
166
    }
167
168
    /**
169
     * @return \Throwable|null
170
     */
171
    public function getOriginalException(): ?\Throwable
172
    {
173
        return $this->originalException;
174
    }
175
176
    /**
177
     * @return null|string
178
     */
179
    public function getOriginalErrorMessage(): ?string
180
    {
181
        return null !== $this->originalException ? $this->originalException->getMessage() : null;
182
    }
183
184
    /**
185
     * @param array|null $nodes
186
     * @return $this
187
     */
188
    protected function resolveNodes(?array $nodes)
189
    {
190
        if (\is_array($nodes)) {
191
            $nodes = !empty($nodes) ? $nodes : [];
192
        } else {
193
            $nodes = [$nodes];
194
        }
195
196
        $this->nodes = \array_filter($nodes, function ($node) {
197
            return null !== $node;
198
        });
199
200
        return $this;
201
    }
202
203
    /**
204
     * @param Source|null $source
205
     * @return $this
206
     */
207
    protected function resolveSource(?Source $source)
208
    {
209
        if (null === $source && !empty($this->nodes)) {
210
            $firstNode = $this->nodes[0] ?? null;
211
            $location  = null !== $firstNode ? $firstNode->getLocation() : null;
212
            $source    = null !== $location ? $location->getSource() : null;
213
        }
214
215
        $this->source = $source;
216
217
        return $this;
218
    }
219
220
    /**
221
     * @param array|null $positions
222
     * @return $this
223
     */
224
    protected function resolvePositions(?array $positions)
225
    {
226
        if (null === $positions && !empty($this->nodes)) {
227
            $positions = \array_reduce($this->nodes, function (array $list, ?NodeInterface $node) {
228
                if (null !== $node) {
229
                    $location = $node->getLocation();
230
                    if (null !== $location) {
231
                        $list[] = $location->getStart();
232
                    }
233
                }
234
                return $list;
235
            }, []);
236
        }
237
238
        if (null !== $positions && empty($positions)) {
239
            $positions = null;
240
        }
241
242
        $this->positions = $positions;
243
244
        return $this;
245
    }
246
247
    /**
248
     * @param array|null  $positions
249
     * @param Source|null $source
250
     * @return $this
251
     */
252
    protected function resolveLocations(?array $positions, ?Source $source)
253
    {
254
        if (null !== $positions && null !== $source) {
255
            $locations = \array_map(function ($position) use ($source) {
256
                return SourceLocation::fromSource($source, $position);
257
            }, $positions);
258
        } elseif (!empty($this->nodes)) {
259
            $locations = \array_reduce($this->nodes, function (array $list, NodeInterface $node) {
260
                $location = $node->getLocation();
261
                if (null !== $location && $location->hasSource()) {
262
                    $list[] = SourceLocation::fromSource($location->getSource(), $location->getStart());
263
                }
264
                return $list;
265
            }, []);
266
        }
267
268
        if (isset($locations)) {
269
            $this->locations = $locations;
270
        }
271
272
        return $this;
273
    }
274
275
    /**
276
     * @inheritdoc
277
     */
278
    public function toArray(): array
279
    {
280
        return [
281
            'message'   => $this->message,
282
            'locations' => $this->getLocationsAsArray(),
283
            'path'      => $this->path,
284
        ];
285
    }
286
287
    /**
288
     * @inheritdoc
289
     */
290
    public function __toString(): string
291
    {
292
        return printError($this);
293
    }
294
}
295