bhtune_server/routes/opc/
mod.rs1use std::convert::Infallible;
14use std::future::Future;
15use std::time::Duration;
16
17use axum::extract::{Path, Query, State};
18use axum::response::sse::{Event, KeepAlive, Sse};
19use axum::routing::{delete, get, post};
20use axum::{Json, Router};
21use bhtune_driver::{
22 BrowseNodeKind, BrowsePageRequest, BrowseSource, Driver, DriverResult, NamespaceOrganization,
23 OpcDaDriver, OpcDaGatewayCompatibility, SearchEvent, SearchIndexControlAction,
24 SearchIndexRequest, SearchMatch, SearchMatchMode, SearchRequest, check_gateway_compatibility,
25 list_opcda_servers,
26};
27use bhtune_runtime::tune::sample_quality_from_driver;
28
29use crate::error::{ApiError, ErrorBody};
30use crate::state::AppState;
31
32const OPC_QUERY_TIMEOUT_SECS: u64 = 30;
41
42enum TimedDriverCall<T> {
49 Ready(T),
50 Incompatible { operation: &'static str },
51 Failed(ApiError),
52}
53
54async fn timed_driver_call<T>(
55 what: &str,
56 timeout: Duration,
57 fut: impl Future<Output = DriverResult<T>>,
58) -> TimedDriverCall<T> {
59 match tokio::time::timeout(timeout, fut).await {
60 Ok(Ok(value)) => TimedDriverCall::Ready(value),
61 Ok(Err(bhtune_driver::DriverError::IncompatibleGateway { operation })) => {
62 TimedDriverCall::Incompatible { operation }
63 }
64 Ok(Err(err)) => TimedDriverCall::Failed(ApiError::BadRequest(format!("{what}: {err}"))),
65 Err(_) => TimedDriverCall::Failed(ApiError::BadRequest(format!(
66 "{what}: no response within {}s",
67 timeout.as_secs()
68 ))),
69 }
70}
71
72async fn with_timeout<T>(
73 what: &str,
74 fut: impl Future<Output = DriverResult<T>>,
75) -> Result<T, ApiError> {
76 match timed_driver_call(what, Duration::from_secs(OPC_QUERY_TIMEOUT_SECS), fut).await {
77 TimedDriverCall::Ready(value) => Ok(value),
78 TimedDriverCall::Incompatible { operation } => Err(ApiError::BadRequest(format!(
79 "{what}: {}",
80 bhtune_driver::DriverError::IncompatibleGateway { operation }
81 ))),
82 TimedDriverCall::Failed(error) => Err(error),
83 }
84}
85
86mod dto;
87mod handlers;
88mod helpers;
89mod validation;
90
91pub use dto::*;
92pub(crate) use handlers::{
93 __path_browse, __path_capabilities, __path_close_browse_session, __path_control_search_index,
94 __path_delete_search_index, __path_read, __path_refresh_search_index, __path_search,
95 __path_search_index, __path_search_index_status, __path_servers,
96 __path_set_search_index_auto_refresh,
97};
98pub(crate) use handlers::{
99 browse, capabilities, close_browse_session, control_search_index, delete_search_index, read,
100 refresh_search_index, search, search_index, search_index_status, servers,
101 set_search_index_auto_refresh,
102};
103
104pub fn router() -> Router<AppState> {
105 Router::new()
106 .route("/api/opc/servers", get(servers))
107 .route("/api/opc/capabilities", get(capabilities))
108 .route("/api/opc/browse", get(browse))
109 .route(
110 "/api/opc/browse/sessions/{session_id}",
111 delete(close_browse_session),
112 )
113 .route("/api/opc/search", get(search))
114 .route("/api/opc/search-index/status", get(search_index_status))
115 .route("/api/opc/search-index/search", get(search_index))
116 .route("/api/opc/search-index/refresh", post(refresh_search_index))
117 .route(
118 "/api/opc/search-index/auto-refresh",
119 post(set_search_index_auto_refresh),
120 )
121 .route("/api/opc/search-index/control", post(control_search_index))
122 .route("/api/opc/search-index", delete(delete_search_index))
123 .route("/api/opc/read", get(read))
124}