Skip to content

Commit

Permalink
Low-level APIs
Browse files Browse the repository at this point in the history
- Generator for low-level methods
- Generated low-level methods
- Guide for low-level methods
- Samples for low-level methods

Signed-off-by: Theo Truong <[email protected]>
  • Loading branch information
nhtruong committed Nov 14, 2023
1 parent 642b420 commit 93b3efb
Show file tree
Hide file tree
Showing 33 changed files with 863 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

## [Unreleased]
### Added
- Added `http` namespace with low-level APIs ([#211](https://github.com/opensearch-project/opensearch-ruby/pull/211))
- Added guide and samples for lowl-level APIS ([#211](https://github.com/opensearch-project/opensearch-ruby/pull/211))
### Changed
### Deprecated
### Removed
Expand Down
1 change: 1 addition & 0 deletions USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ response = client.search index: index_name, body: search {
- [Advanced Index Actions](guides/advanced_index_actions.md)
- [Index Templates](guides/index_template.md)
- [Transport Options](guides/transport_options.md)
- [Low Level API](guides/low_level_api.md)

## Amazon OpenSearch Service

Expand Down
5 changes: 5 additions & 0 deletions api_generator/USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,8 @@ Note that the root namespace is presented by an empty string `''`. For example,
```ruby
generator.generate(version: '2.3', namespace: '')
```

Generate low-level API actions:
```ruby
generator.generate_low_level
```
2 changes: 1 addition & 1 deletion api_generator/lib/action_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def http_verb
end

def required_args
@action.required_components.map { |arg| { arg: } }
@action.required_components.map { |arg| { arg: arg } }
.tap { |args| args.last&.[]=('_blank_line', true) }
end

Expand Down
15 changes: 15 additions & 0 deletions api_generator/lib/api_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
require_relative 'spec_generator'
require_relative 'namespace_generator'
require_relative 'index_generator'
require_relative 'low_level_action_generator'

# Generate API endpoints for OpenSearch Ruby client
class ApiGenerator
Expand All @@ -37,6 +38,20 @@ def generate(gem_folder = '../', version: nil, namespace: nil, actions: nil)
IndexGenerator.new(gem_folder.join('lib/opensearch'), namespaces).generate
end

def generate_low_level(gem_folder = '../')
gem_folder = Pathname gem_folder
namespaces = existing_namespaces(gem_folder)
low_level_namespace = 'http'

NamespaceGenerator.new(gem_folder.join('lib/opensearch/api/namespace'), low_level_namespace).generate(namespaces)
LowLevelBaseActionGenerator.new(gem_folder.join('lib/opensearch/api/actions'), low_level_namespace).generate
IndexGenerator.new(gem_folder.join('lib/opensearch'), namespaces).generate

%w[head get post put patch delete options trace connect].each do |action|
LowLevelActionGenerator.new(gem_folder.join('lib/opensearch/api/actions'), low_level_namespace, action).generate
end
end

private

def target_actions(version, namespace, actions)
Expand Down
27 changes: 27 additions & 0 deletions api_generator/lib/low_level_action_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# SPDX-License-Identifier: Apache-2.0
#
# The OpenSearch Contributors require contributions made to
# this file be licensed under the Apache-2.0 license or a
# compatible open source license.

# frozen_string_literal: true

require_relative 'low_level_base_action_generator'

# Generate low-level API actions via Mustache
class LowLevelActionGenerator < LowLevelBaseActionGenerator
self.template_file = './templates/low_level_action.mustache'

def initialize(output_folder, namespace, action)
super(output_folder, namespace)
@action = action
end

def lower_cased
@action.downcase
end

def upper_cased
@action.upcase
end
end
30 changes: 30 additions & 0 deletions api_generator/lib/low_level_base_action_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# SPDX-License-Identifier: Apache-2.0
#
# The OpenSearch Contributors require contributions made to
# this file be licensed under the Apache-2.0 license or a
# compatible open source license.

# frozen_string_literal: true

require_relative 'base_generator'

# Generate low-level API actions via Mustache
class LowLevelBaseActionGenerator < BaseGenerator
self.template_file = './templates/low_level_base_action.mustache'

def initialize(output_folder, namespace)
super(output_folder)
@namespace = namespace
@action = 'request'
end

def namespace_module
@namespace.camelize
end

private

def output_file
create_folder(*[@output_folder, @namespace].compact).join("#{@action}.rb")
end
end
22 changes: 22 additions & 0 deletions api_generator/templates/low_level_action.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{{{license_header}}}
{{{generated_code_warning}}}

# frozen_string_literal: true

module OpenSearch
module API
module {{namespace_module}}
module Actions
# Make a customized {{upper_cased}} request.
#
# @option arguments [String] :url Relative path to the endpoint (e.g. 'cat/indices/books,movies') (*Required*)
# @option arguments [Hash] :params Querystring parameters to be appended to the path
# @option arguments [Hash] :headers Custom HTTP headers
# @option arguments [String | Hash | Array<Hash>] :body The body of the request
def {{lower_cased}}(url, headers: {}, body: nil, params: {})
request('{{upper_cased}}', url, headers: headers, body: body, params: params)
end
end
end
end
end
21 changes: 21 additions & 0 deletions api_generator/templates/low_level_base_action.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{{{license_header}}}
{{{generated_code_warning}}}

# frozen_string_literal: true

module OpenSearch
module API
module {{namespace_module}}
module Actions
private

def request(method, url, headers: {}, body: nil, params: {})
body = OpenSearch::API::Utils.__bulkify(body) if body.is_a?(Array)
headers.merge!('Content-Type' => 'application/x-ndjson') if body.is_a?(Array)

perform_request(method, url, params, body, headers).body
end
end
end
end
end
93 changes: 93 additions & 0 deletions guides/low_level_api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Low-level API

The OpenSearch client implements many high-level REST DSLs that invoke OpenSearch APIs. However you may find yourself in a situation that requires you to invoke an API that is not supported by the client. In this case, you can use the low-level API to invoke any OpenSearch API. This guide shows you different ways to make custom API calls using the OpenSearch Ruby client.

## Setup
First, create a client instance with the following code:

```ruby
require 'opensearch-ruby'
client = OpenSearch::Client.new({ host: 'localhost' })
```


## The perform_request Method

Normally, to get a summary of all indices in the cluster, you would use `client.cat.indices()`. However, you can achieve the same result using `client.perform_request`:

```ruby
puts client.perform_request('GET', '_cat/indices').body
```
Note that the `perform_request` method returns an `OpenSearch::Transport::Transport::Response` object. To get the response body, you need to call the `body` method.

As you may have guessed, the `perform_request` method also accepts query-string parameters, headers, and a request body. For example, to create in index named `books` with 5 shards and 2 replicas, with explicit timeout of 30 seconds, and content-type of `application/json`:

```ruby
body = { settings: { number_of_shards: 5, number_of_replicas: 2 } }
params = { timeout: '30s' }
headers = { 'Content-Type' => 'application/json' }

client.perform_request('PUT', 'books', params, body, headers)
```

The `perform_request` method only accepts the request body as a Ruby hash or a JSON string. If you want to pass a different type of body, you need to pass it as a string and set the `Content-Type` header accordingly. Some OpenSearch APIs, like `bulk`, accept a newline-delimited JSON (NDJSON) string as a request body. The following code creates two documents in the `books` index using NDJSON through the `perform_request` method:

```ruby
body = [{ index: { _index: "books", _id: 1 } },
{ title: "The Lion King", year: 1994 },
{ index: { _index: "books", _id: 2 } },
{ title: "Beauty and the Beast", year: 1991 }]
headers = { 'Content-Type' => 'application/x-ndjson' }

client.perform_request('POST', '_bulk', {}, OpenSearch::API::Utils.__bulkify(body), headers)
```

## The http Namespace

The `http` namespace provides a set of methods that wrap the `perform_request` method. For example, to get a summary of all indices in the cluster:

```ruby
puts client.http.get('_cat/indices')
```

Note that the `http` methods return the response body directly, like other API methods.

Of course, you can also pass query-string parameters, headers, and a request body to the `http` methods. For example, to create an index named `movies` similar to the `books` index we created previously:

```ruby
body = { settings: { number_of_shards: 5, number_of_replicas: 2 } }
params = { timeout: '30s' }
headers = { 'Content-Type' => 'application/json' }

client.http.put('movies', body: body, params: params, headers: headers)
```

Unlike the `perform_request` method, on top of accepting a Ruby Hash or JSON string as the body, the `http` methods also accept an array of hashes. When such an array is passed as the body, the `http` methods will automatically convert it to NDJSON, and add the appropriate `Content-Type` header. The following code is equivalent to the previous example:

```ruby
body = [{ index: { _index: "books", _id: 1 } },
{ title: "The Lion King", year: 1994 },
{ index: { _index: "books", _id: 2 } },
{ title: "Beauty and the Beast", year: 1991 }]

client.http.post('_bulk', body: body)
```

The `http` namespace includes the following methods:
- get
- put
- post
- delete
- head
- options
- patch
- trace
- connect


## Cleanup
To clean up the resources created in this guide, delete the `books`, and `movies` index:

```ruby
client.indices.delete(index: [:books, :movies])
```
1 change: 1 addition & 0 deletions lib/opensearch/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ def self.included(base)
OpenSearch::API::Cluster,
OpenSearch::API::DanglingIndices,
OpenSearch::API::Features,
OpenSearch::API::Http,
OpenSearch::API::Indices,
OpenSearch::API::Ingest,
OpenSearch::API::Nodes,
Expand Down
28 changes: 28 additions & 0 deletions lib/opensearch/api/actions/http/connect.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# SPDX-License-Identifier: Apache-2.0
#
# The OpenSearch Contributors require contributions made to
# this file be licensed under the Apache-2.0 license or a
# compatible open source license.

# This code was generated from OpenSearch API Spec.
# Update the code generation logic instead of modifying this file directly.

# frozen_string_literal: true

module OpenSearch
module API
module Http
module Actions
# Make a customized CONNECT request.
#
# @option arguments [String] :url Relative path to the endpoint (e.g. 'cat/indices/books,movies') (*Required*)
# @option arguments [Hash] :params Querystring parameters to be appended to the path
# @option arguments [Hash] :headers Custom HTTP headers
# @option arguments [String | Hash | Array<Hash>] :body The body of the request
def connect(url, headers: {}, body: nil, params: {})
request('CONNECT', url, headers: headers, body: body, params: params)
end
end
end
end
end
28 changes: 28 additions & 0 deletions lib/opensearch/api/actions/http/delete.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# SPDX-License-Identifier: Apache-2.0
#
# The OpenSearch Contributors require contributions made to
# this file be licensed under the Apache-2.0 license or a
# compatible open source license.

# This code was generated from OpenSearch API Spec.
# Update the code generation logic instead of modifying this file directly.

# frozen_string_literal: true

module OpenSearch
module API
module Http
module Actions
# Make a customized DELETE request.
#
# @option arguments [String] :url Relative path to the endpoint (e.g. 'cat/indices/books,movies') (*Required*)
# @option arguments [Hash] :params Querystring parameters to be appended to the path
# @option arguments [Hash] :headers Custom HTTP headers
# @option arguments [String | Hash | Array<Hash>] :body The body of the request
def delete(url, headers: {}, body: nil, params: {})
request('DELETE', url, headers: headers, body: body, params: params)
end
end
end
end
end
28 changes: 28 additions & 0 deletions lib/opensearch/api/actions/http/get.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# SPDX-License-Identifier: Apache-2.0
#
# The OpenSearch Contributors require contributions made to
# this file be licensed under the Apache-2.0 license or a
# compatible open source license.

# This code was generated from OpenSearch API Spec.
# Update the code generation logic instead of modifying this file directly.

# frozen_string_literal: true

module OpenSearch
module API
module Http
module Actions
# Make a customized GET request.
#
# @option arguments [String] :url Relative path to the endpoint (e.g. 'cat/indices/books,movies') (*Required*)
# @option arguments [Hash] :params Querystring parameters to be appended to the path
# @option arguments [Hash] :headers Custom HTTP headers
# @option arguments [String | Hash | Array<Hash>] :body The body of the request
def get(url, headers: {}, body: nil, params: {})
request('GET', url, headers: headers, body: body, params: params)
end
end
end
end
end
28 changes: 28 additions & 0 deletions lib/opensearch/api/actions/http/head.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# SPDX-License-Identifier: Apache-2.0
#
# The OpenSearch Contributors require contributions made to
# this file be licensed under the Apache-2.0 license or a
# compatible open source license.

# This code was generated from OpenSearch API Spec.
# Update the code generation logic instead of modifying this file directly.

# frozen_string_literal: true

module OpenSearch
module API
module Http
module Actions
# Make a customized HEAD request.
#
# @option arguments [String] :url Relative path to the endpoint (e.g. 'cat/indices/books,movies') (*Required*)
# @option arguments [Hash] :params Querystring parameters to be appended to the path
# @option arguments [Hash] :headers Custom HTTP headers
# @option arguments [String | Hash | Array<Hash>] :body The body of the request
def head(url, headers: {}, body: nil, params: {})
request('HEAD', url, headers: headers, body: body, params: params)
end
end
end
end
end
Loading

0 comments on commit 93b3efb

Please sign in to comment.