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\Common\Exceptions\UnexpectedValueException;
25: use OpenSearch\Serializers\SerializerInterface;
26:
27: use function array_filter;
28:
29: abstract class AbstractEndpoint
30: {
31: /**
32: * @var array
33: */
34: protected $params = [];
35:
36: /**
37: * @var string|null
38: */
39: protected $index = null;
40:
41: /**
42: * @var string|int|null
43: */
44: protected $id = null;
45:
46: /**
47: * @var string|null
48: */
49: protected $method = null;
50:
51: /**
52: * @var string|array|null
53: */
54: protected $body = null;
55:
56: /**
57: * @var array
58: */
59: private $options = [];
60:
61: /**
62: * @var SerializerInterface
63: */
64: protected $serializer;
65:
66: /**
67: * @return string[]
68: */
69: abstract public function getParamWhitelist(): array;
70:
71: /**
72: * @return string
73: */
74: abstract public function getURI(): string;
75:
76: /**
77: * @return string
78: */
79: abstract public function getMethod(): string;
80:
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)
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)
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: /**
138: * @param int|string|null $docID
139: *
140: * @return $this
141: */
142: public function setId($docID)
143: {
144: if ($docID === null) {
145: return $this;
146: }
147:
148: if (is_int($docID)) {
149: $docID = (string)$docID;
150: }
151:
152: $this->id = urlencode($docID);
153:
154: return $this;
155: }
156:
157: /**
158: * @return array|string
159: */
160: public function getBody()
161: {
162: return $this->body;
163: }
164:
165:
166: public function setBody(array $body)
167: {
168: $this->body = $body;
169:
170: return $this;
171: }
172:
173: protected function getOptionalURI(string $endpoint): string
174: {
175: $uri = [];
176: $uri[] = $this->getOptionalIndex();
177: $uri[] = $endpoint;
178: $uri = array_filter($uri);
179:
180: return '/' . implode('/', $uri);
181: }
182:
183: private function getOptionalIndex(): string
184: {
185: if (isset($this->index) === true) {
186: return $this->index;
187: } else {
188: return '_all';
189: }
190: }
191:
192: /**
193: * @param array<string, mixed> $params
194: *
195: * @throws UnexpectedValueException
196: */
197: private function checkUserParams(array $params)
198: {
199: if (empty($params)) {
200: return; //no params, just return.
201: }
202:
203: $whitelist = array_merge(
204: $this->getParamWhitelist(),
205: ['pretty', 'human', 'error_trace', 'source', 'filter_path', 'opaqueId']
206: );
207:
208: $invalid = array_diff(array_keys($params), $whitelist);
209: if (count($invalid) > 0) {
210: sort($invalid);
211: sort($whitelist);
212: throw new UnexpectedValueException(
213: sprintf(
214: (count($invalid) > 1 ? '"%s" are not valid parameters.' : '"%s" is not a valid parameter.') . ' Allowed parameters are "%s"',
215: implode('", "', $invalid),
216: implode('", "', $whitelist)
217: )
218: );
219: }
220: }
221:
222: /**
223: * @param array<string, mixed> $params Note: this is passed by-reference!
224: */
225: private function extractOptions(&$params)
226: {
227: // Extract out client options, then start transforming
228: if (isset($params['client']) === true) {
229: // Check if the opaqueId is populated and add the header
230: if (isset($params['client']['opaqueId']) === true) {
231: if (isset($params['client']['headers']) === false) {
232: $params['client']['headers'] = [];
233: }
234: $params['client']['headers']['x-opaque-id'] = [trim($params['client']['opaqueId'])];
235: unset($params['client']['opaqueId']);
236: }
237:
238: $this->options['client'] = $params['client'];
239: unset($params['client']);
240: }
241: $ignore = isset($this->options['client']['ignore']) ? $this->options['client']['ignore'] : null;
242: if (isset($ignore) === true) {
243: if (is_string($ignore)) {
244: $this->options['client']['ignore'] = explode(",", $ignore);
245: } elseif (is_array($ignore)) {
246: $this->options['client']['ignore'] = $ignore;
247: } else {
248: $this->options['client']['ignore'] = [$ignore];
249: }
250: }
251: }
252:
253: /**
254: * @param array<string, mixed> $params
255: *
256: * @return array<string, mixed>
257: */
258: private function convertCustom(array $params): array
259: {
260: if (isset($params['custom']) === true) {
261: foreach ($params['custom'] as $k => $v) {
262: $params[$k] = $v;
263: }
264: unset($params['custom']);
265: }
266:
267: return $params;
268: }
269:
270: private function convertArraysToStrings(array $params): array
271: {
272: foreach ($params as $key => &$value) {
273: if (!($key === 'client' || $key == 'custom') && is_array($value) === true) {
274: if ($this->isNestedArray($value) !== true) {
275: $value = implode(",", $value);
276: }
277: }
278: }
279:
280: return $params;
281: }
282:
283: private function isNestedArray(array $a): bool
284: {
285: foreach ($a as $v) {
286: if (is_array($v)) {
287: return true;
288: }
289: }
290:
291: return false;
292: }
293:
294: /**
295: * This function returns all param deprecations also optional with a replacement field
296: *
297: * @return array<string, string|null>
298: */
299: protected function getParamDeprecation(): array
300: {
301: return [];
302: }
303:
304: private function checkForDeprecations(): void
305: {
306: $deprecations = $this->getParamDeprecation();
307:
308: if ($deprecations === []) {
309: return;
310: }
311:
312: $keys = array_keys($this->params);
313:
314: foreach ($keys as $key) {
315: if (array_key_exists($key, $deprecations)) {
316: $val = $deprecations[$key];
317:
318: $msg = sprintf('The parameter "%s" is deprecated and will be removed without replacement in the next major version', $key);
319:
320: if ($val) {
321: $msg = sprintf('The parameter "%s" is deprecated and will be replaced with parameter "%s" in the next major version', $key, $val);
322: }
323:
324: trigger_error($msg, E_USER_DEPRECATED);
325: }
326: }
327: }
328: }
329: