This commit is contained in:
Mubelotix
2025-07-17 11:11:33 +02:00
parent cc9fd82f79
commit 3191316cf3
9 changed files with 145 additions and 199 deletions

View File

@ -138,7 +138,9 @@ pub trait Policy {
auth: Data<AuthController>, auth: Data<AuthController>,
token: &str, token: &str,
index: Option<&str>, index: Option<&str>,
) -> Result<AuthFilter, policies::AuthError> where Self: Sized; ) -> Result<AuthFilter, policies::AuthError>
where
Self: Sized;
} }
pub mod policies { pub mod policies {

View File

@ -424,7 +424,8 @@ async fn render(index: Index, query: RenderQuery) -> Result<RenderResult, Render
fragment.clone() fragment.clone()
} }
found => return Err(UnknownTemplatePrefix { found => {
return Err(UnknownTemplatePrefix {
embedder_name: embedder_name.to_string(), embedder_name: embedder_name.to_string(),
found: found.to_string(), found: found.to_string(),
available_indexing_fragments: embedding_config available_indexing_fragments: embedding_config
@ -435,7 +436,8 @@ async fn render(index: Index, query: RenderQuery) -> Result<RenderResult, Render
.config .config
.embedder_options .embedder_options
.search_fragments(), .search_fragments(),
}), })
}
} }
} }
"chatCompletions" | "chatcompletions" => { "chatCompletions" | "chatcompletions" => {

View File

@ -1,7 +1,7 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use ::time::format_description::well_known::Rfc3339; use ::time::format_description::well_known::Rfc3339;
use maplit::{hashmap}; use maplit::hashmap;
use meilisearch::Opt; use meilisearch::Opt;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use tempfile::TempDir; use tempfile::TempDir;
@ -33,8 +33,9 @@ macro_rules! hashset {
} }
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub static AUTHORIZATIONS: Lazy<HashMap<(&'static str, &'static str), HashSet<&'static [&'static str]>>> = pub static AUTHORIZATIONS: Lazy<
Lazy::new(|| { HashMap<(&'static str, &'static str), HashSet<&'static [&'static str]>>,
> = Lazy::new(|| {
let authorizations = hashmap! { let authorizations = hashmap! {
("POST", "/multi-search") => hashset!{"search", "*"}, ("POST", "/multi-search") => hashset!{"search", "*"},
("POST", "/indexes/products/search") => hashset!{"search", "*"}, ("POST", "/indexes/products/search") => hashset!{"search", "*"},
@ -100,7 +101,12 @@ pub static AUTHORIZATIONS: Lazy<HashMap<(&'static str, &'static str), HashSet<&'
}); });
pub static ALL_ACTIONS: Lazy<HashSet<&'static str>> = Lazy::new(|| { pub static ALL_ACTIONS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
AUTHORIZATIONS.values().flat_map(|v| v.iter()).flat_map(|v| v.iter()).copied().collect::<HashSet<_>>() AUTHORIZATIONS
.values()
.flat_map(|v| v.iter())
.flat_map(|v| v.iter())
.copied()
.collect::<HashSet<_>>()
}); });
static INVALID_RESPONSE: Lazy<Value> = Lazy::new(|| { static INVALID_RESPONSE: Lazy<Value> = Lazy::new(|| {
@ -298,7 +304,11 @@ async fn unauthorized_partial_actions() {
// create a new API key letting all actions except one. // create a new API key letting all actions except one.
server.use_api_key(MASTER_KEY); server.use_api_key(MASTER_KEY);
let actions = actions.iter().filter(|&a| a != excluded_action).copied().collect::<HashSet<_>>(); let actions = actions
.iter()
.filter(|&a| a != excluded_action)
.copied()
.collect::<HashSet<_>>();
let content = json!({ let content = json!({
"indexes": ["products"], "indexes": ["products"],
"actions": actions, "actions": actions,
@ -315,7 +325,14 @@ async fn unauthorized_partial_actions() {
let (mut response, code) = server.dummy_request(method, route).await; let (mut response, code) = server.dummy_request(method, route).await;
response["message"] = serde_json::json!(null); response["message"] = serde_json::json!(null);
assert_eq!(response, INVALID_RESPONSE.clone(), "on route: {:?} - {:?} with actions: {:?}", method, route, actions); assert_eq!(
response,
INVALID_RESPONSE.clone(),
"on route: {:?} - {:?} with actions: {:?}",
method,
route,
actions
);
assert_eq!(code, 403, "{:?}", &response); assert_eq!(code, 403, "{:?}", &response);
} }
} }
@ -355,7 +372,11 @@ async fn access_authorized_no_index_restriction() {
route, route,
actions actions
); );
assert_ne!(code, 403, "on route: {:?} - {:?} with action: {:?}", method, route, actions); assert_ne!(
code, 403,
"on route: {:?} - {:?} with action: {:?}",
method, route, actions
);
} }
} }
} }
@ -793,7 +814,13 @@ async fn error_creating_index_without_action() {
server.use_api_key(MASTER_KEY); server.use_api_key(MASTER_KEY);
// create key with access on all indexes. // create key with access on all indexes.
let create_index_actions = AUTHORIZATIONS.get(&("POST","/indexes")).unwrap().iter().flat_map(|s| s.iter()).cloned().collect::<HashSet<_>>(); let create_index_actions = AUTHORIZATIONS
.get(&("POST", "/indexes"))
.unwrap()
.iter()
.flat_map(|s| s.iter())
.cloned()
.collect::<HashSet<_>>();
let content = json!({ let content = json!({
"indexes": ["*"], "indexes": ["*"],
// Give all action but the ones allowing to create an index. // Give all action but the ones allowing to create an index.

View File

@ -457,10 +457,7 @@ impl<State> Index<'_, State> {
self.service.get(url).await self.service.get(url).await
} }
pub async fn render( pub async fn render(&self, query: Value) -> (Value, StatusCode) {
&self,
query: Value
) -> (Value, StatusCode) {
let url = format!("/indexes/{}/render", urlencode(self.uid.as_ref())); let url = format!("/indexes/{}/render", urlencode(self.uid.as_ref()));
self.service.post_encoded(url, query, self.encoder).await self.service.post_encoded(url, query, self.encoder).await
} }

View File

@ -2,5 +2,5 @@ mod add_documents;
mod delete_documents; mod delete_documents;
mod errors; mod errors;
mod get_documents; mod get_documents;
mod update_documents;
mod render_documents; mod render_documents;
mod update_documents;

View File

@ -6,13 +6,7 @@ use meili_snap::snapshot;
async fn empty_id() { async fn empty_id() {
let index = shared_index_for_fragments().await; let index = shared_index_for_fragments().await;
let (value, code) = index let (value, code) = index.render(json! {{ "template": { "id": "" }}}).await;
.render(json! {{
"template": {
"id": ""
}
}})
.await;
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(value, @r#" snapshot!(value, @r#"
{ {
@ -28,13 +22,7 @@ async fn empty_id() {
async fn wrong_id_prefix() { async fn wrong_id_prefix() {
let index = shared_index_for_fragments().await; let index = shared_index_for_fragments().await;
let (value, code) = index let (value, code) = index.render(json! {{ "template": { "id": "wrong.disregarded" }}}).await;
.render(json! {{
"template": {
"id": "wrong.disregarded"
}
}})
.await;
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(value, @r#" snapshot!(value, @r#"
{ {
@ -50,13 +38,7 @@ async fn wrong_id_prefix() {
async fn missing_embedder() { async fn missing_embedder() {
let index = shared_index_for_fragments().await; let index = shared_index_for_fragments().await;
let (value, code) = index let (value, code) = index.render(json! {{ "template": { "id": "embedders" }}}).await;
.render(json! {{
"template": {
"id": "embedders"
}
}})
.await;
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(value, @r#" snapshot!(value, @r#"
{ {
@ -72,13 +54,8 @@ async fn missing_embedder() {
async fn wrong_embedder() { async fn wrong_embedder() {
let index = shared_index_for_fragments().await; let index = shared_index_for_fragments().await;
let (value, code) = index let (value, code) =
.render(json! {{ index.render(json! {{ "template": { "id": "embedders.wrong.disregarded" }}}).await;
"template": {
"id": "embedders.wrong.disregarded"
}
}})
.await;
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(value, @r#" snapshot!(value, @r#"
{ {
@ -94,13 +71,7 @@ async fn wrong_embedder() {
async fn missing_template_kind() { async fn missing_template_kind() {
let index = shared_index_for_fragments().await; let index = shared_index_for_fragments().await;
let (value, code) = index let (value, code) = index.render(json! {{ "template": { "id": "embedders.rest" }}}).await;
.render(json! {{
"template": {
"id": "embedders.rest"
}
}})
.await;
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(value, @r#" snapshot!(value, @r#"
{ {
@ -116,13 +87,8 @@ async fn missing_template_kind() {
async fn wrong_template_kind() { async fn wrong_template_kind() {
let index = shared_index_for_fragments().await; let index = shared_index_for_fragments().await;
let (value, code) = index let (value, code) =
.render(json! {{ index.render(json! {{ "template": { "id": "embedders.rest.wrong.disregarded" }}}).await;
"template": {
"id": "embedders.rest.wrong.disregarded"
}
}})
.await;
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(value, @r#" snapshot!(value, @r#"
{ {
@ -138,13 +104,8 @@ async fn wrong_template_kind() {
async fn document_template_on_fragmented_index() { async fn document_template_on_fragmented_index() {
let index = shared_index_for_fragments().await; let index = shared_index_for_fragments().await;
let (value, code) = index let (value, code) =
.render(json! {{ index.render(json! {{ "template": { "id": "embedders.rest.documentTemplate" }}}).await;
"template": {
"id": "embedders.rest.documentTemplate"
}
}})
.await;
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(value, @r#" snapshot!(value, @r#"
{ {
@ -160,13 +121,8 @@ async fn document_template_on_fragmented_index() {
async fn missing_fragment_name() { async fn missing_fragment_name() {
let index = shared_index_for_fragments().await; let index = shared_index_for_fragments().await;
let (value, code) = index let (value, code) =
.render(json! {{ index.render(json! {{ "template": { "id": "embedders.rest.indexingFragments" }}}).await;
"template": {
"id": "embedders.rest.indexingFragments"
}
}})
.await;
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(value, @r#" snapshot!(value, @r#"
{ {
@ -177,13 +133,8 @@ async fn missing_fragment_name() {
} }
"#); "#);
let (value, code) = index let (value, code) =
.render(json! {{ index.render(json! {{ "template": { "id": "embedders.rest.searchFragments" }}}).await;
"template": {
"id": "embedders.rest.searchFragments"
}
}})
.await;
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(value, @r#" snapshot!(value, @r#"
{ {
@ -200,11 +151,7 @@ async fn wrong_fragment_name() {
let index = shared_index_for_fragments().await; let index = shared_index_for_fragments().await;
let (value, code) = index let (value, code) = index
.render(json! {{ .render(json! {{ "template": { "id": "embedders.rest.indexingFragments.wrong" }}})
"template": {
"id": "embedders.rest.indexingFragments.wrong"
}
}})
.await; .await;
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(value, @r#" snapshot!(value, @r#"
@ -216,13 +163,8 @@ async fn wrong_fragment_name() {
} }
"#); "#);
let (value, code) = index let (value, code) =
.render(json! {{ index.render(json! {{ "template": { "id": "embedders.rest.searchFragments.wrong" }}}).await;
"template": {
"id": "embedders.rest.searchFragments.wrong"
}
}})
.await;
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(value, @r#" snapshot!(value, @r#"
{ {
@ -239,11 +181,9 @@ async fn leftover_tokens() {
let index = shared_index_for_fragments().await; let index = shared_index_for_fragments().await;
let (value, code) = index let (value, code) = index
.render(json! {{ .render(
"template": { json! {{ "template": { "id": "embedders.rest.indexingFragments.withBreed.leftover" }}},
"id": "embedders.rest.indexingFragments.withBreed.leftover" )
}
}})
.await; .await;
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(value, @r#" snapshot!(value, @r#"
@ -256,11 +196,7 @@ async fn leftover_tokens() {
"#); "#);
let (value, code) = index let (value, code) = index
.render(json! {{ .render(json! {{"template": { "id": "embedders.rest.searchFragments.justBreed.leftover" }}})
"template": {
"id": "embedders.rest.searchFragments.justBreed.leftover"
}
}})
.await; .await;
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(value, @r#" snapshot!(value, @r#"
@ -278,11 +214,7 @@ async fn fragment_retrieval() {
let index = shared_index_for_fragments().await; let index = shared_index_for_fragments().await;
let (value, code) = index let (value, code) = index
.render(json! {{ .render(json! {{ "template": { "id": "embedders.rest.indexingFragments.withBreed" }}})
"template": {
"id": "embedders.rest.indexingFragments.withBreed"
}
}})
.await; .await;
snapshot!(code, @"200 OK"); snapshot!(code, @"200 OK");
snapshot!(value, @r#" snapshot!(value, @r#"
@ -293,11 +225,7 @@ async fn fragment_retrieval() {
"#); "#);
let (value, code) = index let (value, code) = index
.render(json! {{ .render(json! {{ "template": { "id": "embedders.rest.searchFragments.justBreed" }}})
"template": {
"id": "embedders.rest.searchFragments.justBreed"
}
}})
.await; .await;
snapshot!(code, @"200 OK"); snapshot!(code, @"200 OK");
snapshot!(value, @r#" snapshot!(value, @r#"

View File

@ -63,10 +63,7 @@ impl Error {
/// Produces an error message when the error happened at rendering time. /// Produces an error message when the error happened at rendering time.
pub fn rendering_error(&self, root: &str) -> String { pub fn rendering_error(&self, root: &str) -> String {
if self.path.is_empty() { if self.path.is_empty() {
format!( format!("error while rendering template: {}", &self.template_error)
"error while rendering template: {}",
&self.template_error
)
} else { } else {
format!( format!(
"in `{}`, error while rendering template: {}", "in `{}`, error while rendering template: {}",
@ -79,10 +76,7 @@ impl Error {
/// Produces an error message when the error happened at parsing time. /// Produces an error message when the error happened at parsing time.
pub fn parsing_error(&self, root: &str) -> String { pub fn parsing_error(&self, root: &str) -> String {
if self.path.is_empty() { if self.path.is_empty() {
format!( format!("error while parsing template: {}", &self.template_error)
"error while parsing template: {}",
&self.template_error
)
} else { } else {
format!( format!(
"in `{}`, error while parsing template: {}", "in `{}`, error while parsing template: {}",
@ -153,10 +147,7 @@ impl JsonTemplate {
/// If its a map, values inside can be accessed directly by their keys. /// If its a map, values inside can be accessed directly by their keys.
pub fn render_serializable<T: Serialize>(&self, object: &T) -> Result<Value, Error> { pub fn render_serializable<T: Serialize>(&self, object: &T) -> Result<Value, Error> {
let object = liquid::to_object(object) let object = liquid::to_object(object)
.map_err(|err| Error { .map_err(|err| Error { template_error: err, path: ValuePath::new() })?;
template_error: err,
path: ValuePath::new(),
})?;
self.render(&object) self.render(&object)
} }

View File

@ -886,9 +886,7 @@ impl EmbedderOptions {
| EmbedderOptions::OpenAi(_) | EmbedderOptions::OpenAi(_)
| EmbedderOptions::Ollama(_) | EmbedderOptions::Ollama(_)
| EmbedderOptions::UserProvided(_) => None, | EmbedderOptions::UserProvided(_) => None,
EmbedderOptions::Rest(embedder_options) => { EmbedderOptions::Rest(embedder_options) => embedder_options.search_fragments.get(name),
embedder_options.search_fragments.get(name)
}
EmbedderOptions::Composite(embedder_options) => { EmbedderOptions::Composite(embedder_options) => {
if let SubEmbedderOptions::Rest(embedder_options) = &embedder_options.search { if let SubEmbedderOptions::Rest(embedder_options) = &embedder_options.search {
embedder_options.search_fragments.get(name) embedder_options.search_fragments.get(name)