mirror of
https://github.com/meilisearch/MeiliSearch
synced 2025-07-04 20:37:15 +02:00
Merge b53f7eb79f
into a88146d59e
This commit is contained in:
commit
068d251ac5
3 changed files with 308 additions and 0 deletions
|
@ -73,6 +73,7 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
|
|||
.route(web::delete().to(SeqHandler(delete_index))),
|
||||
)
|
||||
.service(web::resource("/stats").route(web::get().to(SeqHandler(get_index_stats))))
|
||||
.service(web::resource("/fields").route(web::get().to(SeqHandler(get_index_fields))))
|
||||
.service(web::scope("/documents").configure(documents::configure))
|
||||
.service(web::scope("/search").configure(search::configure))
|
||||
.service(web::scope("/facet-search").configure(facet_search::configure))
|
||||
|
@ -580,3 +581,47 @@ pub async fn get_index_stats(
|
|||
debug!(returns = ?stats, "Get index stats");
|
||||
Ok(HttpResponse::Ok().json(stats))
|
||||
}
|
||||
|
||||
/// Get fields of index
|
||||
///
|
||||
/// Get all field names in the index (including nested fields).
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/{indexUid}/fields",
|
||||
tag = "Indexes",
|
||||
security(("Bearer" = ["indexes.get", "indexes.*", "*"])),
|
||||
params(("indexUid", example = "movies", description = "Index Unique Identifier", nullable = false)),
|
||||
responses(
|
||||
(status = OK, description = "The fields of the index", body = Vec<String>, content_type = "application/json", example = json!(
|
||||
["id", "title", "user.name", "user.email", "metadata.category"]
|
||||
)),
|
||||
(status = 404, description = "Index not found", body = ResponseError, content_type = "application/json", example = json!(
|
||||
{
|
||||
"message": "Index `movies` not found.",
|
||||
"code": "index_not_found",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#index_not_found"
|
||||
}
|
||||
)),
|
||||
(status = 401, description = "The authorization header is missing", body = ResponseError, content_type = "application/json", example = json!(
|
||||
{
|
||||
"message": "The Authorization header is missing. It must use the bearer authorization method.",
|
||||
"code": "missing_authorization_header",
|
||||
"type": "auth",
|
||||
"link": "https://docs.meilisearch.com/errors#missing_authorization_header"
|
||||
}
|
||||
)),
|
||||
)
|
||||
)]
|
||||
pub async fn get_index_fields(
|
||||
index_scheduler: GuardedData<ActionPolicy<{ actions::INDEXES_GET }>, Data<IndexScheduler>>,
|
||||
index_uid: web::Path<String>,
|
||||
) -> Result<HttpResponse, ResponseError> {
|
||||
let index_uid = IndexUid::try_from(index_uid.into_inner())?;
|
||||
let index = index_scheduler.index(&index_uid)?;
|
||||
let rtxn = index.read_txn()?;
|
||||
let fields: Vec<String> = index.fields_ids_map(&rtxn)?.names().map(|s| s.to_string()).collect();
|
||||
|
||||
debug!(returns = ?fields, "Get index fields");
|
||||
Ok(HttpResponse::Ok().json(fields))
|
||||
}
|
||||
|
|
|
@ -467,6 +467,11 @@ impl<State> Index<'_, State> {
|
|||
self.service.get(url).await
|
||||
}
|
||||
|
||||
pub async fn fields(&self) -> (Value, StatusCode) {
|
||||
let url = format!("/indexes/{}/fields", urlencode(self.uid.as_ref()));
|
||||
self.service.get(url).await
|
||||
}
|
||||
|
||||
/// Performs both GET and POST search queries
|
||||
pub async fn search(
|
||||
&self,
|
||||
|
|
|
@ -60,3 +60,261 @@ async fn error_get_stats_unexisting_index() {
|
|||
assert_eq!(response, expected_response);
|
||||
assert_eq!(code, 404);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn fields() {
|
||||
let server = Server::new_shared();
|
||||
let index = server.unique_index();
|
||||
let (task, code) = index.create(None).await;
|
||||
|
||||
assert_eq!(code, 202);
|
||||
server.wait_task(task.uid()).await.succeeded();
|
||||
|
||||
// Test empty index
|
||||
let (response, code) = index.fields().await;
|
||||
assert_eq!(code, 200);
|
||||
assert!(response.as_array().unwrap().is_empty());
|
||||
|
||||
// Test with documents containing nested fields
|
||||
let documents = json!([
|
||||
{
|
||||
"id": 1,
|
||||
"name": "John",
|
||||
"user": {
|
||||
"email": "john@example.com",
|
||||
"profile": {
|
||||
"age": 30,
|
||||
"location": "Paris"
|
||||
}
|
||||
},
|
||||
"tags": ["developer", "rust"]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "Article",
|
||||
"metadata": {
|
||||
"category": "tech",
|
||||
"author": {
|
||||
"name": "Jane",
|
||||
"id": 123
|
||||
}
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
let (response, code) = index.add_documents(documents, None).await;
|
||||
assert_eq!(code, 202);
|
||||
server.wait_task(response.uid()).await.succeeded();
|
||||
|
||||
// Test fields including nested fields
|
||||
let (response, code) = index.fields().await;
|
||||
assert_eq!(code, 200);
|
||||
|
||||
let fields = response.as_array().unwrap();
|
||||
let field_names: Vec<&str> = fields.iter().map(|f| f.as_str().unwrap()).collect();
|
||||
|
||||
// Check that all expected fields are present (including nested fields)
|
||||
assert!(field_names.contains(&"id"));
|
||||
assert!(field_names.contains(&"name"));
|
||||
assert!(field_names.contains(&"title"));
|
||||
assert!(field_names.contains(&"user.email"));
|
||||
assert!(field_names.contains(&"user.profile.age"));
|
||||
assert!(field_names.contains(&"user.profile.location"));
|
||||
assert!(field_names.contains(&"tags"));
|
||||
assert!(field_names.contains(&"metadata.category"));
|
||||
assert!(field_names.contains(&"metadata.author.name"));
|
||||
assert!(field_names.contains(&"metadata.author.id"));
|
||||
|
||||
// Verify the response is a simple array of strings
|
||||
for field in fields {
|
||||
assert!(field.is_string());
|
||||
}
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn fields_nested_complex() {
|
||||
let server = Server::new_shared();
|
||||
let index = server.unique_index();
|
||||
let (task, code) = index.create(Some("id")).await;
|
||||
|
||||
assert_eq!(code, 202);
|
||||
server.wait_task(task.uid()).await.succeeded();
|
||||
|
||||
// Test with complex deeply nested structures
|
||||
let documents = json!([
|
||||
{
|
||||
"id": 1,
|
||||
"product": {
|
||||
"name": "Laptop",
|
||||
"specifications": {
|
||||
"hardware": {
|
||||
"cpu": {
|
||||
"brand": "Intel",
|
||||
"model": "i7-12700K",
|
||||
"cores": {
|
||||
"physical": 12,
|
||||
"logical": 20
|
||||
}
|
||||
},
|
||||
"memory": {
|
||||
"ram": 16,
|
||||
"type": "DDR4"
|
||||
},
|
||||
"storage": {
|
||||
"primary": {
|
||||
"type": "SSD",
|
||||
"capacity": 512
|
||||
},
|
||||
"secondary": {
|
||||
"type": "HDD",
|
||||
"capacity": 1000
|
||||
}
|
||||
}
|
||||
},
|
||||
"software": {
|
||||
"os": "Windows 11",
|
||||
"applications": ["Chrome", "VS Code", "Docker"]
|
||||
}
|
||||
},
|
||||
"pricing": {
|
||||
"base": 1299.99,
|
||||
"currency": "USD",
|
||||
"discounts": {
|
||||
"student": 0.1,
|
||||
"bulk": 0.05
|
||||
}
|
||||
}
|
||||
},
|
||||
"customer": {
|
||||
"info": {
|
||||
"name": "Alice",
|
||||
"contact": {
|
||||
"email": "alice@example.com",
|
||||
"phone": {
|
||||
"country": "+1",
|
||||
"number": "555-1234"
|
||||
}
|
||||
}
|
||||
},
|
||||
"preferences": {
|
||||
"notifications": {
|
||||
"email": true,
|
||||
"sms": false,
|
||||
"push": {
|
||||
"enabled": true,
|
||||
"frequency": "daily"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"order": {
|
||||
"items": [
|
||||
{
|
||||
"product_id": "ABC123",
|
||||
"quantity": 2
|
||||
},
|
||||
{
|
||||
"product_id": "DEF456",
|
||||
"quantity": 1
|
||||
}
|
||||
],
|
||||
"shipping": {
|
||||
"address": {
|
||||
"street": "123 Main St",
|
||||
"city": "New York",
|
||||
"state": "NY",
|
||||
"zip": "10001",
|
||||
"country": "USA"
|
||||
},
|
||||
"method": "express",
|
||||
"tracking": {
|
||||
"number": "1Z999AA1234567890",
|
||||
"carrier": "UPS"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
let (response, code) = index.add_documents(documents, None).await;
|
||||
assert_eq!(code, 202);
|
||||
server.wait_task(response.uid()).await.succeeded();
|
||||
|
||||
// Test fields with complex nested structures
|
||||
let (response, code) = index.fields().await;
|
||||
assert_eq!(code, 200);
|
||||
|
||||
let fields = response.as_array().unwrap();
|
||||
let field_names: Vec<&str> = fields.iter().map(|f| f.as_str().unwrap()).collect();
|
||||
|
||||
// Test deeply nested fields from the first document
|
||||
assert!(field_names.contains(&"product.name"));
|
||||
assert!(field_names.contains(&"product.specifications.hardware.cpu.brand"));
|
||||
assert!(field_names.contains(&"product.specifications.hardware.cpu.model"));
|
||||
assert!(field_names.contains(&"product.specifications.hardware.cpu.cores.physical"));
|
||||
assert!(field_names.contains(&"product.specifications.hardware.cpu.cores.logical"));
|
||||
assert!(field_names.contains(&"product.specifications.hardware.memory.ram"));
|
||||
assert!(field_names.contains(&"product.specifications.hardware.memory.type"));
|
||||
assert!(field_names.contains(&"product.specifications.hardware.storage.primary.type"));
|
||||
assert!(field_names.contains(&"product.specifications.hardware.storage.primary.capacity"));
|
||||
assert!(field_names.contains(&"product.specifications.hardware.storage.secondary.type"));
|
||||
assert!(field_names.contains(&"product.specifications.hardware.storage.secondary.capacity"));
|
||||
assert!(field_names.contains(&"product.specifications.software.os"));
|
||||
assert!(field_names.contains(&"product.specifications.software.applications"));
|
||||
assert!(field_names.contains(&"product.pricing.base"));
|
||||
assert!(field_names.contains(&"product.pricing.currency"));
|
||||
assert!(field_names.contains(&"product.pricing.discounts.student"));
|
||||
assert!(field_names.contains(&"product.pricing.discounts.bulk"));
|
||||
|
||||
// Test deeply nested fields from the second document
|
||||
assert!(field_names.contains(&"order.items"));
|
||||
assert!(field_names.contains(&"order.shipping.address.street"));
|
||||
assert!(field_names.contains(&"order.shipping.address.city"));
|
||||
assert!(field_names.contains(&"order.shipping.address.state"));
|
||||
assert!(field_names.contains(&"order.shipping.address.zip"));
|
||||
assert!(field_names.contains(&"order.shipping.address.country"));
|
||||
assert!(field_names.contains(&"order.shipping.method"));
|
||||
assert!(field_names.contains(&"order.shipping.tracking.number"));
|
||||
assert!(field_names.contains(&"order.shipping.tracking.carrier"));
|
||||
|
||||
// Test customer fields
|
||||
assert!(field_names.contains(&"customer.info.name"));
|
||||
assert!(field_names.contains(&"customer.info.contact.email"));
|
||||
assert!(field_names.contains(&"customer.info.contact.phone.country"));
|
||||
assert!(field_names.contains(&"customer.info.contact.phone.number"));
|
||||
assert!(field_names.contains(&"customer.preferences.notifications.email"));
|
||||
assert!(field_names.contains(&"customer.preferences.notifications.sms"));
|
||||
assert!(field_names.contains(&"customer.preferences.notifications.push.enabled"));
|
||||
assert!(field_names.contains(&"customer.preferences.notifications.push.frequency"));
|
||||
|
||||
// Verify all fields are strings and no duplicates
|
||||
let unique_fields: std::collections::HashSet<&str> = field_names.iter().cloned().collect();
|
||||
assert_eq!(unique_fields.len(), field_names.len(), "No duplicate fields should exist");
|
||||
|
||||
// Verify the response is a simple array of strings
|
||||
for field in fields {
|
||||
assert!(field.is_string());
|
||||
}
|
||||
|
||||
// Test that we have a reasonable number of fields (should be more than just top-level)
|
||||
assert!(field_names.len() > 10, "Should have more than 10 fields including nested ones");
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn error_get_fields_unexisting_index() {
|
||||
let index = shared_does_not_exists_index().await;
|
||||
let (response, code) = index.fields().await;
|
||||
|
||||
let expected_response = json!({
|
||||
"message": format!("Index `{}` not found.", index.uid),
|
||||
"code": "index_not_found",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#index_not_found"
|
||||
});
|
||||
|
||||
assert_eq!(response, expected_response);
|
||||
assert_eq!(code, 404);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue