use std::str::FromStr; use actix_web::{web, HttpResponse}; use log::debug; use serde::{Deserialize, Serialize}; use time::OffsetDateTime; use meilisearch_lib::index::{Settings, Unchecked}; use meilisearch_lib::MeiliSearch; use meilisearch_types::error::ResponseError; use crate::extractors::authentication::{policies::*, GuardedData}; mod api_key; mod dump; pub mod indexes; mod tasks; pub fn configure(cfg: &mut web::ServiceConfig) { cfg.service(web::scope("/tasks").configure(tasks::configure)) .service(web::resource("/health").route(web::get().to(get_health))) .service(web::scope("/keys").configure(api_key::configure)) .service(web::scope("/dumps").configure(dump::configure)) .service(web::resource("/stats").route(web::get().to(get_stats))) .service(web::resource("/version").route(web::get().to(get_version))) .service(web::scope("/indexes").configure(indexes::configure)); } /// A type that tries to match either a star (*) or /// any other thing that implements `FromStr`. #[derive(Debug)] pub enum StarOr { Star, Other(T), } impl FromStr for StarOr { type Err = T::Err; fn from_str(s: &str) -> Result { if s.trim() == "*" { Ok(StarOr::Star) } else { T::from_str(s).map(StarOr::Other) } } } /// Extracts the raw values from the `StarOr` types and /// return None if a `StarOr::Star` is encountered. pub fn fold_star_or(content: impl IntoIterator>) -> Option where O: FromIterator, { content .into_iter() .map(|value| match value { StarOr::Star => None, StarOr::Other(val) => Some(val), }) .collect() } const PAGINATION_DEFAULT_LIMIT: fn() -> usize = || 20; #[derive(Debug, Clone, Copy, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct Pagination { #[serde(default)] pub offset: usize, #[serde(default = "PAGINATION_DEFAULT_LIMIT")] pub limit: usize, } #[derive(Debug, Clone, Serialize)] pub struct PaginationView { pub results: Vec, pub offset: usize, pub limit: usize, pub total: usize, } impl Pagination { /// Given the full data to paginate, returns the selected section. pub fn auto_paginate_sized( self, content: impl IntoIterator + ExactSizeIterator, ) -> PaginationView where T: Serialize, { let total = content.len(); let content: Vec<_> = content .into_iter() .skip(self.offset) .take(self.limit) .collect(); self.format_with(total, content) } /// Given an iterator and the total number of elements, returns the selected section. pub fn auto_paginate_unsized( self, total: usize, content: impl IntoIterator, ) -> PaginationView where T: Serialize, { let content: Vec<_> = content .into_iter() .skip(self.offset) .take(self.limit) .collect(); self.format_with(total, content) } /// Given the data already paginated + the total number of elements, it stores /// everything in a [PaginationResult]. pub fn format_with(self, total: usize, results: Vec) -> PaginationView where T: Serialize, { PaginationView { results, offset: self.offset, limit: self.limit, total, } } } impl PaginationView { pub fn new(offset: usize, limit: usize, total: usize, results: Vec) -> Self { Self { offset, limit, results, total, } } } #[derive(Debug, Clone, Serialize, Deserialize)] #[allow(clippy::large_enum_variant)] #[serde(tag = "name")] pub enum UpdateType { ClearAll, Customs, DocumentsAddition { #[serde(skip_serializing_if = "Option::is_none")] number: Option, }, DocumentsPartial { #[serde(skip_serializing_if = "Option::is_none")] number: Option, }, DocumentsDeletion { #[serde(skip_serializing_if = "Option::is_none")] number: Option, }, Settings { settings: Settings, }, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ProcessedUpdateResult { pub update_id: u64, #[serde(rename = "type")] pub update_type: UpdateType, pub duration: f64, // in seconds #[serde(with = "time::serde::rfc3339")] pub enqueued_at: OffsetDateTime, #[serde(with = "time::serde::rfc3339")] pub processed_at: OffsetDateTime, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct FailedUpdateResult { pub update_id: u64, #[serde(rename = "type")] pub update_type: UpdateType, pub error: ResponseError, pub duration: f64, // in seconds #[serde(with = "time::serde::rfc3339")] pub enqueued_at: OffsetDateTime, #[serde(with = "time::serde::rfc3339")] pub processed_at: OffsetDateTime, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct EnqueuedUpdateResult { pub update_id: u64, #[serde(rename = "type")] pub update_type: UpdateType, #[serde(with = "time::serde::rfc3339")] pub enqueued_at: OffsetDateTime, #[serde( skip_serializing_if = "Option::is_none", with = "time::serde::rfc3339::option" )] pub started_processing_at: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase", tag = "status")] pub enum UpdateStatusResponse { Enqueued { #[serde(flatten)] content: EnqueuedUpdateResult, }, Processing { #[serde(flatten)] content: EnqueuedUpdateResult, }, Failed { #[serde(flatten)] content: FailedUpdateResult, }, Processed { #[serde(flatten)] content: ProcessedUpdateResult, }, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct IndexUpdateResponse { pub update_id: u64, } impl IndexUpdateResponse { pub fn with_id(update_id: u64) -> Self { Self { update_id } } } /// Always return a 200 with: /// ```json /// { /// "status": "Meilisearch is running" /// } /// ``` pub async fn running() -> HttpResponse { HttpResponse::Ok().json(serde_json::json!({ "status": "Meilisearch is running" })) } async fn get_stats( meilisearch: GuardedData, MeiliSearch>, ) -> Result { let search_rules = &meilisearch.filters().search_rules; let response = meilisearch.get_all_stats(search_rules).await?; debug!("returns: {:?}", response); Ok(HttpResponse::Ok().json(response)) } #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct VersionResponse { commit_sha: String, commit_date: String, pkg_version: String, } async fn get_version( _meilisearch: GuardedData, MeiliSearch>, ) -> HttpResponse { let commit_sha = option_env!("VERGEN_GIT_SHA").unwrap_or("unknown"); let commit_date = option_env!("VERGEN_GIT_COMMIT_TIMESTAMP").unwrap_or("unknown"); HttpResponse::Ok().json(VersionResponse { commit_sha: commit_sha.to_string(), commit_date: commit_date.to_string(), pkg_version: env!("CARGO_PKG_VERSION").to_string(), }) } #[derive(Serialize)] struct KeysResponse { private: Option, public: Option, } pub async fn get_health() -> Result { Ok(HttpResponse::Ok().json(serde_json::json!({ "status": "available" }))) }