Skip to content

Commit

Permalink
Restore exception behavior for backwards compatibility (#245)
Browse files Browse the repository at this point in the history
* Fix ML tests

Signed-off-by: Kim Pepper <[email protected]>

* Fix Security tests

Signed-off-by: Kim Pepper <[email protected]>

* Updates SQL namespace tests

Signed-off-by: Kim Pepper <[email protected]>

* Re-add support for legacy exceptions

Signed-off-by: Kim Pepper <[email protected]>

* Handle exception in boolean request wrapper

Signed-off-by: Kim Pepper <[email protected]>

* Avoid empty()

Signed-off-by: Kim Pepper <[email protected]>

* Generate API

Signed-off-by: Kim Pepper <[email protected]>

---------

Signed-off-by: Kim Pepper <[email protected]>
  • Loading branch information
kimpepper authored Jan 9, 2025
1 parent 5855571 commit 6ed0301
Show file tree
Hide file tree
Showing 290 changed files with 3,141 additions and 1,480 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,24 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- Added workflow for automated API update using OpenSearch API specification ([#209](https://github.com/opensearch-project/opensearch-php/pull/209))
- Added samples ([#218](https://github.com/opensearch-project/opensearch-php/pull/218))
- Added support for PHP 8.3 and 8.4 ([#229](https://github.com/opensearch-project/opensearch-php/pull/229))
- Added a Docker Compose config file for local development.
### Changed
- Switched to PSR Interfaces
- Increased PHP min version to 8.1
- Increased min version of `ezimuel/ringphp` to `^1.2.2`
- Changed fluent setters to return static
### Deprecated
- Passing a callable to \OpenSearch\ClientBuilder::setEndpoint() is deprecated and replaced with passing an EndpointFactory to \OpenSearch\ClientBuilder::setEndpointFactory() ([#237](https://github.com/opensearch-project/opensearch-php/pull/237))
- Connections, Connection pools and Selectors are deprecated. Use a PSR HTTP Client that supports retries instead.
- Connections, Connection pools and Selectors are deprecated. Use a PSR HTTP Client that supports retries instead.
- Throwing exceptions for different HTTP status codes is deprecated. Use the response object to check the status code instead.
### Removed
- Removed support for PHP 7.3, 7.4 and 8.0.
- Removed support for async requests which were never actually working.
### Fixed
- Fixed PHP 8.4 deprecations
- Fixed outdated tests
### Updated APIs
- Updated opensearch-php APIs to reflect [opensearch-api-specification@2395cb4](https://github.com/opensearch-project/opensearch-api-specification/commit/2395cb472ec5581656aac184f7b20548cd5b06ac)
- Updated opensearch-php APIs to reflect [opensearch-api-specification@ebe0f8a](https://github.com/opensearch-project/opensearch-api-specification/commit/ebe0f8a885f7db7e882d160c101055a5aa70a707)
- Updated opensearch-php APIs to reflect [opensearch-api-specification@398481e](https://github.com/opensearch-project/opensearch-api-specification/commit/398481e5bd1cc590d947c35379c47096f2114f00)
- Updated opensearch-php APIs to reflect [opensearch-api-specification@6bb1fed](https://github.com/opensearch-project/opensearch-api-specification/commit/6bb1fed0a2c7cf094a5ecfdb01f0306a4b9f8eba)
Expand Down
9 changes: 9 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
services:
opensearch:
image: opensearchproject/opensearch:2
ports:
- 9200:9200
environment:
- discovery.type=single-node
- plugins.security.disabled=true
- OPENSEARCH_INITIAL_ADMIN_PASSWORD=myStrongPassword123!
4 changes: 4 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" bootstrap="tests/bootstrap.php" colors="true" failOnRisky="true" verbose="true" beStrictAboutChangesToGlobalState="true" beStrictAboutOutputDuringTests="true" beStrictAboutTestsThatDoNotTestAnything="false">
<php>
<env name="OPENSEARCH_URL" value="http://localhost:9200"/>
<env name="OPENSEARCH_INITIAL_ADMIN_PASSWORD" value="myStrongPassword123!"/>
</php>
<coverage>
<include>
<directory>./src/</directory>
Expand Down
181 changes: 151 additions & 30 deletions src/OpenSearch/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,20 @@

namespace OpenSearch;

use OpenSearch\Common\Exceptions\BadMethodCallException;
use OpenSearch\Common\Exceptions\NoNodesAvailableException;
use OpenSearch\Common\Exceptions\BadRequest400Exception;
use OpenSearch\Common\Exceptions\Conflict409Exception;
use OpenSearch\Common\Exceptions\Forbidden403Exception;
use OpenSearch\Common\Exceptions\Missing404Exception;
use OpenSearch\Common\Exceptions\NoDocumentsToGetException;
use OpenSearch\Common\Exceptions\NoShardAvailableException;
use OpenSearch\Common\Exceptions\RequestTimeout408Exception;
use OpenSearch\Common\Exceptions\RoutingMissingException;
use OpenSearch\Common\Exceptions\ScriptLangNotSupportedException;
use OpenSearch\Common\Exceptions\ServerErrorResponseException;
use OpenSearch\Common\Exceptions\Unauthorized401Exception;
use OpenSearch\Endpoints\AbstractEndpoint;
use OpenSearch\Namespaces\NamespaceBuilderInterface;
use OpenSearch\Namespaces\BooleanRequestWrapper;
use OpenSearch\Namespaces\NamespaceBuilderInterface;
use OpenSearch\Namespaces\AsyncSearchNamespace;
use OpenSearch\Namespaces\AsynchronousSearchNamespace;
use OpenSearch\Namespaces\CatNamespace;
Expand Down Expand Up @@ -59,7 +68,6 @@
use OpenSearch\Namespaces\TasksNamespace;
use OpenSearch\Namespaces\TransformsNamespace;
use OpenSearch\Namespaces\WlmNamespace;
use OpenSearch\Traits\DeprecatedPropertyTrait;

/**
* Class Client
Expand All @@ -72,6 +80,8 @@ class Client

/**
* @var Transport
*
* @deprecated in 2.3.2 and will be removed in 3.0.0.
*/
public $transport;

Expand All @@ -96,6 +106,11 @@ class Client
*/
protected $registeredNamespaces = [];

/**
* @deprecated in 2.3.2 and will be removed in 3.0.0.
*/
private bool $throwExceptions = false;

/**
* @var AsyncSearchNamespace
*/
Expand Down Expand Up @@ -262,15 +277,21 @@ class Client
protected $wlm;



/**
* Client constructor
*
* @param \OpenSearch\TransportInterface|\OpenSearch\Transport $transport
* @param TransportInterface|Transport $transport
* @param callable|EndpointFactoryInterface $endpointFactory
* @param NamespaceBuilderInterface[] $registeredNamespaces
*/
public function __construct(TransportInterface|Transport $transport, callable|EndpointFactoryInterface $endpointFactory, array $registeredNamespaces)
{
* @param bool $throwExceptions
*/
public function __construct(
TransportInterface|Transport $transport,
callable|EndpointFactoryInterface $endpointFactory,
array $registeredNamespaces,
bool $throwExceptions = false,
) {
if (!$transport instanceof TransportInterface) {
@trigger_error('Passing an instance of \OpenSearch\Transport to ' . __METHOD__ . '() is deprecated in 2.3.2 and will be removed in 3.0.0. Pass an instance of \OpenSearch\TransportInterface instead.', E_USER_DEPRECATED);
$this->transport = $transport;
Expand All @@ -290,6 +311,13 @@ public function __construct(TransportInterface|Transport $transport, callable|En
}
$this->endpoints = $endpoints;
$this->endpointFactory = $endpointFactory;
if ($throwExceptions === true) {
@trigger_error(
'The $throwExceptions parameter is deprecated in 2.4.0 and will be removed in 3.0.0. Check the response \'status_code\' instead',
E_USER_DEPRECATED
);
$this->throwExceptions = true;
}
$this->asyncSearch = new AsyncSearchNamespace($transport, $this->endpointFactory);
$this->asynchronousSearch = new AsynchronousSearchNamespace($transport, $this->endpointFactory);
$this->cat = new CatNamespace($transport, $this->endpointFactory);
Expand Down Expand Up @@ -726,7 +754,7 @@ public function exists(array $params = []): bool
$endpoint->setId($id);
$endpoint->setIndex($index);

return BooleanRequestWrapper::performRequest($endpoint, $this->transport);
return BooleanRequestWrapper::sendRequest($endpoint, $this->httpTransport);
}

/**
Expand Down Expand Up @@ -765,7 +793,7 @@ public function existsSource(array $params = []): bool
$endpoint->setId($id);
$endpoint->setIndex($index);

return BooleanRequestWrapper::performRequest($endpoint, $this->transport);
return BooleanRequestWrapper::sendRequest($endpoint, $this->httpTransport);
}

/**
Expand Down Expand Up @@ -1222,7 +1250,7 @@ public function ping(array $params = []): bool
$endpoint = $this->endpointFactory->getEndpoint(\OpenSearch\Endpoints\Ping::class);
$endpoint->setParams($params);

return BooleanRequestWrapper::performRequest($endpoint, $this->transport);
return BooleanRequestWrapper::sendRequest($endpoint, $this->httpTransport);
}

/**
Expand Down Expand Up @@ -1322,7 +1350,7 @@ public function reindex(array $params = [])
}

/**
* Changes the number of requests per second for a particular Reindex operation.
* Changes the number of requests per second for a particular reindex operation.
*
* $params['task_id'] = (string) Identifier for the task. (Required)
* $params['requests_per_second'] = (number) The throttle for this request in sub-requests per second.
Expand Down Expand Up @@ -1408,7 +1436,6 @@ public function scriptsPainlessExecute(array $params = [])
* $params['error_trace'] = (boolean) Whether to include the stack trace of returned errors. (Default = false)
* $params['source'] = (string) The URL-encoded request definition. Useful for libraries that do not accept a request body for non-POST requests.
* $params['filter_path'] = (any) Used to reduce the response. This parameter takes a comma-separated list of filters. It supports using wildcards to match any field or part of a field’s name. You can also exclude fields with "-".
* $params['body'] = (array) The scroll ID if not passed by URL or query parameter.
*
* @param array $params Associative array of parameters
* @return array
Expand Down Expand Up @@ -2040,14 +2067,14 @@ protected function getEndpointFactory(): EndpointFactoryInterface
* Catchall for registered namespaces
*
* @return object
* @throws BadMethodCallException if the namespace cannot be found
* @throws \BadMethodCallException if the namespace cannot be found
*/
public function __call(string $name, array $arguments)
{
if (isset($this->registeredNamespaces[$name])) {
return $this->registeredNamespaces[$name];
}
throw new BadMethodCallException("Namespace [$name] not found");
throw new \BadMethodCallException("Namespace [$name] not found");
}

/**
Expand All @@ -2070,37 +2097,131 @@ public function extractArgument(array &$params, string $arg)
}

/**
* Sends a raw request to the cluster
* @return array|string|null
* @throws \Exception
* Send a raw request to the cluster.
*
* @throws \Psr\Http\Client\ClientExceptionInterface
* @throws \OpenSearch\Common\Exceptions\OpenSearchException
*/
public function request(string $method, string $uri, array $attributes = []): array|string|null
{
public function request(
string $method,
string $uri,
array $attributes = []
): array|string|null {
$params = $attributes['params'] ?? [];
$body = $attributes['body'] ?? null;
$options = $attributes['options'] ?? [];

return $this->httpTransport->sendRequest($method, $uri, $params, $body, $options['headers'] ?? []);
$response = $this->httpTransport->sendRequest($method, $uri, $params, $body, $options['headers'] ?? []);

// @todo: Remove this in the next major release.
// Throw legacy exceptions.
if ($this->throwExceptions) {
if (isset($response['status']) && $response['status'] >= 400) {
$this->throwLegacyException($response);
}
}

return $response;
}

/**
* Sends a request for the given endpoint.
*
* @param \OpenSearch\Endpoints\AbstractEndpoint $endpoint
*
* @return array|string|null
* Send a request for an endpoint.
*
* @throws \Exception
* @throws \Psr\Http\Client\ClientExceptionInterface
* @throws \OpenSearch\Common\Exceptions\OpenSearchException
*/
private function performRequest(AbstractEndpoint $endpoint): array|string|null
{
$options = $endpoint->getOptions();
return $this->httpTransport->sendRequest(
$response = $this->httpTransport->sendRequest(
$endpoint->getMethod(),
$endpoint->getURI(),
$endpoint->getParams(),
$endpoint->getBody(),
$options['headers'] ?? []
$endpoint->getOptions()
);

// @todo: Remove this in the next major release.
// Throw legacy exceptions.
if ($this->throwExceptions) {
if (isset($response['status']) && $response['status'] >= 400) {
$this->throwLegacyException($response);
}
}

return $response;
}

/**
* Throw legacy exceptions.
*
* @param array<string,mixed> $response
*
* @throws \OpenSearch\Common\Exceptions\OpenSearchException
*/
private function throwLegacyException(array $response): void
{
if ($response['status'] >= 400 && $response['status'] < 500) {
$this->throwLegacyClientException($response);
}
if ($response['status'] >= 500) {
$this->throwLegacyServerException($response);
}
}

/**
* Throw legacy client exceptions based on status code.
*
* @throws \OpenSearch\Common\Exceptions\OpenSearchException
*/
private function throwLegacyClientException($response): void
{
$statusCode = $response['status_code'];
$responseBody = $this->convertBodyToString($response['body'], $statusCode);
throw match ($statusCode) {
401 => new Unauthorized401Exception($responseBody, $statusCode),
403 => new Forbidden403Exception($responseBody, $statusCode),
404 => new Missing404Exception($responseBody, $statusCode),
409 => new Conflict409Exception($responseBody, $statusCode),
400 => (str_contains($responseBody, 'script_lang not supported'))
? new ScriptLangNotSupportedException($responseBody . $statusCode)
: new BadRequest400Exception($responseBody, $statusCode),
408 => new RequestTimeout408Exception($responseBody, $statusCode),
default => new BadRequest400Exception($responseBody, $statusCode),
};
}

/**
* Throw legacy server exceptions based on status code.
*
* @throws \OpenSearch\Common\Exceptions\OpenSearchException
*/
private function throwLegacyServerException($response): void
{
$statusCode = $response['status_code'];
$error = $response['body']['error'] ?? [];
$reason = $error['reason'] ?? 'undefined reason';
$type = $error['type'] ?? 'undefined type';
$errorMessage = "$type: $reason";
$responseBody = $this->convertBodyToString($response['body'], $statusCode);

$exception = new ServerErrorResponseException($responseBody, $statusCode);
if ($statusCode === 500) {
if (str_contains($responseBody, "RoutingMissingException")) {
$exception = new RoutingMissingException($errorMessage, $statusCode);
} elseif (preg_match('/ActionRequestValidationException.+ no documents to get/', $responseBody) === 1) {
$exception = new NoDocumentsToGetException($errorMessage, $statusCode);
} elseif (str_contains($responseBody, 'NoShardAvailableActionException')) {
$exception = new NoShardAvailableException($errorMessage, $statusCode);
}
}
throw $exception;
}

private function convertBodyToString(mixed $body, int $statusCode): string
{
return empty($body)
? "Unknown $statusCode error from OpenSearch"
: (is_string($body) ? $body : json_encode($body));
}

}
7 changes: 3 additions & 4 deletions src/OpenSearch/Endpoints/AsynchronousSearch/Delete.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,11 @@ class Delete extends AbstractEndpoint
{
public function getURI(): string
{
if (isset($this->id) !== true) {
throw new RuntimeException(
'id is required for delete'
);
if (!isset($this->id) || $this->id === '') {
throw new RuntimeException('id is required for delete');
}
$id = $this->id;

return "/_plugins/_asynchronous_search/$id";
}

Expand Down
7 changes: 3 additions & 4 deletions src/OpenSearch/Endpoints/AsynchronousSearch/Get.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,11 @@ class Get extends AbstractEndpoint
{
public function getURI(): string
{
if (isset($this->id) !== true) {
throw new RuntimeException(
'id is required for get'
);
if (!isset($this->id) || $this->id === '') {
throw new RuntimeException('id is required for get');
}
$id = $this->id;

return "/_plugins/_asynchronous_search/$id";
}

Expand Down
2 changes: 1 addition & 1 deletion src/OpenSearch/Endpoints/AsynchronousSearch/Search.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public function getMethod(): string

public function setBody($body): static
{
if (isset($body) !== true) {
if (is_null($body)) {
return $this;
}
$this->body = $body;
Expand Down
Loading

0 comments on commit 6ed0301

Please sign in to comment.