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 array|null |
||||
70 | */ |
||||
71 | protected $extensions; |
||||
72 | |||||
73 | /** |
||||
74 | * @var null|\Throwable |
||||
75 | */ |
||||
76 | protected $originalException; |
||||
77 | |||||
78 | /** |
||||
79 | * ExecutionException constructor. |
||||
80 | * |
||||
81 | * @param string $message |
||||
82 | * @param array|null $nodes |
||||
83 | * @param Source|null $source |
||||
84 | * @param array|null $positions |
||||
85 | * @param array|null $path |
||||
86 | * @param array|null $extensions |
||||
87 | * @param \Throwable|null $originalException |
||||
88 | */ |
||||
89 | public function __construct( |
||||
90 | string $message, |
||||
91 | ?array $nodes = null, |
||||
92 | ?Source $source = null, |
||||
93 | ?array $positions = null, |
||||
94 | ?array $path = null, |
||||
95 | ?array $extensions = null, |
||||
96 | ?\Throwable $originalException = null |
||||
97 | ) { |
||||
98 | parent::__construct($message); |
||||
99 | |||||
100 | $this->resolveNodes($nodes); |
||||
101 | $this->resolveSource($source); |
||||
102 | $this->resolvePositions($positions); |
||||
103 | $this->resolveLocations($positions, $source); |
||||
104 | |||||
105 | $this->path = $path; |
||||
106 | $this->extensions = $extensions; |
||||
107 | $this->originalException = $originalException; |
||||
108 | } |
||||
109 | |||||
110 | /** |
||||
111 | * @return NodeInterface[] |
||||
112 | */ |
||||
113 | public function getNodes(): ?array |
||||
114 | { |
||||
115 | return $this->nodes; |
||||
116 | } |
||||
117 | |||||
118 | /** |
||||
119 | * @return bool |
||||
120 | */ |
||||
121 | public function hasSource(): bool |
||||
122 | { |
||||
123 | return null !== $this->source; |
||||
124 | } |
||||
125 | |||||
126 | /** |
||||
127 | * @return Source|null |
||||
128 | */ |
||||
129 | public function getSource(): ?Source |
||||
130 | { |
||||
131 | return $this->source; |
||||
132 | } |
||||
133 | |||||
134 | /** |
||||
135 | * @return int[]|null |
||||
136 | */ |
||||
137 | public function getPositions(): ?array |
||||
138 | { |
||||
139 | return $this->positions; |
||||
140 | } |
||||
141 | |||||
142 | /** |
||||
143 | * @return bool |
||||
144 | */ |
||||
145 | public function hasLocations(): bool |
||||
146 | { |
||||
147 | return !empty($this->locations); |
||||
148 | } |
||||
149 | |||||
150 | /** |
||||
151 | * @return array|null |
||||
152 | */ |
||||
153 | public function getLocations(): ?array |
||||
154 | { |
||||
155 | return $this->locations; |
||||
156 | } |
||||
157 | |||||
158 | /** |
||||
159 | * @return array|null |
||||
160 | */ |
||||
161 | public function getLocationsAsArray(): ?array |
||||
162 | { |
||||
163 | return !empty($this->locations) ? \array_map(function (SourceLocation $location) { |
||||
164 | return $location->toArray(); |
||||
165 | }, $this->locations) : null; |
||||
166 | } |
||||
167 | |||||
168 | /** |
||||
169 | * @return array|null |
||||
170 | */ |
||||
171 | public function getPath(): ?array |
||||
172 | { |
||||
173 | return $this->path; |
||||
174 | } |
||||
175 | |||||
176 | /** |
||||
177 | * @return array|null |
||||
178 | */ |
||||
179 | public function getExtensions(): ?array |
||||
180 | { |
||||
181 | return $this->extensions; |
||||
182 | } |
||||
183 | |||||
184 | /** |
||||
185 | * @param array|null $extensions |
||||
186 | * @return self |
||||
187 | */ |
||||
188 | public function setExtensions(?array $extensions): self |
||||
189 | { |
||||
190 | $this->extensions = $extensions; |
||||
191 | return $this; |
||||
192 | } |
||||
193 | |||||
194 | /** |
||||
195 | * @return \Throwable|null |
||||
196 | */ |
||||
197 | public function getOriginalException(): ?\Throwable |
||||
198 | { |
||||
199 | return $this->originalException; |
||||
200 | } |
||||
201 | |||||
202 | /** |
||||
203 | * @return null|string |
||||
204 | */ |
||||
205 | public function getOriginalErrorMessage(): ?string |
||||
206 | { |
||||
207 | return null !== $this->originalException ? $this->originalException->getMessage() : null; |
||||
208 | } |
||||
209 | |||||
210 | /** |
||||
211 | * @inheritdoc |
||||
212 | */ |
||||
213 | public function toArray(): array |
||||
214 | { |
||||
215 | $result = [ |
||||
216 | 'message' => $this->message, |
||||
217 | // TODO: Do not include `locations` if `null` (similar to `path` and `extensions`). |
||||
218 | 'locations' => $this->getLocationsAsArray(), |
||||
219 | ]; |
||||
220 | |||||
221 | if (null !== $this->path) { |
||||
222 | $result['path'] = $this->path; |
||||
223 | } |
||||
224 | |||||
225 | if (null !== $this->extensions) { |
||||
226 | $result['extensions'] = $this->extensions; |
||||
227 | } |
||||
228 | |||||
229 | return $result; |
||||
230 | } |
||||
231 | |||||
232 | /** |
||||
233 | * @inheritdoc |
||||
234 | */ |
||||
235 | public function __toString(): string |
||||
236 | { |
||||
237 | return printError($this); |
||||
238 | } |
||||
239 | |||||
240 | /** |
||||
241 | * @param array|null $nodes |
||||
242 | * @return $this |
||||
243 | */ |
||||
244 | protected function resolveNodes(?array $nodes): self |
||||
245 | { |
||||
246 | if (\is_array($nodes)) { |
||||
0 ignored issues
–
show
introduced
by
![]() |
|||||
247 | $nodes = !empty($nodes) ? $nodes : []; |
||||
248 | } else { |
||||
249 | $nodes = [$nodes]; |
||||
250 | } |
||||
251 | |||||
252 | $this->nodes = \array_filter($nodes, function ($node) { |
||||
253 | return null !== $node; |
||||
254 | }); |
||||
255 | |||||
256 | return $this; |
||||
257 | } |
||||
258 | |||||
259 | /** |
||||
260 | * @param Source|null $source |
||||
261 | * @return $this |
||||
262 | */ |
||||
263 | protected function resolveSource(?Source $source): self |
||||
264 | { |
||||
265 | if (null === $source && !empty($this->nodes)) { |
||||
266 | $firstNode = $this->nodes[0] ?? null; |
||||
267 | $location = null !== $firstNode ? $firstNode->getLocation() : null; |
||||
268 | $source = null !== $location ? $location->getSource() : null; |
||||
269 | } |
||||
270 | |||||
271 | $this->source = $source; |
||||
272 | |||||
273 | return $this; |
||||
274 | } |
||||
275 | |||||
276 | /** |
||||
277 | * @param array|null $positions |
||||
278 | * @return $this |
||||
279 | */ |
||||
280 | protected function resolvePositions(?array $positions): self |
||||
281 | { |
||||
282 | if (null === $positions && !empty($this->nodes)) { |
||||
0 ignored issues
–
show
|
|||||
283 | $positions = \array_reduce($this->nodes, function (array $list, ?NodeInterface $node) { |
||||
284 | if (null !== $node) { |
||||
285 | $location = $node->getLocation(); |
||||
286 | if (null !== $location) { |
||||
287 | $list[] = $location->getStart(); |
||||
288 | } |
||||
289 | } |
||||
290 | return $list; |
||||
291 | }, []); |
||||
292 | } |
||||
293 | |||||
294 | if (null !== $positions && empty($positions)) { |
||||
295 | $positions = null; |
||||
296 | } |
||||
297 | |||||
298 | $this->positions = $positions; |
||||
299 | |||||
300 | return $this; |
||||
301 | } |
||||
302 | |||||
303 | /** |
||||
304 | * @param array|null $positions |
||||
305 | * @param Source|null $source |
||||
306 | * @return $this |
||||
307 | */ |
||||
308 | protected function resolveLocations(?array $positions, ?Source $source): self |
||||
309 | { |
||||
310 | $locations = null; |
||||
311 | |||||
312 | if (null !== $positions && null !== $source) { |
||||
313 | $locations = \array_map(function ($position) use ($source) { |
||||
314 | return SourceLocation::fromSource($source, $position); |
||||
315 | }, $positions); |
||||
316 | } elseif (!empty($this->nodes)) { |
||||
317 | $locations = \array_reduce($this->nodes, function (array $list, NodeInterface $node) { |
||||
318 | $location = $node->getLocation(); |
||||
319 | if (null !== $location) { |
||||
320 | $list[] = SourceLocation::fromSource($location->getSource(), $location->getStart()); |
||||
0 ignored issues
–
show
It seems like
$location->getSource() can also be of type null ; however, parameter $source of Digia\GraphQL\Language\S...eLocation::fromSource() does only seem to accept Digia\GraphQL\Language\Source , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
321 | } |
||||
322 | return $list; |
||||
323 | }, []); |
||||
324 | } |
||||
325 | |||||
326 | if ($locations !== null) { |
||||
327 | $this->locations = $locations; |
||||
328 | } |
||||
329 | |||||
330 | return $this; |
||||
331 | } |
||||
332 | } |
||||
333 |