use std::prelude::v1::*;
use std::collections::BTreeMap;
use std::sync::Arc;
use parking_lot::Mutex;
use product_os_capabilities::What;
#[cfg(feature = "controller")]
use product_os_command_control::ProductOSController;
use product_os_router::{Body, Response, IntoResponse, StatusCode, Extension, Path};
use serde_json::Value;
use crate::CONTROLLER_LOCK_TIMEOUT;
#[cfg(feature = "controller")]
pub fn feature_responder<S>(router: &mut product_os_router::ProductOSRouter<S>, prefix: Option<String>)
where
S: Clone + Send + Sync + 'static,
{
let mut path = "/".to_string();
match prefix {
None => path.push_str("api"),
Some(p) => {
path.push_str(p.as_str());
}
}
path.push_str("/:semver");
path.push_str("/*path");
router.add_handler(path.as_str(), product_os_router::Method::GET, api_features_no_body);
router.add_handler(path.as_str(), product_os_router::Method::HEAD, api_features_no_body);
router.add_handler(path.as_str(), product_os_router::Method::TRACE, api_features_no_body);
router.add_handler(path.as_str(), product_os_router::Method::POST, api_features_with_body);
router.add_handler(path.as_str(), product_os_router::Method::PUT, api_features_with_body);
router.add_handler(path.as_str(), product_os_router::Method::PATCH, api_features_with_body);
router.add_handler(path.as_str(), product_os_router::Method::DELETE, api_features_with_body);
}
#[cfg(feature = "controller")]
fn error_response(status: StatusCode, message: &str) -> Response<Body> {
Response::builder()
.status(status)
.header("content-type", "application/json")
.body(Body::from(alloc::format!("{{\"error\":\"{}\"}}", message)))
.unwrap_or_else(|_| {
let mut resp = Response::new(Body::from("{}"));
*resp.status_mut() = status;
resp
})
.into_response()
}
#[cfg(feature = "controller")]
async fn dispatch_feature(
params: &BTreeMap<String, String>,
controller_mutex: Arc<Mutex<ProductOSController>>,
payload: Option<Value>,
) -> Response<Body> {
let path = match params.get("path") {
Some(p) => p,
None => return error_response(StatusCode::BAD_REQUEST, "Missing path parameter"),
};
let semver = match params.get("semver") {
Some(s) => s,
None => return error_response(StatusCode::BAD_REQUEST, "Missing semver parameter"),
};
tracing::info!("Feature path requested: {} on version: {}", path, semver);
match controller_mutex.try_lock_for(CONTROLLER_LOCK_TIMEOUT) {
None => error_response(StatusCode::GATEWAY_TIMEOUT, "Controller lock timeout"),
Some(controller_locked) => {
let me_features = controller_locked.get_registry().get_me().get_features();
match me_features.find(path.as_str()) {
None => {
tracing::error!("Not found: {}", path);
error_response(StatusCode::NOT_FOUND, "Feature not found")
}
Some((registry_feature, _)) => match ®istry_feature.feature {
Some(feature) => {
feature
.request(
&What::Characteristic(path.to_owned()),
&payload,
semver,
)
.await
}
None => match ®istry_feature.feature_mut {
Some(feature_mut) => {
match feature_mut.try_lock_for(CONTROLLER_LOCK_TIMEOUT) {
Some(mut feature) => {
feature
.request_mut(
&What::Characteristic(path.to_owned()),
&payload,
semver,
)
.await
}
None => error_response(
StatusCode::SERVICE_UNAVAILABLE,
"Feature lock timeout",
),
}
}
None => error_response(
StatusCode::SERVICE_UNAVAILABLE,
"Feature not available",
),
},
},
}
}
}
}
#[cfg(feature = "controller")]
async fn api_features_with_body(
Path(params): Path<BTreeMap<String, String>>,
Extension(controller_mutex): Extension<Arc<Mutex<ProductOSController>>>,
axum::Json(payload): axum::Json<Value>,
) -> Response<Body> {
dispatch_feature(¶ms, controller_mutex, Some(payload)).await
}
#[cfg(feature = "controller")]
async fn api_features_no_body(
Path(params): Path<BTreeMap<String, String>>,
Extension(controller_mutex): Extension<Arc<Mutex<ProductOSController>>>,
) -> Response<Body> {
dispatch_feature(¶ms, controller_mutex, None).await
}