1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace LaravelUi5\OData\Exception;
6:
7: use LaravelUi5\OData\Http\ODataResponse;
8: use Illuminate\Contracts\Support\Responsable;
9: use Illuminate\Support\Facades\App;
10: use RuntimeException;
11: use Throwable;
12:
13: /**
14: * @link https://docs.oasis-open.org/odata/odata/v4.01/os/part1-protocol/odata-v4.01-os-part1-protocol.html#sec_ErrorResponseBody
15: */
16: abstract class ProtocolException extends RuntimeException implements Responsable
17: {
18: protected $httpCode = ODataResponse::HTTP_INTERNAL_SERVER_ERROR;
19: protected $odataCode;
20: protected $message;
21: protected $target = null;
22: protected $details = [];
23: protected $innerError = [];
24: protected $headers = [];
25: protected $suppressContent = false;
26: protected $originalException = null;
27:
28: public function __construct(?string $code = null, ?string $message = null, ?Throwable $originalException = null)
29: {
30: if ($code) {
31: $this->odataCode = $code;
32: }
33:
34: if ($message) {
35: $this->message = $message;
36: }
37:
38: if ($originalException) {
39: $this->originalException = $originalException;
40: }
41:
42: parent::__construct($this->message);
43: }
44:
45: /**
46: * Set the OData error code
47: * @param string $code Code
48: * @return $this
49: */
50: public function code(string $code): self
51: {
52: $this->odataCode = $code;
53: return $this;
54: }
55:
56: /**
57: * Set the OData error message
58: * @param string $message Message
59: * @return $this
60: */
61: public function message(string $message): self
62: {
63: $this->message = $message;
64: return $this;
65: }
66:
67: /**
68: * Set the OData error target
69: * @param string $target Target
70: * @return $this
71: */
72: public function target(string $target): self
73: {
74: $this->target = $target;
75: return $this;
76: }
77:
78: /**
79: * Set the OData error details
80: * @param string $code Details
81: * @return $this
82: */
83: public function addDetail(string $code, string $message, ?string $target = null): self
84: {
85: $detail = [
86: 'code' => $code,
87: 'message' => $message,
88: ];
89:
90: if ($target) {
91: $detail['target'] = $target;
92: }
93:
94: $this->details[] = $detail;
95:
96: return $this;
97: }
98:
99: /**
100: * Set the OData inner error
101: * @param string $key Key
102: * @param string $value Value
103: * @return $this
104: */
105: public function addInnerError(string $key, string $value): self
106: {
107: $this->innerError[$key] = $value;
108:
109: return $this;
110: }
111:
112: /**
113: * Set a header on the outgoing response
114: * @param string $key Key
115: * @param string $value Value
116: * @return $this
117: */
118: public function header(string $key, string $value): self
119: {
120: $this->headers[$key] = $value;
121: return $this;
122: }
123:
124: /**
125: * Serialize this error
126: * @return array
127: */
128: public function serialize()
129: {
130: return array_filter([
131: 'httpCode' => $this->httpCode,
132: 'odataCode' => $this->odataCode,
133: 'message' => $this->message,
134: 'target' => $this->target,
135: 'details' => $this->details,
136: 'innererror' => $this->innerError,
137: 'headers' => $this->headers,
138: ]);
139: }
140:
141: /**
142: * Convert this exception to a Symfony error
143: * @return array
144: */
145: public function toError()
146: {
147: return [
148: 'code' => $this->odataCode,
149: 'message' => $this->message,
150: 'target' => $this->target,
151: 'details' => $this->details,
152: 'innererror' => $this->innerError ?: (object) [],
153: ];
154: }
155:
156: /**
157: * Get the original exception that caused this exception
158: *
159: * @return Throwable|null
160: */
161: public function getOriginalException(): ?Throwable
162: {
163: return $this->originalException;
164: }
165:
166: /**
167: * Convert this exception to a Symfony response
168: * @param null $request Request
169: * @return ODataResponse Response
170: */
171: public function toResponse($request = null): ODataResponse
172: {
173: $response = App::make(ODataResponse::class);
174:
175: $response->setCallback(function () {
176: if ($this->suppressContent) {
177: return;
178: }
179:
180: echo json_encode(['error' => $this->toError()], JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR);
181: });
182:
183: $response->setProtocolVersion('1.1');
184: $response->setStatusCode($this->httpCode);
185: $response->headers->replace($this->headers);
186: $response->headers->set('content-type', 'application/json');
187:
188: return $response;
189: }
190:
191: public function getInnerException(): ?Throwable
192: {
193: return $this->originalException;
194: }
195: }
196: