1: <?php
2:
3: declare(strict_types=1);
4:
5: /**
6: * Copyright OpenSearch Contributors
7: * SPDX-License-Identifier: Apache-2.0
8: *
9: * OpenSearch PHP client
10: *
11: * @link https://github.com/opensearch-project/opensearch-php/
12: * @copyright Copyright (c) Elasticsearch B.V (https://www.elastic.co)
13: * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
14: * @license https://www.gnu.org/licenses/lgpl-2.1.html GNU Lesser General Public License, Version 2.1
15: *
16: * Licensed to Elasticsearch B.V under one or more agreements.
17: * Elasticsearch B.V licenses this file to you under the Apache 2.0 License or
18: * the GNU Lesser General Public License, Version 2.1, at your option.
19: * See the LICENSE file in the project root for more information.
20: */
21:
22: namespace OpenSearch\Endpoints;
23:
24: use OpenSearch\EndpointInterface;
25: use OpenSearch\Exception\UnexpectedValueException;
26: use OpenSearch\Serializers\SerializerInterface;
27:
28: use function array_filter;
29:
30: abstract class AbstractEndpoint implements EndpointInterface
31: {
32: /**
33: * @var array
34: */
35: protected $params = [];
36:
37: /**
38: * @var string|null
39: */
40: protected $index = null;
41:
42: /**
43: * @var string|int|null
44: */
45: protected $id = null;
46:
47: /**
48: * @var string|null
49: */
50: protected $method = null;
51:
52: /**
53: * @var string|array|null
54: */
55: protected $body = null;
56:
57: /**
58: * @var array
59: */
60: private $options = [];
61:
62: /**
63: * @var SerializerInterface
64: */
65: protected $serializer;
66:
67: /**
68: * @return string[]
69: */
70: abstract public function getParamWhitelist(): array;
71:
72: /**
73: * @return string
74: */
75: abstract public function getURI(): string;
76:
77: /**
78: * @return string
79: */
80: abstract public function getMethod(): string;
81:
82: /**
83: * Set the parameters for this endpoint
84: *
85: * @param mixed[] $params Array of parameters
86: * @return $this
87: */
88: public function setParams(array $params): static
89: {
90: $this->extractOptions($params);
91: $this->checkUserParams($params);
92: $params = $this->convertCustom($params);
93: $this->params = $this->convertArraysToStrings($params);
94:
95: $this->checkForDeprecations();
96:
97: return $this;
98: }
99:
100: public function getParams(): array
101: {
102: return $this->params;
103: }
104:
105: public function getOptions(): array
106: {
107: return $this->options;
108: }
109:
110: public function getIndex(): ?string
111: {
112: return $this->index;
113: }
114:
115: /**
116: * @param string|string[]|null $index
117: *
118: * @return $this
119: */
120: public function setIndex($index): static
121: {
122: if ($index === null) {
123: return $this;
124: }
125:
126: if (is_array($index) === true) {
127: $index = array_filter($index);
128: $index = array_map('trim', $index);
129: $index = implode(",", $index);
130: }
131:
132: $this->index = urlencode($index);
133:
134: return $this;
135: }
136:
137: public function setId(int|string|null $docID): static
138: {
139: if ($docID === null) {
140: return $this;
141: }
142:
143: if (is_int($docID)) {
144: $docID = (string)$docID;
145: }
146:
147: $this->id = urlencode($docID);
148:
149: return $this;
150: }
151:
152: public function getBody(): string|array|null
153: {
154: return $this->body;
155: }
156:
157: public function setBody(string|iterable|null $body): static
158: {
159: $this->body = $body;
160:
161: return $this;
162: }
163:
164: protected function getOptionalURI(string $endpoint): string
165: {
166: $uri = [];
167: $uri[] = $this->getOptionalIndex();
168: $uri[] = $endpoint;
169: $uri = array_filter($uri);
170:
171: return '/' . implode('/', $uri);
172: }
173:
174: private function getOptionalIndex(): string
175: {
176: if (isset($this->index) === true) {
177: return $this->index;
178: } else {
179: return '_all';
180: }
181: }
182:
183: /**
184: * @param array<string, mixed> $params
185: *
186: * @throws UnexpectedValueException
187: */
188: private function checkUserParams(array $params)
189: {
190: if (empty($params)) {
191: return; //no params, just return.
192: }
193:
194: $whitelist = array_merge(
195: $this->getParamWhitelist(),
196: ['pretty', 'human', 'error_trace', 'source', 'filter_path', 'opaqueId']
197: );
198:
199: $invalid = array_diff(array_keys($params), $whitelist);
200: if (count($invalid) > 0) {
201: sort($invalid);
202: sort($whitelist);
203: throw new UnexpectedValueException(
204: sprintf(
205: (count($invalid) > 1 ? '"%s" are not valid parameters.' : '"%s" is not a valid parameter.') . ' Allowed parameters are "%s"',
206: implode('", "', $invalid),
207: implode('", "', $whitelist)
208: )
209: );
210: }
211: }
212:
213: /**
214: * @param array<string, mixed> $params Note: this is passed by-reference!
215: */
216: private function extractOptions(&$params)
217: {
218: // Extract out client options, then start transforming
219: if (isset($params['client']) === true) {
220: // Check if the opaqueId is populated and add the header
221: if (isset($params['client']['opaqueId']) === true) {
222: if (isset($params['client']['headers']) === false) {
223: $params['client']['headers'] = [];
224: }
225: $params['client']['headers']['x-opaque-id'] = [trim($params['client']['opaqueId'])];
226: unset($params['client']['opaqueId']);
227: }
228:
229: $this->options['client'] = $params['client'];
230: unset($params['client']);
231: }
232: $ignore = isset($this->options['client']['ignore']) ? $this->options['client']['ignore'] : null;
233: if (isset($ignore) === true) {
234: if (is_string($ignore)) {
235: $this->options['client']['ignore'] = explode(",", $ignore);
236: } elseif (is_array($ignore)) {
237: $this->options['client']['ignore'] = $ignore;
238: } else {
239: $this->options['client']['ignore'] = [$ignore];
240: }
241: }
242: }
243:
244: /**
245: * @param array<string, mixed> $params
246: *
247: * @return array<string, mixed>
248: */
249: private function convertCustom(array $params): array
250: {
251: if (isset($params['custom']) === true) {
252: foreach ($params['custom'] as $k => $v) {
253: $params[$k] = $v;
254: }
255: unset($params['custom']);
256: }
257:
258: return $params;
259: }
260:
261: private function convertArraysToStrings(array $params): array
262: {
263: foreach ($params as $key => &$value) {
264: if (!($key === 'client' || $key == 'custom') && is_array($value) === true) {
265: if ($this->isNestedArray($value) !== true) {
266: $value = implode(",", $value);
267: }
268: }
269: }
270:
271: return $params;
272: }
273:
274: private function isNestedArray(array $a): bool
275: {
276: foreach ($a as $v) {
277: if (is_array($v)) {
278: return true;
279: }
280: }
281:
282: return false;
283: }
284:
285: /**
286: * This function returns all param deprecations also optional with a replacement field
287: *
288: * @return array<string, string|null>
289: */
290: protected function getParamDeprecation(): array
291: {
292: return [];
293: }
294:
295: private function checkForDeprecations(): void
296: {
297: $deprecations = $this->getParamDeprecation();
298:
299: if ($deprecations === []) {
300: return;
301: }
302:
303: $keys = array_keys($this->params);
304:
305: foreach ($keys as $key) {
306: if (array_key_exists($key, $deprecations)) {
307: $val = $deprecations[$key];
308:
309: $msg = sprintf('The parameter "%s" is deprecated and will be removed without replacement in the next major version', $key);
310:
311: if ($val) {
312: $msg = sprintf('The parameter "%s" is deprecated and will be replaced with parameter "%s" in the next major version', $key, $val);
313: }
314:
315: trigger_error($msg, E_USER_DEPRECATED);
316: }
317: }
318: }
319: }
320: