mirror of
				https://github.com/meilisearch/meilisearch.git
				synced 2025-10-27 05:56:26 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			153 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			153 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| use deserr::Deserr;
 | |
| use serde::{Deserialize, Serialize};
 | |
| use utoipa::ToSchema;
 | |
| 
 | |
| use crate::is_faceted_by;
 | |
| 
 | |
| #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
 | |
| #[repr(transparent)]
 | |
| #[serde(transparent)]
 | |
| pub struct AttributePatterns {
 | |
|     #[schema(example = json!(["title", "overview_*", "release_date"]))]
 | |
|     pub patterns: Vec<String>,
 | |
| }
 | |
| 
 | |
| impl<E: deserr::DeserializeError> Deserr<E> for AttributePatterns {
 | |
|     fn deserialize_from_value<V: deserr::IntoValue>(
 | |
|         value: deserr::Value<V>,
 | |
|         location: deserr::ValuePointerRef,
 | |
|     ) -> Result<Self, E> {
 | |
|         Vec::<String>::deserialize_from_value(value, location).map(|patterns| Self { patterns })
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl From<Vec<String>> for AttributePatterns {
 | |
|     fn from(patterns: Vec<String>) -> Self {
 | |
|         Self { patterns }
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl AttributePatterns {
 | |
|     /// Match a string against the attribute patterns using the match_pattern function.
 | |
|     pub fn match_str(&self, str: &str) -> PatternMatch {
 | |
|         let mut pattern_match = PatternMatch::NoMatch;
 | |
|         for pattern in &self.patterns {
 | |
|             match match_pattern(pattern, str) {
 | |
|                 PatternMatch::Match => return PatternMatch::Match,
 | |
|                 PatternMatch::Parent => pattern_match = PatternMatch::Parent,
 | |
|                 PatternMatch::NoMatch => {}
 | |
|             }
 | |
|         }
 | |
|         pattern_match
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// Match a string against a pattern.
 | |
| ///
 | |
| /// The pattern can be a wildcard, a prefix, a suffix or an exact match.
 | |
| ///
 | |
| /// # Arguments
 | |
| ///
 | |
| /// * `pattern` - The pattern to match against.
 | |
| /// * `str` - The string to match against the pattern.
 | |
| pub fn match_pattern(pattern: &str, str: &str) -> PatternMatch {
 | |
|     // If the pattern is a wildcard, return Match
 | |
|     if pattern == "*" {
 | |
|         return PatternMatch::Match;
 | |
|     } else if pattern.starts_with('*') && pattern.ends_with('*') {
 | |
|         // If the pattern starts and ends with a wildcard, return Match if the string contains the pattern without the wildcards
 | |
|         if str.contains(&pattern[1..pattern.len() - 1]) {
 | |
|             return PatternMatch::Match;
 | |
|         }
 | |
|     } else if let Some(pattern) = pattern.strip_prefix('*') {
 | |
|         // If the pattern starts with a wildcard, return Match if the string ends with the pattern without the wildcard
 | |
|         if str.ends_with(pattern) {
 | |
|             return PatternMatch::Match;
 | |
|         }
 | |
|     } else if let Some(pattern) = pattern.strip_suffix('*') {
 | |
|         // If the pattern ends with a wildcard, return Match if the string starts with the pattern without the wildcard
 | |
|         if str.starts_with(pattern) {
 | |
|             return PatternMatch::Match;
 | |
|         }
 | |
|     } else if pattern == str {
 | |
|         // If the pattern is exactly the string, return Match
 | |
|         return PatternMatch::Match;
 | |
|     }
 | |
| 
 | |
|     // If the field is a parent field of the pattern, return Parent
 | |
|     if is_faceted_by(pattern, str) {
 | |
|         PatternMatch::Parent
 | |
|     } else {
 | |
|         PatternMatch::NoMatch
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// Match a field against a pattern using the legacy behavior.
 | |
| ///
 | |
| /// A field matches a pattern if it is a parent of the pattern or if it is the pattern itself.
 | |
| /// This behavior is used to match the sortable attributes, the searchable attributes and the filterable attributes rules `Field`.
 | |
| ///
 | |
| /// # Arguments
 | |
| ///
 | |
| /// * `pattern` - The pattern to match against.
 | |
| /// * `field` - The field to match against the pattern.
 | |
| pub fn match_field_legacy(pattern: &str, field: &str) -> PatternMatch {
 | |
|     if is_faceted_by(field, pattern) {
 | |
|         // If the field matches the pattern or is a nested field of the pattern, return Match (legacy behavior)
 | |
|         PatternMatch::Match
 | |
|     } else if is_faceted_by(pattern, field) {
 | |
|         // If the field is a parent field of the pattern, return Parent
 | |
|         PatternMatch::Parent
 | |
|     } else {
 | |
|         // If the field does not match the pattern and is not a parent of a nested field that matches the pattern, return NoMatch
 | |
|         PatternMatch::NoMatch
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// Match a field against a distinct field.
 | |
| pub fn match_distinct_field(distinct_field: Option<&str>, field: &str) -> PatternMatch {
 | |
|     if let Some(distinct_field) = distinct_field {
 | |
|         if field == distinct_field {
 | |
|             // If the field matches exactly the distinct field, return Match
 | |
|             return PatternMatch::Match;
 | |
|         } else if is_faceted_by(distinct_field, field) {
 | |
|             // If the field is a parent field of the distinct field, return Parent
 | |
|             return PatternMatch::Parent;
 | |
|         }
 | |
|     }
 | |
|     // If the field does not match the distinct field and is not a parent of a nested field that matches the distinct field, return NoMatch
 | |
|     PatternMatch::NoMatch
 | |
| }
 | |
| 
 | |
| #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 | |
| pub enum PatternMatch {
 | |
|     /// The field is a parent of a nested field that matches the pattern
 | |
|     /// For example, the field is `toto`, and the pattern is `toto.titi`
 | |
|     Parent,
 | |
|     /// The field matches the pattern
 | |
|     Match,
 | |
|     /// The field does not match the pattern
 | |
|     NoMatch,
 | |
| }
 | |
| 
 | |
| #[cfg(test)]
 | |
| mod tests {
 | |
|     use super::*;
 | |
| 
 | |
|     #[test]
 | |
|     fn test_match_pattern() {
 | |
|         assert_eq!(match_pattern("*", "test"), PatternMatch::Match);
 | |
|         assert_eq!(match_pattern("test*", "test"), PatternMatch::Match);
 | |
|         assert_eq!(match_pattern("test*", "testa"), PatternMatch::Match);
 | |
|         assert_eq!(match_pattern("*test", "test"), PatternMatch::Match);
 | |
|         assert_eq!(match_pattern("*test", "atest"), PatternMatch::Match);
 | |
|         assert_eq!(match_pattern("*test*", "test"), PatternMatch::Match);
 | |
|         assert_eq!(match_pattern("*test*", "atesta"), PatternMatch::Match);
 | |
|         assert_eq!(match_pattern("*test*", "atest"), PatternMatch::Match);
 | |
|         assert_eq!(match_pattern("*test*", "testa"), PatternMatch::Match);
 | |
|         assert_eq!(match_pattern("test*test", "test"), PatternMatch::NoMatch);
 | |
|         assert_eq!(match_pattern("*test", "testa"), PatternMatch::NoMatch);
 | |
|         assert_eq!(match_pattern("test*", "atest"), PatternMatch::NoMatch);
 | |
|     }
 | |
| }
 |