use std::collections::HashSet; use std::convert::{TryFrom, TryInto}; use actix_web::{get, post, web, HttpResponse}; use serde_json::Value; use serde::Deserialize; use crate::error::ResponseError; use crate::helpers::Authentication; use crate::index::{SearchQuery, DEFAULT_SEARCH_LIMIT}; use crate::routes::IndexParam; use crate::Data; pub fn services(cfg: &mut web::ServiceConfig) { cfg.service(search_with_post).service(search_with_url_query); } #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct SearchQueryGet { q: Option, offset: Option, limit: Option, attributes_to_retrieve: Option, attributes_to_crop: Option, crop_length: Option, attributes_to_highlight: Option, filter: Option, matches: Option, facet_distributions: Option, } impl TryFrom for SearchQuery { type Error = anyhow::Error; fn try_from(other: SearchQueryGet) -> anyhow::Result { let attributes_to_retrieve = other .attributes_to_retrieve .map(|attrs| attrs.split(',').map(String::from).collect::>()); let attributes_to_crop = other .attributes_to_crop .map(|attrs| attrs.split(',').map(String::from).collect::>()); let attributes_to_highlight = other .attributes_to_highlight .map(|attrs| attrs.split(',').map(String::from).collect::>()); let facet_distributions = other .facet_distributions .map(|attrs| attrs.split(',').map(String::from).collect::>()); let filter = match other.filter { Some(f) => { match serde_json::from_str(&f) { Ok(v) => Some(v), _ => Some(Value::String(f)), } }, None => None, }; Ok(Self { q: other.q, offset: other.offset, limit: other.limit.unwrap_or(DEFAULT_SEARCH_LIMIT), attributes_to_retrieve, attributes_to_crop, crop_length: other.crop_length, attributes_to_highlight, filter, matches: other.matches, facet_distributions, }) } } #[get("/indexes/{index_uid}/search", wrap = "Authentication::Public")] async fn search_with_url_query( data: web::Data, path: web::Path, params: web::Query, ) -> Result { let query: SearchQuery = match params.into_inner().try_into() { Ok(q) => q, Err(e) => { return Ok( HttpResponse::BadRequest().json(serde_json::json!({ "error": e.to_string() })) ) } }; let search_result = data.search(path.into_inner().index_uid, query).await; match search_result { Ok(docs) => Ok(HttpResponse::Ok().json(docs)), Err(e) => { Ok(HttpResponse::BadRequest().json(serde_json::json!({ "error": e.to_string() }))) } } } #[post("/indexes/{index_uid}/search", wrap = "Authentication::Public")] async fn search_with_post( data: web::Data, path: web::Path, params: web::Json, ) -> Result { let search_result = data .search(path.into_inner().index_uid, params.into_inner()) .await; match search_result { Ok(docs) => Ok(HttpResponse::Ok().json(docs)), Err(e) => { Ok(HttpResponse::BadRequest().json(serde_json::json!({ "error": e.to_string() }))) } } }