feat(opensearch): refactoring (#4244)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
Co-authored-by: Sanchith Hegde <22217505+SanchithHegde@users.noreply.github.com>
This commit is contained in:
ivor-juspay
2024-05-02 15:50:58 +05:30
committed by GitHub
parent e4ed1e6395
commit 22cb01ac1e
16 changed files with 693 additions and 151 deletions

View File

@ -2,99 +2,33 @@ use api_models::analytics::search::{
GetGlobalSearchRequest, GetSearchRequestWithIndex, GetSearchResponse, OpenMsearchOutput,
OpensearchOutput, SearchIndex,
};
use aws_config::{self, meta::region::RegionProviderChain, Region};
use common_utils::errors::CustomResult;
use opensearch::{
auth::Credentials,
cert::CertificateValidation,
http::{
request::JsonBody,
transport::{SingleNodeConnectionPool, TransportBuilder},
Url,
},
MsearchParts, OpenSearch, SearchParts,
};
use serde_json::{json, Value};
use common_utils::errors::{CustomResult, ReportSwitchExt};
use error_stack::ResultExt;
use serde_json::Value;
use strum::IntoEnumIterator;
use crate::{errors::AnalyticsError, OpensearchAuth, OpensearchConfig, OpensearchIndexes};
#[derive(Debug, thiserror::Error)]
pub enum OpensearchError {
#[error("Opensearch connection error")]
ConnectionError,
#[error("Opensearch NON-200 response content: '{0}'")]
ResponseNotOK(String),
#[error("Opensearch response error")]
ResponseError,
}
pub fn search_index_to_opensearch_index(index: SearchIndex, config: &OpensearchIndexes) -> String {
match index {
SearchIndex::PaymentAttempts => config.payment_attempts.clone(),
SearchIndex::PaymentIntents => config.payment_intents.clone(),
SearchIndex::Refunds => config.refunds.clone(),
SearchIndex::Disputes => config.disputes.clone(),
}
}
async fn get_opensearch_client(config: OpensearchConfig) -> Result<OpenSearch, OpensearchError> {
let url = Url::parse(&config.host).map_err(|_| OpensearchError::ConnectionError)?;
let transport = match config.auth {
OpensearchAuth::Basic { username, password } => {
let credentials = Credentials::Basic(username, password);
TransportBuilder::new(SingleNodeConnectionPool::new(url))
.cert_validation(CertificateValidation::None)
.auth(credentials)
.build()
.map_err(|_| OpensearchError::ConnectionError)?
}
OpensearchAuth::Aws { region } => {
let region_provider = RegionProviderChain::first_try(Region::new(region));
let sdk_config = aws_config::from_env().region(region_provider).load().await;
let conn_pool = SingleNodeConnectionPool::new(url);
TransportBuilder::new(conn_pool)
.auth(
sdk_config
.clone()
.try_into()
.map_err(|_| OpensearchError::ConnectionError)?,
)
.service_name("es")
.build()
.map_err(|_| OpensearchError::ConnectionError)?
}
};
Ok(OpenSearch::new(transport))
}
use crate::opensearch::{
OpenSearchClient, OpenSearchError, OpenSearchQuery, OpenSearchQueryBuilder,
};
pub async fn msearch_results(
client: &OpenSearchClient,
req: GetGlobalSearchRequest,
merchant_id: &String,
config: OpensearchConfig,
) -> CustomResult<Vec<GetSearchResponse>, AnalyticsError> {
let client = get_opensearch_client(config.clone())
) -> CustomResult<Vec<GetSearchResponse>, OpenSearchError> {
let mut query_builder = OpenSearchQueryBuilder::new(OpenSearchQuery::Msearch, req.query);
query_builder
.add_filter_clause("merchant_id".to_string(), merchant_id.to_string())
.switch()?;
let response_body = client
.execute(query_builder)
.await
.map_err(|_| AnalyticsError::UnknownError)?;
let mut msearch_vector: Vec<JsonBody<Value>> = vec![];
for index in SearchIndex::iter() {
msearch_vector
.push(json!({"index": search_index_to_opensearch_index(index,&config.indexes)}).into());
msearch_vector.push(json!({"query": {"bool": {"filter": [{"multi_match": {"type": "phrase", "query": req.query, "lenient": true}},{"match_phrase": {"merchant_id": merchant_id}}]}}}).into());
}
let response = client
.msearch(MsearchParts::None)
.body(msearch_vector)
.send()
.await
.map_err(|_| AnalyticsError::UnknownError)?;
let response_body = response
.change_context(OpenSearchError::ConnectionError)?
.json::<OpenMsearchOutput<Value>>()
.await
.map_err(|_| AnalyticsError::UnknownError)?;
.change_context(OpenSearchError::ResponseError)?;
Ok(response_body
.responses
@ -114,29 +48,30 @@ pub async fn msearch_results(
}
pub async fn search_results(
client: &OpenSearchClient,
req: GetSearchRequestWithIndex,
merchant_id: &String,
config: OpensearchConfig,
) -> CustomResult<GetSearchResponse, AnalyticsError> {
) -> CustomResult<GetSearchResponse, OpenSearchError> {
let search_req = req.search_req;
let client = get_opensearch_client(config.clone())
.await
.map_err(|_| AnalyticsError::UnknownError)?;
let mut query_builder =
OpenSearchQueryBuilder::new(OpenSearchQuery::Search(req.index), search_req.query);
let response = client
.search(SearchParts::Index(&[&search_index_to_opensearch_index(req.index.clone(),&config.indexes)]))
.from(search_req.offset)
.size(search_req.count)
.body(json!({"query": {"bool": {"filter": [{"multi_match": {"type": "phrase", "query": search_req.query, "lenient": true}},{"match_phrase": {"merchant_id": merchant_id}}]}}}))
.send()
.await
.map_err(|_| AnalyticsError::UnknownError)?;
query_builder
.add_filter_clause("merchant_id".to_string(), merchant_id.to_string())
.switch()?;
let response_body = response
query_builder
.set_offset_n_count(search_req.offset, search_req.count)
.switch()?;
let response_body = client
.execute(query_builder)
.await
.change_context(OpenSearchError::ConnectionError)?
.json::<OpensearchOutput<Value>>()
.await
.map_err(|_| AnalyticsError::UnknownError)?;
.change_context(OpenSearchError::ResponseError)?;
Ok(GetSearchResponse {
count: response_body.hits.total.value,