diff --git a/meilisearch/src/routes/features.rs b/meilisearch/src/routes/features.rs new file mode 100644 index 000000000..08a0f5ba1 --- /dev/null +++ b/meilisearch/src/routes/features.rs @@ -0,0 +1,102 @@ +use actix_web::web::{self, Data}; +use actix_web::{HttpRequest, HttpResponse}; +use deserr::actix_web::AwebJson; +use deserr::Deserr; +use index_scheduler::IndexScheduler; +use log::debug; +use meilisearch_types::deserr::DeserrJsonError; +use meilisearch_types::error::ResponseError; +use meilisearch_types::keys::actions; +use serde_json::json; + +use crate::analytics::Analytics; +use crate::extractors::authentication::policies::ActionPolicy; +use crate::extractors::authentication::GuardedData; +use crate::extractors::sequential_extractor::SeqHandler; + +pub fn configure(cfg: &mut web::ServiceConfig) { + cfg.service( + web::resource("") + .route(web::get().to(SeqHandler(get_features))) + .route(web::patch().to(SeqHandler(patch_features))) + .route(web::delete().to(SeqHandler(delete_features))) + .route(web::post().to(SeqHandler(post_features))), + ); +} + +async fn get_features( + index_scheduler: GuardedData< + ActionPolicy<{ actions::EXPERIMENTAL_FEATURES_GET }>, + Data, + >, + req: HttpRequest, + analytics: Data, +) -> Result { + let features = index_scheduler.features()?; + + analytics.publish("Experimental features Seen".to_string(), json!(null), Some(&req)); + debug!("returns: {:?}", features.runtime_features()); + Ok(HttpResponse::Ok().json(features.runtime_features())) +} + +#[derive(Debug, Deserr)] +#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)] +pub struct RuntimeTogglableFeatures { + #[deserr(default)] + pub score_details: Option, + #[deserr(default)] + pub vector_store: Option, +} + +async fn patch_features( + index_scheduler: GuardedData< + ActionPolicy<{ actions::EXPERIMENTAL_FEATURES_UPDATE }>, + Data, + >, + new_features: AwebJson, + analytics: Data, +) -> Result { + let features = index_scheduler.features()?; + + let old_features = features.runtime_features(); + + let new_features = meilisearch_types::features::RuntimeTogglableFeatures { + score_details: new_features.0.score_details.unwrap_or(old_features.score_details), + vector_store: new_features.0.vector_store.unwrap_or(old_features.vector_store), + }; + + analytics.publish("Experimental features PATCH".to_string(), json!(new_features), None); + index_scheduler.put_runtime_features(new_features)?; + Ok(HttpResponse::Ok().json(new_features)) +} + +async fn post_features( + index_scheduler: GuardedData< + ActionPolicy<{ actions::EXPERIMENTAL_FEATURES_UPDATE }>, + Data, + >, + new_features: AwebJson, + analytics: Data, +) -> Result { + let new_features = meilisearch_types::features::RuntimeTogglableFeatures { + score_details: new_features.0.score_details.unwrap_or(false), + vector_store: new_features.0.vector_store.unwrap_or(false), + }; + + analytics.publish("Experimental features POST".to_string(), json!(new_features), None); + index_scheduler.put_runtime_features(new_features)?; + Ok(HttpResponse::Ok().json(new_features)) +} + +async fn delete_features( + index_scheduler: GuardedData< + ActionPolicy<{ actions::EXPERIMENTAL_FEATURES_UPDATE }>, + Data, + >, + analytics: Data, +) -> Result { + let deleted_features = Default::default(); + analytics.publish("Experimental features DELETE".to_string(), json!(null), None); + index_scheduler.put_runtime_features(deleted_features)?; + Ok(HttpResponse::Ok().json(deleted_features)) +} diff --git a/meilisearch/src/routes/mod.rs b/meilisearch/src/routes/mod.rs index 57d670b5f..9c5d29119 100644 --- a/meilisearch/src/routes/mod.rs +++ b/meilisearch/src/routes/mod.rs @@ -20,6 +20,7 @@ const PAGINATION_DEFAULT_LIMIT: usize = 20; mod api_key; mod dump; +pub mod features; pub mod indexes; mod metrics; mod multi_search; @@ -35,7 +36,8 @@ pub fn configure(cfg: &mut web::ServiceConfig, enable_metrics: bool) { .service(web::resource("/version").route(web::get().to(get_version))) .service(web::scope("/indexes").configure(indexes::configure)) .service(web::scope("/multi-search").configure(multi_search::configure)) - .service(web::scope("/swap-indexes").configure(swap_indexes::configure)); + .service(web::scope("/swap-indexes").configure(swap_indexes::configure)) + .service(web::scope("/experimental-features").configure(features::configure)); if enable_metrics { cfg.service(web::scope("/metrics").configure(metrics::configure));