From 6e1263a820a4bd501dec8f9cc1fc9c586380a0f0 Mon Sep 17 00:00:00 2001 From: imdhemy Date: Sat, 2 Mar 2024 12:52:21 +0100 Subject: [PATCH 1/7] feat(client): send raw json request Signed-off-by: imdhemy --- src/OpenSearch/Client.php | 38 +++++++++----- tests/ClientIntegrationTest.php | 89 +++++++++++++++++++++++++++++---- 2 files changed, 103 insertions(+), 24 deletions(-) diff --git a/src/OpenSearch/Client.php b/src/OpenSearch/Client.php index abb889340..36e1b6211 100644 --- a/src/OpenSearch/Client.php +++ b/src/OpenSearch/Client.php @@ -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 @@ -1357,7 +1352,7 @@ public function sql(): SqlNamespace public function ml(): MachineLearningNamespace { - return $this->ml; + return $this->ml; } /** @@ -1391,6 +1386,23 @@ 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 $params = [], + $body = null, + array $options = [] + ) { + $promise = $this->transport->performRequest($method, $uri, $params, $body, $options); + + return $this->transport->resultOrFuture($promise, $options); + } + /** * @return callable|array */ diff --git a/tests/ClientIntegrationTest.php b/tests/ClientIntegrationTest.php index 466c885e4..cbcc7009a 100644 --- a/tests/ClientIntegrationTest.php +++ b/tests/ClientIntegrationTest.php @@ -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; @@ -33,7 +32,7 @@ * Class ClientTest * * @subpackage Tests - * @group Integration + * @group Integration */ class ClientIntegrationTest extends \PHPUnit\Framework\TestCase { @@ -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)); @@ -103,8 +102,8 @@ public function testIndexCannotBeEmptyStringForDelete() $client->delete( [ - 'index' => '', - 'id' => 'test' + 'index' => '', + 'id' => 'test', ] ); } @@ -117,8 +116,8 @@ public function testIdCannotBeEmptyStringForDelete() $client->delete( [ - 'index' => 'test', - 'id' => '' + 'index' => 'test', + 'id' => '', ] ); } @@ -131,8 +130,8 @@ public function testIndexCannotBeArrayOfEmptyStringsForDelete() $client->delete( [ - 'index' => ['', '', ''], - 'id' => 'test' + 'index' => ['', '', ''], + 'id' => 'test', ] ); } @@ -145,12 +144,79 @@ public function testIndexCannotBeArrayOfNullsForDelete() $client->delete( [ - 'index' => [null, null, null], - 'id' => 'test' + 'index' => [null, null, null], + 'id' => 'test', ] ); } + /** @test */ + public function sendRawGetRequest(): 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 sendRawPostRequest(): void + { + $client = $this->getClient(); + $index = 'test_index_' . time(); + $client->indices()->create(['index' => $index]); + + $response = $client->request('POST', "/$index/_doc", [], ['foo' => 'bar']); + + $this->assertIsArray($response); + $this->assertArrayHasKey('_index', $response); + $this->assertEquals($index, $response['_index']); + $this->assertArrayHasKey('result', $response); + $this->assertEquals('created', $response['result']); + + $client->indices()->delete(['index' => $index]); + } + + /** @test */ + public function sendRawPutRequest(): void + { + $client = $this->getClient(); + $index = 'test_index_' . time(); + $client->indices()->create(['index' => $index]); + + $response = $client->request('PUT', "/$index/_settings", [], ['index' => ['number_of_replicas' => 2]]); + + $this->assertIsArray($response); + $this->assertArrayHasKey('acknowledged', $response); + $this->assertTrue($response['acknowledged']); + + $client->indices()->delete(['index' => $index]); + } + + /** @test */ + public function sendRawDeleteRequest(): void + { + $client = $this->getClient(); + $index = 'test_index_' . time(); + $client->indices()->create(['index' => $index]); + $client->index(['index' => $index, 'id' => 1, 'body' => ['foo' => 'bar']]); + + $response = $client->request('DELETE', "/$index/_doc/1"); + + $this->assertIsArray($response); + $this->assertArrayHasKey('_index', $response); + $this->assertEquals($index, $response['_index']); + $this->assertArrayHasKey('result', $response); + $this->assertEquals('deleted', $response['result']); + + $client->indices()->delete(['index' => $index]); + } + private function getLevelOutput(string $level, array $output): string { foreach ($output as $out) { @@ -158,6 +224,7 @@ private function getLevelOutput(string $level, array $output): string return $out; } } + return ''; } } From 52c31dd66c5a9cf556393d4018f1f6d2480c5f89 Mon Sep 17 00:00:00 2001 From: imdhemy Date: Sat, 2 Mar 2024 12:58:51 +0100 Subject: [PATCH 2/7] chore: update changelog Signed-off-by: imdhemy --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dcbf5597..0d1ba7d3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 From 886ba275765136511e8d636a4660e3e002a88c87 Mon Sep 17 00:00:00 2001 From: imdhemy Date: Sat, 2 Mar 2024 13:36:52 +0100 Subject: [PATCH 3/7] chore: use same attributes of the transport class Signed-off-by: imdhemy --- src/OpenSearch/Client.php | 8 +++++--- tests/ClientIntegrationTest.php | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/OpenSearch/Client.php b/src/OpenSearch/Client.php index 36e1b6211..e57d6e9c5 100644 --- a/src/OpenSearch/Client.php +++ b/src/OpenSearch/Client.php @@ -1394,10 +1394,12 @@ public function extractArgument(array &$params, string $arg) public function request( string $method, string $uri, - array $params = [], - $body = null, - array $options = [] + 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); diff --git a/tests/ClientIntegrationTest.php b/tests/ClientIntegrationTest.php index cbcc7009a..32d710f8b 100644 --- a/tests/ClientIntegrationTest.php +++ b/tests/ClientIntegrationTest.php @@ -171,7 +171,7 @@ public function sendRawPostRequest(): void $index = 'test_index_' . time(); $client->indices()->create(['index' => $index]); - $response = $client->request('POST', "/$index/_doc", [], ['foo' => 'bar']); + $response = $client->request('POST', "/$index/_doc", ['body' => ['foo' => 'bar']]); $this->assertIsArray($response); $this->assertArrayHasKey('_index', $response); @@ -189,7 +189,7 @@ public function sendRawPutRequest(): void $index = 'test_index_' . time(); $client->indices()->create(['index' => $index]); - $response = $client->request('PUT', "/$index/_settings", [], ['index' => ['number_of_replicas' => 2]]); + $response = $client->request('PUT', "/$index/_settings", ['body' => ['index' => ['number_of_replicas' => 2]]]); $this->assertIsArray($response); $this->assertArrayHasKey('acknowledged', $response); From f85b70923c4e095dd6fffd856de66451b8464ce0 Mon Sep 17 00:00:00 2001 From: imdhemy Date: Sat, 2 Mar 2024 13:37:34 +0100 Subject: [PATCH 4/7] chore(guides): add raw json request guide Signed-off-by: imdhemy --- guides/raw-request.md | 50 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 guides/raw-request.md diff --git a/guides/raw-request.md b/guides/raw-request.md new file mode 100644 index 000000000..5424f8cf0 --- /dev/null +++ b/guides/raw-request.md @@ -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' + ] + ] + ] +]); +``` From 9276ce93388e4a8cea0e1c0469585c73d6734be4 Mon Sep 17 00:00:00 2001 From: imdhemy Date: Sat, 2 Mar 2024 13:46:28 +0100 Subject: [PATCH 5/7] chore: code cleanup Signed-off-by: imdhemy --- src/OpenSearch/Client.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/OpenSearch/Client.php b/src/OpenSearch/Client.php index e57d6e9c5..912158867 100644 --- a/src/OpenSearch/Client.php +++ b/src/OpenSearch/Client.php @@ -1391,11 +1391,8 @@ public function extractArgument(array &$params, string $arg) * @return callable|array * @throws NoNodesAvailableException */ - public function request( - string $method, - string $uri, - array $attributes = [] - ) { + public function request(string $method, string $uri, array $attributes = []) + { $params = $attributes['params'] ?? []; $body = $attributes['body'] ?? null; $options = $attributes['options'] ?? []; From 7efd80b39f427149efa4e329bb192c92efbcce41 Mon Sep 17 00:00:00 2001 From: imdhemy Date: Sat, 2 Mar 2024 21:00:21 +0100 Subject: [PATCH 6/7] chore(client): improve raw request tests Signed-off-by: imdhemy --- tests/ClientIntegrationTest.php | 51 ++++------------------------- tests/ClientTest.php | 57 +++++++++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 47 deletions(-) diff --git a/tests/ClientIntegrationTest.php b/tests/ClientIntegrationTest.php index 32d710f8b..f3b480b5b 100644 --- a/tests/ClientIntegrationTest.php +++ b/tests/ClientIntegrationTest.php @@ -151,7 +151,7 @@ public function testIndexCannotBeArrayOfNullsForDelete() } /** @test */ - public function sendRawGetRequest(): void + public function sendRawRequest(): void { $client = $this->getClient(); @@ -165,56 +165,19 @@ public function sendRawGetRequest(): void } /** @test */ - public function sendRawPostRequest(): void + public function insertDocumentUsingRawRequest(): void { $client = $this->getClient(); - $index = 'test_index_' . time(); - $client->indices()->create(['index' => $index]); + $randomIndex = 'test_index_' .time(); - $response = $client->request('POST', "/$index/_doc", ['body' => ['foo' => 'bar']]); + $response = $client->request('POST', "/$randomIndex/_doc", ['body' => ['field' => 'value']]); $this->assertIsArray($response); $this->assertArrayHasKey('_index', $response); - $this->assertEquals($index, $response['_index']); + $this->assertSame($randomIndex, $response['_index']); + $this->assertArrayHasKey('_id', $response); $this->assertArrayHasKey('result', $response); - $this->assertEquals('created', $response['result']); - - $client->indices()->delete(['index' => $index]); - } - - /** @test */ - public function sendRawPutRequest(): void - { - $client = $this->getClient(); - $index = 'test_index_' . time(); - $client->indices()->create(['index' => $index]); - - $response = $client->request('PUT', "/$index/_settings", ['body' => ['index' => ['number_of_replicas' => 2]]]); - - $this->assertIsArray($response); - $this->assertArrayHasKey('acknowledged', $response); - $this->assertTrue($response['acknowledged']); - - $client->indices()->delete(['index' => $index]); - } - - /** @test */ - public function sendRawDeleteRequest(): void - { - $client = $this->getClient(); - $index = 'test_index_' . time(); - $client->indices()->create(['index' => $index]); - $client->index(['index' => $index, 'id' => 1, 'body' => ['foo' => 'bar']]); - - $response = $client->request('DELETE', "/$index/_doc/1"); - - $this->assertIsArray($response); - $this->assertArrayHasKey('_index', $response); - $this->assertEquals($index, $response['_index']); - $this->assertArrayHasKey('result', $response); - $this->assertEquals('deleted', $response['result']); - - $client->indices()->delete(['index' => $index]); + $this->assertSame('created', $response['result']); } private function getLevelOutput(string $level, array $output): string diff --git a/tests/ClientTest.php b/tests/ClientTest.php index bc1d092cd..4a405c103 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -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 @@ -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')); + } } From 79d2197e2cbe6864709867e8c68b87f7ce519402 Mon Sep 17 00:00:00 2001 From: Dhemy Date: Mon, 4 Mar 2024 22:06:07 +0100 Subject: [PATCH 7/7] fix(cicd): fix link checker (#175) Signed-off-by: imdhemy --- .github/workflows/links.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/links.yml b/.github/workflows/links.yml index 5e9a2151c..3c51ffdc5 100644 --- a/.github/workflows/links.yml +++ b/.github/workflows/links.yml @@ -13,13 +13,13 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: lychee Link Checker id: lychee - uses: lycheeverse/lychee-action@v1.0.8 + uses: lycheeverse/lychee-action@v1 with: args: --accept=200,403,429 "**/*.html" "**/*.md" "**/*.txt" "**/*.json" --exclude "https://github.com/\[your*" --exclude "https://localhost:9200" --exclude-mail env: GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} - name: Fail if there were link errors - run: exit ${{ steps.lychee.outputs.exit_code }} \ No newline at end of file + run: exit ${{ steps.lychee.outputs.exit_code }}