feat: OpenSearchClient — search and analytics API (60 endpoints)
Typed OpenSearch API covering documents, search, indices, cluster, nodes, cat, ingest pipelines, and snapshots. Bump: sunbeam-sdk v0.7.0
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -3591,7 +3591,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sunbeam-sdk"
|
name = "sunbeam-sdk"
|
||||||
version = "0.5.0"
|
version = "0.6.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "sunbeam-sdk"
|
name = "sunbeam-sdk"
|
||||||
version = "0.6.0"
|
version = "0.7.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Sunbeam SDK — reusable library for cluster management"
|
description = "Sunbeam SDK — reusable library for cluster management"
|
||||||
repository = "https://src.sunbeam.pt/studio/cli"
|
repository = "https://src.sunbeam.pt/studio/cli"
|
||||||
|
|||||||
796
sunbeam-sdk/src/search/mod.rs
Normal file
796
sunbeam-sdk/src/search/mod.rs
Normal file
@@ -0,0 +1,796 @@
|
|||||||
|
//! OpenSearch client.
|
||||||
|
|
||||||
|
pub mod types;
|
||||||
|
|
||||||
|
use crate::client::{AuthMethod, HttpTransport, ServiceClient};
|
||||||
|
use crate::error::{Result, ResultExt, SunbeamError};
|
||||||
|
use reqwest::Method;
|
||||||
|
use serde_json::Value;
|
||||||
|
use types::*;
|
||||||
|
|
||||||
|
/// Client for the OpenSearch HTTP API.
|
||||||
|
pub struct OpenSearchClient {
|
||||||
|
pub(crate) transport: HttpTransport,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServiceClient for OpenSearchClient {
|
||||||
|
fn service_name(&self) -> &'static str {
|
||||||
|
"opensearch"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn base_url(&self) -> &str {
|
||||||
|
&self.transport.base_url
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_parts(base_url: String, auth: AuthMethod) -> Self {
|
||||||
|
Self {
|
||||||
|
transport: HttpTransport::new(&base_url, auth),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OpenSearchClient {
|
||||||
|
/// Build an OpenSearchClient from domain (e.g. `https://search.{domain}`).
|
||||||
|
pub fn connect(domain: &str) -> Self {
|
||||||
|
let base_url = format!("https://search.{domain}");
|
||||||
|
Self::from_parts(base_url, AuthMethod::Bearer(String::new()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Replace the bearer token.
|
||||||
|
pub fn set_token(&mut self, token: String) {
|
||||||
|
self.transport.set_auth(AuthMethod::Bearer(token));
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Documents
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Index a document with an explicit ID.
|
||||||
|
pub async fn index_doc(
|
||||||
|
&self,
|
||||||
|
index: &str,
|
||||||
|
id: &str,
|
||||||
|
body: &Value,
|
||||||
|
) -> Result<IndexResponse> {
|
||||||
|
self.transport
|
||||||
|
.json(Method::PUT, &format!("{index}/_doc/{id}"), Some(body), "opensearch index doc")
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Index a document with an auto-generated ID.
|
||||||
|
pub async fn index_doc_auto_id(
|
||||||
|
&self,
|
||||||
|
index: &str,
|
||||||
|
body: &Value,
|
||||||
|
) -> Result<IndexResponse> {
|
||||||
|
self.transport
|
||||||
|
.json(Method::POST, &format!("{index}/_doc"), Some(body), "opensearch index doc auto")
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a document by ID.
|
||||||
|
pub async fn get_doc(&self, index: &str, id: &str) -> Result<GetResponse> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::GET,
|
||||||
|
&format!("{index}/_doc/{id}"),
|
||||||
|
Option::<&()>::None,
|
||||||
|
"opensearch get doc",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a document exists (HEAD request, returns bool).
|
||||||
|
pub async fn head_doc(&self, index: &str, id: &str) -> Result<bool> {
|
||||||
|
let resp = self
|
||||||
|
.transport
|
||||||
|
.request(Method::HEAD, &format!("{index}/_doc/{id}"))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.with_ctx(|| "opensearch head doc: request failed".to_string())?;
|
||||||
|
Ok(resp.status().is_success())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete a document by ID.
|
||||||
|
pub async fn delete_doc(&self, index: &str, id: &str) -> Result<DeleteResponse> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::DELETE,
|
||||||
|
&format!("{index}/_doc/{id}"),
|
||||||
|
Option::<&()>::None,
|
||||||
|
"opensearch delete doc",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update a document by ID.
|
||||||
|
pub async fn update_doc(
|
||||||
|
&self,
|
||||||
|
index: &str,
|
||||||
|
id: &str,
|
||||||
|
body: &Value,
|
||||||
|
) -> Result<UpdateResponse> {
|
||||||
|
self.transport
|
||||||
|
.json(Method::POST, &format!("{index}/_update/{id}"), Some(body), "opensearch update doc")
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bulk index/update/delete operations.
|
||||||
|
pub async fn bulk(&self, body: &Value) -> Result<BulkResponse> {
|
||||||
|
self.transport
|
||||||
|
.json(Method::POST, "_bulk", Some(body), "opensearch bulk")
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Multi-get documents.
|
||||||
|
pub async fn multi_get(&self, body: &Value) -> Result<MultiGetResponse> {
|
||||||
|
self.transport
|
||||||
|
.json(Method::POST, "_mget", Some(body), "opensearch mget")
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reindex documents from one index to another.
|
||||||
|
pub async fn reindex(&self, body: &Value) -> Result<ReindexResponse> {
|
||||||
|
self.transport
|
||||||
|
.json(Method::POST, "_reindex", Some(body), "opensearch reindex")
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete documents matching a query.
|
||||||
|
pub async fn delete_by_query(
|
||||||
|
&self,
|
||||||
|
index: &str,
|
||||||
|
body: &Value,
|
||||||
|
) -> Result<DeleteByQueryResponse> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::POST,
|
||||||
|
&format!("{index}/_delete_by_query"),
|
||||||
|
Some(body),
|
||||||
|
"opensearch delete by query",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Search
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Search an index.
|
||||||
|
pub async fn search(&self, index: &str, body: &Value) -> Result<SearchResponse> {
|
||||||
|
self.transport
|
||||||
|
.json(Method::POST, &format!("{index}/_search"), Some(body), "opensearch search")
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Search across all indices.
|
||||||
|
pub async fn search_all(&self, body: &Value) -> Result<SearchResponse> {
|
||||||
|
self.transport
|
||||||
|
.json(Method::POST, "_search", Some(body), "opensearch search all")
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Multi-search.
|
||||||
|
pub async fn multi_search(&self, body: &Value) -> Result<MultiSearchResponse> {
|
||||||
|
self.transport
|
||||||
|
.json(Method::POST, "_msearch", Some(body), "opensearch msearch")
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Count documents matching a query.
|
||||||
|
pub async fn count(&self, index: &str, body: &Value) -> Result<CountResponse> {
|
||||||
|
self.transport
|
||||||
|
.json(Method::POST, &format!("{index}/_count"), Some(body), "opensearch count")
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scroll through search results.
|
||||||
|
pub async fn scroll(&self, body: &Value) -> Result<SearchResponse> {
|
||||||
|
self.transport
|
||||||
|
.json(Method::POST, "_search/scroll", Some(body), "opensearch scroll")
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear a scroll context.
|
||||||
|
pub async fn clear_scroll(&self, body: &Value) -> Result<()> {
|
||||||
|
self.transport
|
||||||
|
.send(Method::DELETE, "_search/scroll", Some(body), "opensearch clear scroll")
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get search shard information.
|
||||||
|
pub async fn search_shards(&self, index: &str) -> Result<ShardsResponse> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::GET,
|
||||||
|
&format!("{index}/_search_shards"),
|
||||||
|
Option::<&()>::None,
|
||||||
|
"opensearch search shards",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute a search template.
|
||||||
|
pub async fn search_template(&self, body: &Value) -> Result<SearchResponse> {
|
||||||
|
self.transport
|
||||||
|
.json(Method::POST, "_search/template", Some(body), "opensearch search template")
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Indices
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Create an index.
|
||||||
|
pub async fn create_index(&self, index: &str, body: &Value) -> Result<AckResponse> {
|
||||||
|
self.transport
|
||||||
|
.json(Method::PUT, index, Some(body), "opensearch create index")
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete an index.
|
||||||
|
pub async fn delete_index(&self, index: &str) -> Result<AckResponse> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::DELETE,
|
||||||
|
index,
|
||||||
|
Option::<&()>::None,
|
||||||
|
"opensearch delete index",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get index metadata.
|
||||||
|
pub async fn get_index(&self, index: &str) -> Result<Value> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::GET,
|
||||||
|
index,
|
||||||
|
Option::<&()>::None,
|
||||||
|
"opensearch get index",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if an index exists (HEAD request, returns bool).
|
||||||
|
pub async fn index_exists(&self, index: &str) -> Result<bool> {
|
||||||
|
let resp = self
|
||||||
|
.transport
|
||||||
|
.request(Method::HEAD, index)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.with_ctx(|| "opensearch index exists: request failed".to_string())?;
|
||||||
|
Ok(resp.status().is_success())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get index settings.
|
||||||
|
pub async fn get_settings(&self, index: &str) -> Result<Value> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::GET,
|
||||||
|
&format!("{index}/_settings"),
|
||||||
|
Option::<&()>::None,
|
||||||
|
"opensearch get settings",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update index settings.
|
||||||
|
pub async fn update_settings(&self, index: &str, body: &Value) -> Result<AckResponse> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::PUT,
|
||||||
|
&format!("{index}/_settings"),
|
||||||
|
Some(body),
|
||||||
|
"opensearch update settings",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get index mapping.
|
||||||
|
pub async fn get_mapping(&self, index: &str) -> Result<Value> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::GET,
|
||||||
|
&format!("{index}/_mapping"),
|
||||||
|
Option::<&()>::None,
|
||||||
|
"opensearch get mapping",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Put (update) index mapping.
|
||||||
|
pub async fn put_mapping(&self, index: &str, body: &Value) -> Result<AckResponse> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::PUT,
|
||||||
|
&format!("{index}/_mapping"),
|
||||||
|
Some(body),
|
||||||
|
"opensearch put mapping",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get aliases for an index.
|
||||||
|
pub async fn get_aliases(&self, index: &str) -> Result<Value> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::GET,
|
||||||
|
&format!("{index}/_alias"),
|
||||||
|
Option::<&()>::None,
|
||||||
|
"opensearch get aliases",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create or update aliases (bulk alias action).
|
||||||
|
pub async fn create_alias(&self, body: &Value) -> Result<AckResponse> {
|
||||||
|
self.transport
|
||||||
|
.json(Method::POST, "_aliases", Some(body), "opensearch create alias")
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete an alias from an index.
|
||||||
|
pub async fn delete_alias(&self, index: &str, alias: &str) -> Result<AckResponse> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::DELETE,
|
||||||
|
&format!("{index}/_alias/{alias}"),
|
||||||
|
Option::<&()>::None,
|
||||||
|
"opensearch delete alias",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create or update an index template.
|
||||||
|
pub async fn create_template(&self, name: &str, body: &Value) -> Result<AckResponse> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::PUT,
|
||||||
|
&format!("_index_template/{name}"),
|
||||||
|
Some(body),
|
||||||
|
"opensearch create template",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete an index template.
|
||||||
|
pub async fn delete_template(&self, name: &str) -> Result<AckResponse> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::DELETE,
|
||||||
|
&format!("_index_template/{name}"),
|
||||||
|
Option::<&()>::None,
|
||||||
|
"opensearch delete template",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get an index template.
|
||||||
|
pub async fn get_template(&self, name: &str) -> Result<Value> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::GET,
|
||||||
|
&format!("_index_template/{name}"),
|
||||||
|
Option::<&()>::None,
|
||||||
|
"opensearch get template",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Open a closed index.
|
||||||
|
pub async fn open_index(&self, index: &str) -> Result<AckResponse> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::POST,
|
||||||
|
&format!("{index}/_open"),
|
||||||
|
Option::<&()>::None,
|
||||||
|
"opensearch open index",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Close an index.
|
||||||
|
pub async fn close_index(&self, index: &str) -> Result<AckResponse> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::POST,
|
||||||
|
&format!("{index}/_close"),
|
||||||
|
Option::<&()>::None,
|
||||||
|
"opensearch close index",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Cluster
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Get cluster health.
|
||||||
|
pub async fn cluster_health(&self) -> Result<ClusterHealth> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::GET,
|
||||||
|
"_cluster/health",
|
||||||
|
Option::<&()>::None,
|
||||||
|
"opensearch cluster health",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get cluster state.
|
||||||
|
pub async fn cluster_state(&self) -> Result<Value> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::GET,
|
||||||
|
"_cluster/state",
|
||||||
|
Option::<&()>::None,
|
||||||
|
"opensearch cluster state",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get cluster stats.
|
||||||
|
pub async fn cluster_stats(&self) -> Result<Value> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::GET,
|
||||||
|
"_cluster/stats",
|
||||||
|
Option::<&()>::None,
|
||||||
|
"opensearch cluster stats",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get cluster settings.
|
||||||
|
pub async fn cluster_settings(&self) -> Result<Value> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::GET,
|
||||||
|
"_cluster/settings",
|
||||||
|
Option::<&()>::None,
|
||||||
|
"opensearch cluster settings",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update cluster settings.
|
||||||
|
pub async fn update_cluster_settings(&self, body: &Value) -> Result<Value> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::PUT,
|
||||||
|
"_cluster/settings",
|
||||||
|
Some(body),
|
||||||
|
"opensearch update cluster settings",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Explain shard allocation.
|
||||||
|
pub async fn allocation_explain(&self, body: &Value) -> Result<Value> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::POST,
|
||||||
|
"_cluster/allocation/explain",
|
||||||
|
Some(body),
|
||||||
|
"opensearch allocation explain",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reroute shards.
|
||||||
|
pub async fn reroute(&self, body: &Value) -> Result<Value> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::POST,
|
||||||
|
"_cluster/reroute",
|
||||||
|
Some(body),
|
||||||
|
"opensearch reroute",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Nodes
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Get nodes info.
|
||||||
|
pub async fn nodes_info(&self) -> Result<Value> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::GET,
|
||||||
|
"_nodes",
|
||||||
|
Option::<&()>::None,
|
||||||
|
"opensearch nodes info",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get nodes stats.
|
||||||
|
pub async fn nodes_stats(&self) -> Result<Value> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::GET,
|
||||||
|
"_nodes/stats",
|
||||||
|
Option::<&()>::None,
|
||||||
|
"opensearch nodes stats",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get nodes hot threads (returns plain text).
|
||||||
|
pub async fn nodes_hot_threads(&self) -> Result<String> {
|
||||||
|
let bytes = self
|
||||||
|
.transport
|
||||||
|
.bytes(Method::GET, "_nodes/hot_threads", "opensearch hot threads")
|
||||||
|
.await?;
|
||||||
|
String::from_utf8(bytes.to_vec())
|
||||||
|
.map_err(|e| SunbeamError::network(format!("opensearch hot threads: invalid UTF-8: {e}")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Cat
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// List indices via the cat API.
|
||||||
|
pub async fn cat_indices(&self) -> Result<Vec<CatIndex>> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::GET,
|
||||||
|
"_cat/indices?format=json",
|
||||||
|
Option::<&()>::None,
|
||||||
|
"opensearch cat indices",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List nodes via the cat API.
|
||||||
|
pub async fn cat_nodes(&self) -> Result<Vec<CatNode>> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::GET,
|
||||||
|
"_cat/nodes?format=json",
|
||||||
|
Option::<&()>::None,
|
||||||
|
"opensearch cat nodes",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List shards via the cat API.
|
||||||
|
pub async fn cat_shards(&self) -> Result<Vec<CatShard>> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::GET,
|
||||||
|
"_cat/shards?format=json",
|
||||||
|
Option::<&()>::None,
|
||||||
|
"opensearch cat shards",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cluster health via the cat API.
|
||||||
|
pub async fn cat_health(&self) -> Result<Vec<CatHealth>> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::GET,
|
||||||
|
"_cat/health?format=json",
|
||||||
|
Option::<&()>::None,
|
||||||
|
"opensearch cat health",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allocation info via the cat API.
|
||||||
|
pub async fn cat_allocation(&self) -> Result<Vec<CatAllocation>> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::GET,
|
||||||
|
"_cat/allocation?format=json",
|
||||||
|
Option::<&()>::None,
|
||||||
|
"opensearch cat allocation",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Ingest
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Create or update an ingest pipeline.
|
||||||
|
pub async fn create_pipeline(&self, id: &str, body: &Value) -> Result<AckResponse> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::PUT,
|
||||||
|
&format!("_ingest/pipeline/{id}"),
|
||||||
|
Some(body),
|
||||||
|
"opensearch create pipeline",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get an ingest pipeline.
|
||||||
|
pub async fn get_pipeline(&self, id: &str) -> Result<Value> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::GET,
|
||||||
|
&format!("_ingest/pipeline/{id}"),
|
||||||
|
Option::<&()>::None,
|
||||||
|
"opensearch get pipeline",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete an ingest pipeline.
|
||||||
|
pub async fn delete_pipeline(&self, id: &str) -> Result<AckResponse> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::DELETE,
|
||||||
|
&format!("_ingest/pipeline/{id}"),
|
||||||
|
Option::<&()>::None,
|
||||||
|
"opensearch delete pipeline",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Simulate an ingest pipeline.
|
||||||
|
pub async fn simulate_pipeline(&self, id: &str, body: &Value) -> Result<Value> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::POST,
|
||||||
|
&format!("_ingest/pipeline/{id}/_simulate"),
|
||||||
|
Some(body),
|
||||||
|
"opensearch simulate pipeline",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all ingest pipelines.
|
||||||
|
pub async fn get_all_pipelines(&self) -> Result<Value> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::GET,
|
||||||
|
"_ingest/pipeline",
|
||||||
|
Option::<&()>::None,
|
||||||
|
"opensearch get all pipelines",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Snapshots
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Create or update a snapshot repository.
|
||||||
|
pub async fn create_snapshot_repo(&self, name: &str, body: &Value) -> Result<AckResponse> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::PUT,
|
||||||
|
&format!("_snapshot/{name}"),
|
||||||
|
Some(body),
|
||||||
|
"opensearch create snapshot repo",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete a snapshot repository.
|
||||||
|
pub async fn delete_snapshot_repo(&self, name: &str) -> Result<AckResponse> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::DELETE,
|
||||||
|
&format!("_snapshot/{name}"),
|
||||||
|
Option::<&()>::None,
|
||||||
|
"opensearch delete snapshot repo",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a snapshot.
|
||||||
|
pub async fn create_snapshot(
|
||||||
|
&self,
|
||||||
|
repo: &str,
|
||||||
|
name: &str,
|
||||||
|
body: &Value,
|
||||||
|
) -> Result<Value> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::PUT,
|
||||||
|
&format!("_snapshot/{repo}/{name}"),
|
||||||
|
Some(body),
|
||||||
|
"opensearch create snapshot",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete a snapshot.
|
||||||
|
pub async fn delete_snapshot(&self, repo: &str, name: &str) -> Result<AckResponse> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::DELETE,
|
||||||
|
&format!("_snapshot/{repo}/{name}"),
|
||||||
|
Option::<&()>::None,
|
||||||
|
"opensearch delete snapshot",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List all snapshots in a repository.
|
||||||
|
pub async fn list_snapshots(&self, repo: &str) -> Result<Value> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::GET,
|
||||||
|
&format!("_snapshot/{repo}/_all"),
|
||||||
|
Option::<&()>::None,
|
||||||
|
"opensearch list snapshots",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Restore a snapshot.
|
||||||
|
pub async fn restore_snapshot(
|
||||||
|
&self,
|
||||||
|
repo: &str,
|
||||||
|
name: &str,
|
||||||
|
body: &Value,
|
||||||
|
) -> Result<Value> {
|
||||||
|
self.transport
|
||||||
|
.json(
|
||||||
|
Method::POST,
|
||||||
|
&format!("_snapshot/{repo}/{name}/_restore"),
|
||||||
|
Some(body),
|
||||||
|
"opensearch restore snapshot",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Tests
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_connect_url() {
|
||||||
|
let c = OpenSearchClient::connect("sunbeam.pt");
|
||||||
|
assert_eq!(c.base_url(), "https://search.sunbeam.pt");
|
||||||
|
assert_eq!(c.service_name(), "opensearch");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_parts() {
|
||||||
|
let c = OpenSearchClient::from_parts(
|
||||||
|
"http://localhost:9200".into(),
|
||||||
|
AuthMethod::Bearer("tok".into()),
|
||||||
|
);
|
||||||
|
assert_eq!(c.base_url(), "http://localhost:9200");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_set_token() {
|
||||||
|
let mut c = OpenSearchClient::connect("example.com");
|
||||||
|
c.set_token("my-token".into());
|
||||||
|
assert!(matches!(c.transport.auth, AuthMethod::Bearer(ref s) if s == "my-token"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_head_doc_unreachable() {
|
||||||
|
let c = OpenSearchClient::from_parts(
|
||||||
|
"http://127.0.0.1:19998".into(),
|
||||||
|
AuthMethod::None,
|
||||||
|
);
|
||||||
|
let result = c.head_doc("test", "1").await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_index_exists_unreachable() {
|
||||||
|
let c = OpenSearchClient::from_parts(
|
||||||
|
"http://127.0.0.1:19998".into(),
|
||||||
|
AuthMethod::None,
|
||||||
|
);
|
||||||
|
let result = c.index_exists("test").await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
551
sunbeam-sdk/src/search/types.rs
Normal file
551
sunbeam-sdk/src/search/types.rs
Normal file
@@ -0,0 +1,551 @@
|
|||||||
|
//! OpenSearch response types.
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Document responses
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Response from index / delete / update operations.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct IndexResponse {
|
||||||
|
#[serde(rename = "_index")]
|
||||||
|
pub index: String,
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
pub id: String,
|
||||||
|
#[serde(rename = "_version", default)]
|
||||||
|
pub version: Option<u64>,
|
||||||
|
pub result: Option<String>,
|
||||||
|
#[serde(rename = "_shards", default)]
|
||||||
|
pub shards: Option<ShardInfo>,
|
||||||
|
#[serde(rename = "_seq_no", default)]
|
||||||
|
pub seq_no: Option<u64>,
|
||||||
|
#[serde(rename = "_primary_term", default)]
|
||||||
|
pub primary_term: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shard success/failure counters.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ShardInfo {
|
||||||
|
pub total: u32,
|
||||||
|
pub successful: u32,
|
||||||
|
pub failed: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Response from GET `{index}/_doc/{id}`.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct GetResponse {
|
||||||
|
#[serde(rename = "_index")]
|
||||||
|
pub index: String,
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
pub id: String,
|
||||||
|
#[serde(rename = "_version", default)]
|
||||||
|
pub version: Option<u64>,
|
||||||
|
#[serde(rename = "_seq_no", default)]
|
||||||
|
pub seq_no: Option<u64>,
|
||||||
|
#[serde(rename = "_primary_term", default)]
|
||||||
|
pub primary_term: Option<u64>,
|
||||||
|
pub found: bool,
|
||||||
|
#[serde(rename = "_source", default)]
|
||||||
|
pub source: Option<serde_json::Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Response from DELETE `{index}/_doc/{id}`.
|
||||||
|
pub type DeleteResponse = IndexResponse;
|
||||||
|
|
||||||
|
/// Response from POST `{index}/_update/{id}`.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct UpdateResponse {
|
||||||
|
#[serde(rename = "_index")]
|
||||||
|
pub index: String,
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
pub id: String,
|
||||||
|
#[serde(rename = "_version", default)]
|
||||||
|
pub version: Option<u64>,
|
||||||
|
pub result: Option<String>,
|
||||||
|
#[serde(rename = "_shards", default)]
|
||||||
|
pub shards: Option<ShardInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Bulk
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Response from POST `_bulk`.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct BulkResponse {
|
||||||
|
pub took: u64,
|
||||||
|
pub errors: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub items: Vec<serde_json::Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Multi-get
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Response from POST `_mget`.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct MultiGetResponse {
|
||||||
|
pub docs: Vec<GetResponse>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Reindex
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Response from POST `_reindex`.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ReindexResponse {
|
||||||
|
pub took: u64,
|
||||||
|
#[serde(default)]
|
||||||
|
pub timed_out: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub total: u64,
|
||||||
|
#[serde(default)]
|
||||||
|
pub updated: u64,
|
||||||
|
#[serde(default)]
|
||||||
|
pub created: u64,
|
||||||
|
#[serde(default)]
|
||||||
|
pub deleted: u64,
|
||||||
|
#[serde(default)]
|
||||||
|
pub failures: Vec<serde_json::Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Delete by query
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Response from POST `{index}/_delete_by_query`.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct DeleteByQueryResponse {
|
||||||
|
pub took: u64,
|
||||||
|
#[serde(default)]
|
||||||
|
pub timed_out: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub total: u64,
|
||||||
|
#[serde(default)]
|
||||||
|
pub deleted: u64,
|
||||||
|
#[serde(default)]
|
||||||
|
pub failures: Vec<serde_json::Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Search
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Response from POST `{index}/_search` and `_search`.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct SearchResponse {
|
||||||
|
pub took: u64,
|
||||||
|
pub timed_out: bool,
|
||||||
|
#[serde(rename = "_shards", default)]
|
||||||
|
pub shards: Option<ShardInfo>,
|
||||||
|
pub hits: HitsEnvelope,
|
||||||
|
#[serde(default)]
|
||||||
|
pub aggregations: Option<serde_json::Value>,
|
||||||
|
#[serde(rename = "_scroll_id", default)]
|
||||||
|
pub scroll_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Top-level hits wrapper.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct HitsEnvelope {
|
||||||
|
pub total: HitsTotal,
|
||||||
|
#[serde(default)]
|
||||||
|
pub max_score: Option<f64>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub hits: Vec<Hit>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Total hit count.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct HitsTotal {
|
||||||
|
pub value: u64,
|
||||||
|
#[serde(default)]
|
||||||
|
pub relation: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A single search hit.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Hit {
|
||||||
|
#[serde(rename = "_index")]
|
||||||
|
pub index: String,
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
pub id: String,
|
||||||
|
#[serde(rename = "_score", default)]
|
||||||
|
pub score: Option<f64>,
|
||||||
|
#[serde(rename = "_source", default)]
|
||||||
|
pub source: Option<serde_json::Value>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub highlight: Option<serde_json::Value>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub sort: Option<Vec<serde_json::Value>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Response from POST `_msearch`.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct MultiSearchResponse {
|
||||||
|
pub responses: Vec<SearchResponse>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Response from POST `{index}/_count`.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct CountResponse {
|
||||||
|
pub count: u64,
|
||||||
|
#[serde(rename = "_shards", default)]
|
||||||
|
pub shards: Option<ShardInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Response from GET `{index}/_search_shards`.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ShardsResponse {
|
||||||
|
#[serde(default)]
|
||||||
|
pub nodes: serde_json::Value,
|
||||||
|
#[serde(default)]
|
||||||
|
pub shards: Vec<Vec<serde_json::Value>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Index management
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Acknowledged response from index management operations.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct AckResponse {
|
||||||
|
pub acknowledged: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub shards_acknowledged: Option<bool>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub index: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Cluster
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Response from GET `_cluster/health`.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ClusterHealth {
|
||||||
|
pub cluster_name: String,
|
||||||
|
pub status: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub timed_out: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub number_of_nodes: u32,
|
||||||
|
#[serde(default)]
|
||||||
|
pub number_of_data_nodes: u32,
|
||||||
|
#[serde(default)]
|
||||||
|
pub active_primary_shards: u32,
|
||||||
|
#[serde(default)]
|
||||||
|
pub active_shards: u32,
|
||||||
|
#[serde(default)]
|
||||||
|
pub relocating_shards: u32,
|
||||||
|
#[serde(default)]
|
||||||
|
pub initializing_shards: u32,
|
||||||
|
#[serde(default)]
|
||||||
|
pub unassigned_shards: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Cat responses
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// A row from `_cat/indices?format=json`.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct CatIndex {
|
||||||
|
#[serde(default)]
|
||||||
|
pub health: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub status: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub index: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub uuid: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub pri: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub rep: Option<String>,
|
||||||
|
#[serde(rename = "docs.count", default)]
|
||||||
|
pub docs_count: Option<String>,
|
||||||
|
#[serde(rename = "docs.deleted", default)]
|
||||||
|
pub docs_deleted: Option<String>,
|
||||||
|
#[serde(rename = "store.size", default)]
|
||||||
|
pub store_size: Option<String>,
|
||||||
|
#[serde(rename = "pri.store.size", default)]
|
||||||
|
pub pri_store_size: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A row from `_cat/nodes?format=json`.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct CatNode {
|
||||||
|
#[serde(default)]
|
||||||
|
pub ip: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub name: Option<String>,
|
||||||
|
#[serde(rename = "heap.percent", default)]
|
||||||
|
pub heap_percent: Option<String>,
|
||||||
|
#[serde(rename = "ram.percent", default)]
|
||||||
|
pub ram_percent: Option<String>,
|
||||||
|
#[serde(rename = "cpu", default)]
|
||||||
|
pub cpu: Option<String>,
|
||||||
|
#[serde(rename = "node.role", default)]
|
||||||
|
pub node_role: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub master: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A row from `_cat/shards?format=json`.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct CatShard {
|
||||||
|
#[serde(default)]
|
||||||
|
pub index: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub shard: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub prirep: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub state: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub docs: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub store: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub node: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A row from `_cat/health?format=json`.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct CatHealth {
|
||||||
|
#[serde(default)]
|
||||||
|
pub cluster: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub status: Option<String>,
|
||||||
|
#[serde(rename = "node.total", default)]
|
||||||
|
pub node_total: Option<String>,
|
||||||
|
#[serde(rename = "node.data", default)]
|
||||||
|
pub node_data: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub shards: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub pri: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub relo: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub init: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub unassign: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A row from `_cat/allocation?format=json`.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct CatAllocation {
|
||||||
|
#[serde(default)]
|
||||||
|
pub shards: Option<String>,
|
||||||
|
#[serde(rename = "disk.indices", default)]
|
||||||
|
pub disk_indices: Option<String>,
|
||||||
|
#[serde(rename = "disk.used", default)]
|
||||||
|
pub disk_used: Option<String>,
|
||||||
|
#[serde(rename = "disk.avail", default)]
|
||||||
|
pub disk_avail: Option<String>,
|
||||||
|
#[serde(rename = "disk.total", default)]
|
||||||
|
pub disk_total: Option<String>,
|
||||||
|
#[serde(rename = "disk.percent", default)]
|
||||||
|
pub disk_percent: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub host: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub node: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Tests
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_index_response() {
|
||||||
|
let json = r#"{
|
||||||
|
"_index": "test",
|
||||||
|
"_id": "1",
|
||||||
|
"_version": 1,
|
||||||
|
"result": "created",
|
||||||
|
"_shards": {"total": 2, "successful": 1, "failed": 0},
|
||||||
|
"_seq_no": 0,
|
||||||
|
"_primary_term": 1
|
||||||
|
}"#;
|
||||||
|
let resp: IndexResponse = serde_json::from_str(json).unwrap();
|
||||||
|
assert_eq!(resp.index, "test");
|
||||||
|
assert_eq!(resp.id, "1");
|
||||||
|
assert_eq!(resp.result.as_deref(), Some("created"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_get_response_found() {
|
||||||
|
let json = r#"{
|
||||||
|
"_index": "test",
|
||||||
|
"_id": "1",
|
||||||
|
"_version": 1,
|
||||||
|
"found": true,
|
||||||
|
"_source": {"title": "Hello"}
|
||||||
|
}"#;
|
||||||
|
let resp: GetResponse = serde_json::from_str(json).unwrap();
|
||||||
|
assert!(resp.found);
|
||||||
|
assert!(resp.source.is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_get_response_not_found() {
|
||||||
|
let json = r#"{
|
||||||
|
"_index": "test",
|
||||||
|
"_id": "999",
|
||||||
|
"found": false
|
||||||
|
}"#;
|
||||||
|
let resp: GetResponse = serde_json::from_str(json).unwrap();
|
||||||
|
assert!(!resp.found);
|
||||||
|
assert!(resp.source.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_search_response() {
|
||||||
|
let json = r#"{
|
||||||
|
"took": 5,
|
||||||
|
"timed_out": false,
|
||||||
|
"_shards": {"total": 5, "successful": 5, "failed": 0},
|
||||||
|
"hits": {
|
||||||
|
"total": {"value": 1, "relation": "eq"},
|
||||||
|
"max_score": 1.0,
|
||||||
|
"hits": [{
|
||||||
|
"_index": "test",
|
||||||
|
"_id": "1",
|
||||||
|
"_score": 1.0,
|
||||||
|
"_source": {"title": "Hello"}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
let resp: SearchResponse = serde_json::from_str(json).unwrap();
|
||||||
|
assert_eq!(resp.took, 5);
|
||||||
|
assert!(!resp.timed_out);
|
||||||
|
assert_eq!(resp.hits.total.value, 1);
|
||||||
|
assert_eq!(resp.hits.hits.len(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_bulk_response() {
|
||||||
|
let json = r#"{
|
||||||
|
"took": 30,
|
||||||
|
"errors": false,
|
||||||
|
"items": [{"index": {"_index": "test", "_id": "1", "result": "created"}}]
|
||||||
|
}"#;
|
||||||
|
let resp: BulkResponse = serde_json::from_str(json).unwrap();
|
||||||
|
assert_eq!(resp.took, 30);
|
||||||
|
assert!(!resp.errors);
|
||||||
|
assert_eq!(resp.items.len(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_count_response() {
|
||||||
|
let json = r#"{
|
||||||
|
"count": 42,
|
||||||
|
"_shards": {"total": 5, "successful": 5, "failed": 0}
|
||||||
|
}"#;
|
||||||
|
let resp: CountResponse = serde_json::from_str(json).unwrap();
|
||||||
|
assert_eq!(resp.count, 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_ack_response() {
|
||||||
|
let json = r#"{
|
||||||
|
"acknowledged": true,
|
||||||
|
"shards_acknowledged": true,
|
||||||
|
"index": "my-index"
|
||||||
|
}"#;
|
||||||
|
let resp: AckResponse = serde_json::from_str(json).unwrap();
|
||||||
|
assert!(resp.acknowledged);
|
||||||
|
assert_eq!(resp.index.as_deref(), Some("my-index"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_cluster_health() {
|
||||||
|
let json = r#"{
|
||||||
|
"cluster_name": "opensearch-cluster",
|
||||||
|
"status": "green",
|
||||||
|
"timed_out": false,
|
||||||
|
"number_of_nodes": 3,
|
||||||
|
"number_of_data_nodes": 3,
|
||||||
|
"active_primary_shards": 10,
|
||||||
|
"active_shards": 20,
|
||||||
|
"relocating_shards": 0,
|
||||||
|
"initializing_shards": 0,
|
||||||
|
"unassigned_shards": 0
|
||||||
|
}"#;
|
||||||
|
let resp: ClusterHealth = serde_json::from_str(json).unwrap();
|
||||||
|
assert_eq!(resp.cluster_name, "opensearch-cluster");
|
||||||
|
assert_eq!(resp.status, "green");
|
||||||
|
assert_eq!(resp.number_of_nodes, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_cat_index() {
|
||||||
|
let json = r#"{
|
||||||
|
"health": "green",
|
||||||
|
"status": "open",
|
||||||
|
"index": "my-index",
|
||||||
|
"uuid": "abc123",
|
||||||
|
"pri": "1",
|
||||||
|
"rep": "1",
|
||||||
|
"docs.count": "100",
|
||||||
|
"store.size": "10mb"
|
||||||
|
}"#;
|
||||||
|
let resp: CatIndex = serde_json::from_str(json).unwrap();
|
||||||
|
assert_eq!(resp.index.as_deref(), Some("my-index"));
|
||||||
|
assert_eq!(resp.docs_count.as_deref(), Some("100"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_reindex_response() {
|
||||||
|
let json = r#"{
|
||||||
|
"took": 100,
|
||||||
|
"timed_out": false,
|
||||||
|
"total": 50,
|
||||||
|
"updated": 0,
|
||||||
|
"created": 50,
|
||||||
|
"deleted": 0,
|
||||||
|
"failures": []
|
||||||
|
}"#;
|
||||||
|
let resp: ReindexResponse = serde_json::from_str(json).unwrap();
|
||||||
|
assert_eq!(resp.took, 100);
|
||||||
|
assert_eq!(resp.created, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_delete_by_query_response() {
|
||||||
|
let json = r#"{
|
||||||
|
"took": 10,
|
||||||
|
"timed_out": false,
|
||||||
|
"total": 5,
|
||||||
|
"deleted": 5,
|
||||||
|
"failures": []
|
||||||
|
}"#;
|
||||||
|
let resp: DeleteByQueryResponse = serde_json::from_str(json).unwrap();
|
||||||
|
assert_eq!(resp.deleted, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_multi_get_response() {
|
||||||
|
let json = r#"{
|
||||||
|
"docs": [{
|
||||||
|
"_index": "test",
|
||||||
|
"_id": "1",
|
||||||
|
"found": true,
|
||||||
|
"_source": {"title": "Hello"}
|
||||||
|
}]
|
||||||
|
}"#;
|
||||||
|
let resp: MultiGetResponse = serde_json::from_str(json).unwrap();
|
||||||
|
assert_eq!(resp.docs.len(), 1);
|
||||||
|
assert!(resp.docs[0].found);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user