Skip to content

Commit

Permalink
[Feature] Send raw json request (#177)
Browse files Browse the repository at this point in the history
* feat(client): send raw json request

Signed-off-by: imdhemy <imdhemy@gmail.com>

* chore: update changelog

Signed-off-by: imdhemy <imdhemy@gmail.com>

* chore: use same attributes of the transport class

Signed-off-by: imdhemy <imdhemy@gmail.com>

* chore(guides): add raw json request guide

Signed-off-by: imdhemy <imdhemy@gmail.com>

* chore: code cleanup

Signed-off-by: imdhemy <imdhemy@gmail.com>

* chore(client): improve raw request tests

Signed-off-by: imdhemy <imdhemy@gmail.com>

* fix(cicd): fix link checker (#175)

Signed-off-by: imdhemy <imdhemy@gmail.com>

---------

Signed-off-by: imdhemy <imdhemy@gmail.com>
  • Loading branch information
imdhemy authored Mar 5, 2024
1 parent c6c06f4 commit 4398973
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 27 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- Added the `RefreshSearchAnalyzers` endpoint ([[#152](https://github.com/opensearch-project/opensearch-php/issues/152))
- Added support for `format` parameter to specify the sql response format ([#161](https://github.com/opensearch-project/opensearch-php/pull/161))
- Added ml commons model, model group and connector APIs ([#170](https://github.com/opensearch-project/opensearch-php/pull/170))
- [Feature] Send raw json request ([#171](https://github.com/opensearch-project/opensearch-php/pull/177))

### Changed

Expand Down
50 changes: 50 additions & 0 deletions guides/raw-request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Raw JSON Requests

Opensearch client implements many high-level APIs out of the box. However, there are times when you need to send a raw
JSON request to the server. This can be done using the `request()` method of the client.

The `request()` method expects the following parameters:

| Parameter | Description |
|---------------|------------------------------------------------------------------------------|
| `$method` | The HTTP method to use for the request, `GET`, `POST`, `PUT`, `DELETE`, etc. |
| `$uri` | The URI to send the request to, e.g. `/_search`. |
| `$attributes` | An array of request options. `body`, `params` & `options` |

> ![NOTE]
> The `request()` is a wrapper around the `\OpenSearch\Transport::performRequest()` method, this explains why [the
> parameters](https://github.com/opensearch-project/opensearch-php/blob/bf9b9815e9636196295432baa9c0368bc2efadd8/src/OpenSearch/Transport.php#L99)
> are intentionally similar.
Most of the time, you will only need to provide the `body` attribute. The `body` attribute is the JSON payload to send
to the server. The `params` attribute is used to set query parameters, and the `options` attribute is used to set
additional request options.

To send a raw JSON request you need to map the documentation to the `request()` method parameters. For example, [this
request](https://opensearch.org/docs/2.12/search-plugins/keyword-search/#example) that query searches for the
words `long live king` in the `shakespeare` index:

```
GET shakespeare/_search
{
"query": {
"match": {
"text_entry": "long live king"
}
}
}
```

Can be translated to the following `request()` method call:

```php
$response = $client->request('GET', '/shakespeare/_search', [
'body' => [
'query' => [
'match' => [
'text_entry' => 'long live king'
]
]
]
]);
```
37 changes: 24 additions & 13 deletions src/OpenSearch/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,26 @@
namespace OpenSearch;

use OpenSearch\Common\Exceptions\BadMethodCallException;
use OpenSearch\Common\Exceptions\InvalidArgumentException;
use OpenSearch\Common\Exceptions\NoNodesAvailableException;
use OpenSearch\Common\Exceptions\BadRequest400Exception;
use OpenSearch\Common\Exceptions\Missing404Exception;
use OpenSearch\Common\Exceptions\TransportException;
use OpenSearch\Endpoints\AbstractEndpoint;
use OpenSearch\Namespaces\AbstractNamespace;
use OpenSearch\Namespaces\MachineLearningNamespace;
use OpenSearch\Namespaces\NamespaceBuilderInterface;
use OpenSearch\Namespaces\AsyncSearchNamespace;
use OpenSearch\Namespaces\BooleanRequestWrapper;
use OpenSearch\Namespaces\CatNamespace;
use OpenSearch\Namespaces\ClusterNamespace;
use OpenSearch\Namespaces\DanglingIndicesNamespace;
use OpenSearch\Namespaces\DataFrameTransformDeprecatedNamespace;
use OpenSearch\Namespaces\IndicesNamespace;
use OpenSearch\Namespaces\IngestNamespace;
use OpenSearch\Namespaces\MachineLearningNamespace;
use OpenSearch\Namespaces\MonitoringNamespace;
use OpenSearch\Namespaces\NamespaceBuilderInterface;
use OpenSearch\Namespaces\NodesNamespace;
use OpenSearch\Namespaces\SearchableSnapshotsNamespace;
use OpenSearch\Namespaces\SecurityNamespace;
use OpenSearch\Namespaces\SnapshotNamespace;
use OpenSearch\Namespaces\SqlNamespace;
use OpenSearch\Namespaces\TasksNamespace;
use OpenSearch\Namespaces\AsyncSearchNamespace;
use OpenSearch\Namespaces\DataFrameTransformDeprecatedNamespace;
use OpenSearch\Namespaces\MonitoringNamespace;
use OpenSearch\Namespaces\SearchableSnapshotsNamespace;
use OpenSearch\Namespaces\SslNamespace;
use OpenSearch\Namespaces\TasksNamespace;

/**
* Class Client
Expand Down Expand Up @@ -1357,7 +1352,7 @@ public function sql(): SqlNamespace

public function ml(): MachineLearningNamespace
{
return $this->ml;
return $this->ml;
}

/**
Expand Down Expand Up @@ -1391,6 +1386,22 @@ public function extractArgument(array &$params, string $arg)
}
}

/**
* Sends a raw request to the cluster
* @return callable|array
* @throws NoNodesAvailableException
*/
public function request(string $method, string $uri, array $attributes = [])
{
$params = $attributes['params'] ?? [];
$body = $attributes['body'] ?? null;
$options = $attributes['options'] ?? [];

$promise = $this->transport->performRequest($method, $uri, $params, $body, $options);

return $this->transport->resultOrFuture($promise, $options);
}

/**
* @return callable|array
*/
Expand Down
52 changes: 41 additions & 11 deletions tests/ClientIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
use OpenSearch\Client;
use OpenSearch\ClientBuilder;
use OpenSearch\Common\Exceptions\BadRequest400Exception;
use OpenSearch\Common\Exceptions\OpenSearchException;
use OpenSearch\Common\Exceptions\Missing404Exception;
use OpenSearch\Tests\ClientBuilder\ArrayLogger;
use Psr\Log\LogLevel;
Expand All @@ -33,7 +32,7 @@
* Class ClientTest
*
* @subpackage Tests
* @group Integration
* @group Integration
*/
class ClientIntegrationTest extends \PHPUnit\Framework\TestCase
{
Expand Down Expand Up @@ -88,7 +87,7 @@ public function testLogRequestFailHasWarning()
try {
$result = $client->get([
'index' => 'foo',
'id' => 'bar'
'id' => 'bar',
]);
} catch (Missing404Exception $e) {
$this->assertNotEmpty($this->getLevelOutput(LogLevel::WARNING, $this->logger->output));
Expand All @@ -103,8 +102,8 @@ public function testIndexCannotBeEmptyStringForDelete()

$client->delete(
[
'index' => '',
'id' => 'test'
'index' => '',
'id' => 'test',
]
);
}
Expand All @@ -117,8 +116,8 @@ public function testIdCannotBeEmptyStringForDelete()

$client->delete(
[
'index' => 'test',
'id' => ''
'index' => 'test',
'id' => '',
]
);
}
Expand All @@ -131,8 +130,8 @@ public function testIndexCannotBeArrayOfEmptyStringsForDelete()

$client->delete(
[
'index' => ['', '', ''],
'id' => 'test'
'index' => ['', '', ''],
'id' => 'test',
]
);
}
Expand All @@ -145,19 +144,50 @@ public function testIndexCannotBeArrayOfNullsForDelete()

$client->delete(
[
'index' => [null, null, null],
'id' => 'test'
'index' => [null, null, null],
'id' => 'test',
]
);
}

/** @test */
public function sendRawRequest(): void
{
$client = $this->getClient();

$response = $client->request('GET', '/');

$this->assertIsArray($response);
$expectedKeys = ['name', 'cluster_name', 'cluster_uuid', 'version', 'tagline'];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $response);
}
}

/** @test */
public function insertDocumentUsingRawRequest(): void
{
$client = $this->getClient();
$randomIndex = 'test_index_' .time();

$response = $client->request('POST', "/$randomIndex/_doc", ['body' => ['field' => 'value']]);

$this->assertIsArray($response);
$this->assertArrayHasKey('_index', $response);
$this->assertSame($randomIndex, $response['_index']);
$this->assertArrayHasKey('_id', $response);
$this->assertArrayHasKey('result', $response);
$this->assertSame('created', $response['result']);
}

private function getLevelOutput(string $level, array $output): string
{
foreach ($output as $out) {
if (false !== strpos($out, $level)) {
return $out;
}
}

return '';
}
}
57 changes: 54 additions & 3 deletions tests/ClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@

namespace OpenSearch\Tests;

use GuzzleHttp\Ring\Client\MockHandler;
use GuzzleHttp\Ring\Future\FutureArray;
use Mockery as m;
use OpenSearch;
use OpenSearch\Client;
use OpenSearch\ClientBuilder;
use OpenSearch\Common\Exceptions\MaxRetriesException;
use GuzzleHttp\Ring\Client\MockHandler;
use GuzzleHttp\Ring\Future\FutureArray;
use Mockery as m;

/**
* Class ClientTest
Expand Down Expand Up @@ -409,4 +409,55 @@ public function testExtractArgumentIterable()
$this->assertCount(0, $params);
$this->assertInstanceOf(\IteratorIterator::class, $argument);
}

/** @test */
public function sendRawRequest(): void
{
$callable = function () {};
$transport = $this->createMock(OpenSearch\Transport::class);
$client = new OpenSearch\Client($transport, $callable, []);

$transport->expects($this->once())->method('performRequest')->with('GET', '/', [], null, []);

$client->request('GET', '/');
}

/** @test */
public function sendRawRequestWithBody(): void
{
$callable = function () {};
$transport = $this->createMock(OpenSearch\Transport::class);
$client = new OpenSearch\Client($transport, $callable, []);
$body = ['query' => ['match' => ['text_entry' => 'long live king']]];

$transport->expects($this->once())->method('performRequest')->with('GET', '/shakespeare/_search', [], $body, []);

$client->request('GET', '/shakespeare/_search', compact('body'));
}

/** @test */
public function sendRawRequestWithParams(): void
{
$callable = function () {};
$transport = $this->createMock(OpenSearch\Transport::class);
$client = new OpenSearch\Client($transport, $callable, []);
$params = ['foo' => 'bar'];

$transport->expects($this->once())->method('performRequest')->with('GET', '/_search', $params, null, []);

$client->request('GET', '/_search', compact('params'));
}

/** @test */
public function sendRawRequestWithOptions(): void
{
$callable = function () {};
$transport = $this->createMock(OpenSearch\Transport::class);
$client = new OpenSearch\Client($transport, $callable, []);
$options = ['client' => ['future' => 'lazy']];

$transport->expects($this->once())->method('performRequest')->with('GET', '/', [], null, $options);

$client->request('GET', '/', compact('options'));
}
}

0 comments on commit 4398973

Please sign in to comment.