milli_core/
attribute_patterns.rs1use deserr::Deserr;
2use serde::{Deserialize, Serialize};
3use utoipa::ToSchema;
4
5use crate::is_faceted_by;
6
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
8#[repr(transparent)]
9#[serde(transparent)]
10pub struct AttributePatterns {
11 #[schema(example = json!(["title", "overview_*", "release_date"]))]
12 pub patterns: Vec<String>,
13}
14
15impl<E: deserr::DeserializeError> Deserr<E> for AttributePatterns {
16 fn deserialize_from_value<V: deserr::IntoValue>(
17 value: deserr::Value<V>,
18 location: deserr::ValuePointerRef,
19 ) -> Result<Self, E> {
20 Vec::<String>::deserialize_from_value(value, location).map(|patterns| Self { patterns })
21 }
22}
23
24impl From<Vec<String>> for AttributePatterns {
25 fn from(patterns: Vec<String>) -> Self {
26 Self { patterns }
27 }
28}
29
30impl AttributePatterns {
31 pub fn match_str(&self, str: &str) -> PatternMatch {
33 let mut pattern_match = PatternMatch::NoMatch;
34 for pattern in &self.patterns {
35 match match_pattern(pattern, str) {
36 PatternMatch::Match => return PatternMatch::Match,
37 PatternMatch::Parent => pattern_match = PatternMatch::Parent,
38 PatternMatch::NoMatch => {}
39 }
40 }
41 pattern_match
42 }
43}
44
45pub fn match_pattern(pattern: &str, str: &str) -> PatternMatch {
54 if pattern == "*" {
56 return PatternMatch::Match;
57 } else if pattern.starts_with('*') && pattern.ends_with('*') {
58 if str.contains(&pattern[1..pattern.len() - 1]) {
60 return PatternMatch::Match;
61 }
62 } else if let Some(pattern) = pattern.strip_prefix('*') {
63 if str.ends_with(pattern) {
65 return PatternMatch::Match;
66 }
67 } else if let Some(pattern) = pattern.strip_suffix('*') {
68 if str.starts_with(pattern) {
70 return PatternMatch::Match;
71 }
72 } else if pattern == str {
73 return PatternMatch::Match;
75 }
76
77 if is_faceted_by(pattern, str) {
79 PatternMatch::Parent
80 } else {
81 PatternMatch::NoMatch
82 }
83}
84
85pub fn match_field_legacy(pattern: &str, field: &str) -> PatternMatch {
95 if is_faceted_by(field, pattern) {
96 PatternMatch::Match
98 } else if is_faceted_by(pattern, field) {
99 PatternMatch::Parent
101 } else {
102 PatternMatch::NoMatch
104 }
105}
106
107pub fn match_distinct_field(distinct_field: Option<&str>, field: &str) -> PatternMatch {
109 if let Some(distinct_field) = distinct_field {
110 if field == distinct_field {
111 return PatternMatch::Match;
113 } else if is_faceted_by(distinct_field, field) {
114 return PatternMatch::Parent;
116 }
117 }
118 PatternMatch::NoMatch
120}
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123pub enum PatternMatch {
124 Parent,
127 Match,
129 NoMatch,
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn test_match_pattern() {
139 assert_eq!(match_pattern("*", "test"), PatternMatch::Match);
140 assert_eq!(match_pattern("test*", "test"), PatternMatch::Match);
141 assert_eq!(match_pattern("test*", "testa"), PatternMatch::Match);
142 assert_eq!(match_pattern("*test", "test"), PatternMatch::Match);
143 assert_eq!(match_pattern("*test", "atest"), PatternMatch::Match);
144 assert_eq!(match_pattern("*test*", "test"), PatternMatch::Match);
145 assert_eq!(match_pattern("*test*", "atesta"), PatternMatch::Match);
146 assert_eq!(match_pattern("*test*", "atest"), PatternMatch::Match);
147 assert_eq!(match_pattern("*test*", "testa"), PatternMatch::Match);
148 assert_eq!(match_pattern("test*test", "test"), PatternMatch::NoMatch);
149 assert_eq!(match_pattern("*test", "testa"), PatternMatch::NoMatch);
150 assert_eq!(match_pattern("test*", "atest"), PatternMatch::NoMatch);
151 }
152}