feat(routing): Add API key auth for decision engine endpoints (#8640)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
spritianeja03
2025-07-21 17:09:14 +05:30
committed by GitHub
parent 82eb3ca7b4
commit f4da057ca8
8 changed files with 191 additions and 3 deletions

View File

@ -178,3 +178,27 @@ impl ApiEventMetric for RuleMigrationError {
Some(ApiEventsType::Routing) Some(ApiEventsType::Routing)
} }
} }
impl ApiEventMetric for crate::open_router::DecideGatewayResponse {
fn get_api_event_type(&self) -> Option<ApiEventsType> {
Some(ApiEventsType::Routing)
}
}
impl ApiEventMetric for crate::open_router::OpenRouterDecideGatewayRequest {
fn get_api_event_type(&self) -> Option<ApiEventsType> {
Some(ApiEventsType::Routing)
}
}
impl ApiEventMetric for crate::open_router::UpdateScorePayload {
fn get_api_event_type(&self) -> Option<ApiEventsType> {
Some(ApiEventsType::Routing)
}
}
impl ApiEventMetric for crate::open_router::UpdateScoreResponse {
fn get_api_event_type(&self) -> Option<ApiEventsType> {
Some(ApiEventsType::Routing)
}
}

View File

@ -26,6 +26,41 @@ pub struct OpenRouterDecideGatewayRequest {
pub elimination_enabled: Option<bool>, pub elimination_enabled: Option<bool>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DecideGatewayResponse {
pub decided_gateway: Option<String>,
pub gateway_priority_map: Option<serde_json::Value>,
pub filter_wise_gateways: Option<serde_json::Value>,
pub priority_logic_tag: Option<String>,
pub routing_approach: Option<String>,
pub gateway_before_evaluation: Option<String>,
pub priority_logic_output: Option<PriorityLogicOutput>,
pub reset_approach: Option<String>,
pub routing_dimension: Option<String>,
pub routing_dimension_level: Option<String>,
pub is_scheduled_outage: Option<bool>,
pub is_dynamic_mga_enabled: Option<bool>,
pub gateway_mga_id_map: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PriorityLogicOutput {
pub is_enforcement: Option<bool>,
pub gws: Option<Vec<String>>,
pub priority_logic_tag: Option<String>,
pub gateway_reference_ids: Option<HashMap<String, String>>,
pub primary_logic: Option<PriorityLogicData>,
pub fallback_logic: Option<PriorityLogicData>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PriorityLogicData {
pub name: Option<String>,
pub status: Option<String>,
pub failure_reason: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum RankingAlgorithm { pub enum RankingAlgorithm {

View File

@ -1232,7 +1232,7 @@ where
String(String), String(String),
} }
#[derive(Debug)] #[derive(Debug, Serialize)]
pub struct RoutingEventsResponse<Res> pub struct RoutingEventsResponse<Res>
where where
Res: Serialize + serde::de::DeserializeOwned + Clone, Res: Serialize + serde::de::DeserializeOwned + Clone,

View File

@ -5,7 +5,12 @@ use std::collections::HashSet;
#[cfg(all(feature = "v1", feature = "dynamic_routing"))] #[cfg(all(feature = "v1", feature = "dynamic_routing"))]
use api_models::routing::DynamicRoutingAlgoAccessor; use api_models::routing::DynamicRoutingAlgoAccessor;
use api_models::{ use api_models::{
enums, mandates as mandates_api, routing, enums, mandates as mandates_api,
open_router::{
DecideGatewayResponse, OpenRouterDecideGatewayRequest, UpdateScorePayload,
UpdateScoreResponse,
},
routing,
routing::{ routing::{
self as routing_types, RoutingRetrieveQuery, RuleMigrationError, RuleMigrationResponse, self as routing_types, RoutingRetrieveQuery, RuleMigrationError, RuleMigrationResponse,
}, },
@ -13,6 +18,7 @@ use api_models::{
use async_trait::async_trait; use async_trait::async_trait;
#[cfg(all(feature = "v1", feature = "dynamic_routing"))] #[cfg(all(feature = "v1", feature = "dynamic_routing"))]
use common_utils::ext_traits::AsyncExt; use common_utils::ext_traits::AsyncExt;
use common_utils::request::Method;
use diesel_models::routing_algorithm::RoutingAlgorithm; use diesel_models::routing_algorithm::RoutingAlgorithm;
use error_stack::ResultExt; use error_stack::ResultExt;
#[cfg(all(feature = "v1", feature = "dynamic_routing"))] #[cfg(all(feature = "v1", feature = "dynamic_routing"))]
@ -2621,3 +2627,59 @@ pub async fn migrate_rules_for_profile(
errors: error_list, errors: error_list,
}) })
} }
pub async fn decide_gateway_open_router(
state: SessionState,
req_body: OpenRouterDecideGatewayRequest,
) -> RouterResponse<DecideGatewayResponse> {
let response = if state.conf.open_router.dynamic_routing_enabled {
SRApiClient::send_decision_engine_request(
&state,
Method::Post,
"decide-gateway",
Some(req_body),
None,
None,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)?
.response
.ok_or(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to perform decide gateway call with open router")?
} else {
Err(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Dynamic routing is not enabled")?
};
Ok(hyperswitch_domain_models::api::ApplicationResponse::Json(
response,
))
}
pub async fn update_gateway_score_open_router(
state: SessionState,
req_body: UpdateScorePayload,
) -> RouterResponse<UpdateScoreResponse> {
let response = if state.conf.open_router.dynamic_routing_enabled {
SRApiClient::send_decision_engine_request(
&state,
Method::Post,
"update-gateway-score",
Some(req_body),
None,
None,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)?
.response
.ok_or(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to perform update gateway score call with open router")?
} else {
Err(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Dynamic routing is not enabled")?
};
Ok(hyperswitch_domain_models::api::ApplicationResponse::Json(
response,
))
}

View File

@ -973,6 +973,19 @@ impl Routing {
})), })),
); );
#[cfg(feature = "dynamic_routing")]
{
route = route
.service(
web::resource("/evaluate")
.route(web::post().to(routing::call_decide_gateway_open_router)),
)
.service(
web::resource("/feedback")
.route(web::post().to(routing::call_update_gateway_score_open_router)),
)
}
#[cfg(feature = "payouts")] #[cfg(feature = "payouts")]
{ {
route = route route = route

View File

@ -83,7 +83,9 @@ impl From<Flow> for ApiIdentifier {
| Flow::DecisionManagerUpsertConfig | Flow::DecisionManagerUpsertConfig
| Flow::RoutingEvaluateRule | Flow::RoutingEvaluateRule
| Flow::DecisionEngineRuleMigration | Flow::DecisionEngineRuleMigration
| Flow::VolumeSplitOnRoutingType => Self::Routing, | Flow::VolumeSplitOnRoutingType
| Flow::DecisionEngineDecideGatewayCall
| Flow::DecisionEngineGatewayFeedbackCall => Self::Routing,
Flow::RetrieveForexFlow => Self::Forex, Flow::RetrieveForexFlow => Self::Forex,

View File

@ -1672,3 +1672,51 @@ async fn get_merchant_account(
.to_not_found_response(ApiErrorResponse::MerchantAccountNotFound)?; .to_not_found_response(ApiErrorResponse::MerchantAccountNotFound)?;
Ok((key_store, merchant_account)) Ok((key_store, merchant_account))
} }
#[cfg(all(feature = "olap", feature = "v1", feature = "dynamic_routing"))]
#[instrument(skip_all)]
pub async fn call_decide_gateway_open_router(
state: web::Data<AppState>,
req: HttpRequest,
payload: web::Json<api_models::open_router::OpenRouterDecideGatewayRequest>,
) -> impl Responder {
let flow = Flow::DecisionEngineDecideGatewayCall;
Box::pin(oss_api::server_wrap(
flow,
state,
&req,
payload.clone(),
|state, _auth, payload, _| routing::decide_gateway_open_router(state.clone(), payload),
&auth::ApiKeyAuth {
is_connected_allowed: false,
is_platform_allowed: false,
},
api_locking::LockAction::NotApplicable,
))
.await
}
#[cfg(all(feature = "olap", feature = "v1", feature = "dynamic_routing"))]
#[instrument(skip_all)]
pub async fn call_update_gateway_score_open_router(
state: web::Data<AppState>,
req: HttpRequest,
payload: web::Json<api_models::open_router::UpdateScorePayload>,
) -> impl Responder {
let flow = Flow::DecisionEngineGatewayFeedbackCall;
Box::pin(oss_api::server_wrap(
flow,
state,
&req,
payload.clone(),
|state, _auth, payload, _| {
routing::update_gateway_score_open_router(state.clone(), payload)
},
&auth::ApiKeyAuth {
is_connected_allowed: false,
is_platform_allowed: false,
},
api_locking::LockAction::NotApplicable,
))
.await
}

View File

@ -614,6 +614,10 @@ pub enum Flow {
ThreeDsDecisionRuleExecute, ThreeDsDecisionRuleExecute,
/// Incoming Network Token Webhook Receive /// Incoming Network Token Webhook Receive
IncomingNetworkTokenWebhookReceive, IncomingNetworkTokenWebhookReceive,
/// Decision Engine Decide Gateway Call
DecisionEngineDecideGatewayCall,
/// Decision Engine Gateway Feedback Call
DecisionEngineGatewayFeedbackCall,
} }
/// Trait for providing generic behaviour to flow metric /// Trait for providing generic behaviour to flow metric