use std::sync::Arc;
use axum::{extract::State, response::IntoResponse, routing::post};
use endhost_api_models::{SegmentsDiscovery, SegmentsError, UnderlayDiscovery};
use endhost_api_protobuf::v1::{
ListSegmentsRequest, ListSegmentsResponse, ListUnderlaysRequest, ListUnderlaysResponse,
};
use scion_sdk_axum_connect_rpc::extractor::ConnectRpc;
use sciparse::identifier::isd_asn::IsdAsn;
pub const ENDHOST_API_V1: &str = "scion.endhost.v1";
pub const UNDERLAY_SERVICE: &str = "UnderlayService";
pub const SEGMENTS_SERVICE: &str = "SegmentsService";
#[deprecated(note = "Use SEGMENTS_SERVICE instead")]
pub const PATH_SERVICE: &str = "PathService";
pub const LIST_UNDERLAYS: &str = "/ListUnderlays";
pub const LIST_SEGMENTS: &str = "/ListSegments";
#[deprecated(note = "Use LIST_SEGMENTS instead")]
pub const LIST_PATHS: &str = "/ListPaths";
pub fn nest_endhost_api(
base_router: axum::Router,
underlay_service: Arc<dyn UnderlayDiscovery>,
path_service: Arc<dyn SegmentsDiscovery>,
) -> axum::Router {
let underlay_router = axum::Router::new()
.route(LIST_UNDERLAYS, post(list_underlays_handler))
.with_state(underlay_service);
let base_router = base_router.nest(
&service_path(ENDHOST_API_V1, UNDERLAY_SERVICE),
underlay_router,
);
#[allow(deprecated)]
let path_router = axum::Router::new()
.route(LIST_PATHS, post(list_segments_handler))
.with_state(path_service.clone());
#[allow(deprecated)]
let base_router = base_router.nest(&service_path(ENDHOST_API_V1, PATH_SERVICE), path_router);
let segment_router = axum::Router::new()
.route(LIST_SEGMENTS, post(list_segments_handler))
.with_state(path_service);
base_router.nest(
&service_path(ENDHOST_API_V1, SEGMENTS_SERVICE),
segment_router,
)
}
async fn list_underlays_handler(
State(underlay_service): State<Arc<dyn UnderlayDiscovery>>,
ConnectRpc(request): ConnectRpc<ListUnderlaysRequest>,
) -> ConnectRpc<ListUnderlaysResponse> {
tracing::info!(request = ?request, "list_underlays request");
let response: ListUnderlaysResponse = underlay_service
.list_underlays(request.isd_as.map(IsdAsn::from).unwrap_or(IsdAsn::WILDCARD))
.into();
tracing::info!(response = ?response, "list_underlays response");
ConnectRpc(response)
}
async fn list_segments_handler(
State(path_service): State<Arc<dyn SegmentsDiscovery>>,
ConnectRpc(request): ConnectRpc<ListSegmentsRequest>,
) -> Result<ConnectRpc<ListSegmentsResponse>, axum::response::Response> {
let (src, dst) = (
IsdAsn::from(request.src_isd_as),
IsdAsn::from(request.dst_isd_as),
);
tracing::debug!(?src, ?dst, page_size=?request.page_size, page_token=?request.page_token, "list_segments request");
match path_service
.list_segments(src, dst, request.page_size, request.page_token)
.await
{
Ok(segments) => {
let response: ListSegmentsResponse = segments.into();
tracing::info!(
num_core = response.core_segments.len(),
num_up = response.up_segments.len(),
num_down = response.down_segments.len(),
"list_segments response"
);
Ok(ConnectRpc(response))
}
Err(SegmentsError::InvalidArgument(msg)) => {
tracing::error!(src = %src, dst = %dst, error = %msg, "list_segments invalid argument");
Err((axum::http::StatusCode::BAD_REQUEST, msg).into_response())
}
Err(SegmentsError::InternalError(msg)) => {
tracing::error!(src = %src, dst = %dst, error = %msg, "list_segments internal error");
Err((axum::http::StatusCode::INTERNAL_SERVER_ERROR, msg).into_response())
}
}
}
fn service_path(api: &str, service: &str) -> String {
format!("/{api}.{service}")
}