1use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6use super::traits::{Resource, ResourceKind};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10#[serde(rename_all = "camelCase")]
11pub struct Index {
12 pub name: String,
13 pub fields: Vec<Field>,
14 #[serde(skip_serializing_if = "Option::is_none")]
15 pub scoring_profiles: Option<Vec<ScoringProfile>>,
16 #[serde(skip_serializing_if = "Option::is_none")]
17 pub default_scoring_profile: Option<String>,
18 #[serde(skip_serializing_if = "Option::is_none")]
19 pub cors_options: Option<CorsOptions>,
20 #[serde(skip_serializing_if = "Option::is_none")]
21 pub suggesters: Option<Vec<Suggester>>,
22 #[serde(skip_serializing_if = "Option::is_none")]
23 pub analyzers: Option<Vec<Value>>,
24 #[serde(skip_serializing_if = "Option::is_none")]
25 pub tokenizers: Option<Vec<Value>>,
26 #[serde(skip_serializing_if = "Option::is_none")]
27 pub token_filters: Option<Vec<Value>>,
28 #[serde(skip_serializing_if = "Option::is_none")]
29 pub char_filters: Option<Vec<Value>>,
30 #[serde(skip_serializing_if = "Option::is_none")]
31 pub similarity: Option<Value>,
32 #[serde(skip_serializing_if = "Option::is_none")]
33 pub semantic: Option<SemanticConfiguration>,
34 #[serde(skip_serializing_if = "Option::is_none")]
35 pub vector_search: Option<VectorSearch>,
36 #[serde(flatten)]
38 pub extra: std::collections::HashMap<String, Value>,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
42#[serde(rename_all = "camelCase")]
43pub struct Field {
44 pub name: String,
45 #[serde(rename = "type")]
46 pub field_type: String,
47 #[serde(skip_serializing_if = "Option::is_none")]
48 pub key: Option<bool>,
49 #[serde(skip_serializing_if = "Option::is_none")]
50 pub searchable: Option<bool>,
51 #[serde(skip_serializing_if = "Option::is_none")]
52 pub filterable: Option<bool>,
53 #[serde(skip_serializing_if = "Option::is_none")]
54 pub sortable: Option<bool>,
55 #[serde(skip_serializing_if = "Option::is_none")]
56 pub facetable: Option<bool>,
57 #[serde(skip_serializing_if = "Option::is_none")]
58 pub retrievable: Option<bool>,
59 #[serde(skip_serializing_if = "Option::is_none")]
60 pub stored: Option<bool>,
61 #[serde(skip_serializing_if = "Option::is_none")]
62 pub analyzer: Option<String>,
63 #[serde(skip_serializing_if = "Option::is_none")]
64 pub search_analyzer: Option<String>,
65 #[serde(skip_serializing_if = "Option::is_none")]
66 pub index_analyzer: Option<String>,
67 #[serde(skip_serializing_if = "Option::is_none")]
68 pub synonym_maps: Option<Vec<String>>,
69 #[serde(skip_serializing_if = "Option::is_none")]
70 pub fields: Option<Vec<Field>>,
71 #[serde(skip_serializing_if = "Option::is_none")]
72 pub dimensions: Option<i32>,
73 #[serde(skip_serializing_if = "Option::is_none")]
74 pub vector_search_profile: Option<String>,
75 #[serde(flatten)]
76 pub extra: std::collections::HashMap<String, Value>,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
80#[serde(rename_all = "camelCase")]
81pub struct ScoringProfile {
82 pub name: String,
83 #[serde(skip_serializing_if = "Option::is_none")]
84 pub text: Option<Value>,
85 #[serde(skip_serializing_if = "Option::is_none")]
86 pub functions: Option<Vec<Value>>,
87 #[serde(skip_serializing_if = "Option::is_none")]
88 pub function_aggregation: Option<String>,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
92#[serde(rename_all = "camelCase")]
93pub struct CorsOptions {
94 pub allowed_origins: Vec<String>,
95 #[serde(skip_serializing_if = "Option::is_none")]
96 pub max_age_in_seconds: Option<i64>,
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
100#[serde(rename_all = "camelCase")]
101pub struct Suggester {
102 pub name: String,
103 pub search_mode: String,
104 pub source_fields: Vec<String>,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
108#[serde(rename_all = "camelCase")]
109pub struct SemanticConfiguration {
110 #[serde(skip_serializing_if = "Option::is_none")]
111 pub default_configuration: Option<String>,
112 #[serde(skip_serializing_if = "Option::is_none")]
113 pub configurations: Option<Vec<Value>>,
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
117#[serde(rename_all = "camelCase")]
118pub struct VectorSearch {
119 #[serde(skip_serializing_if = "Option::is_none")]
120 pub algorithms: Option<Vec<Value>>,
121 #[serde(skip_serializing_if = "Option::is_none")]
122 pub profiles: Option<Vec<Value>>,
123 #[serde(skip_serializing_if = "Option::is_none")]
124 pub vectorizers: Option<Vec<Value>>,
125 #[serde(skip_serializing_if = "Option::is_none")]
126 pub compressions: Option<Vec<Value>>,
127}
128
129impl Resource for Index {
130 fn kind() -> ResourceKind {
131 ResourceKind::Index
132 }
133
134 fn name(&self) -> &str {
135 &self.name
136 }
137
138 fn immutable_fields() -> &'static [&'static str] {
139 &["fields"]
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn test_index_kind() {
150 assert_eq!(Index::kind(), ResourceKind::Index);
151 }
152
153 #[test]
154 fn test_index_immutable_fields() {
155 assert_eq!(Index::immutable_fields(), &["fields"]);
156 }
157
158 #[test]
159 fn test_index_deserialize_minimal() {
160 let json = r#"{
161 "name": "my-index",
162 "fields": [
163 { "name": "id", "type": "Edm.String", "key": true }
164 ]
165 }"#;
166 let index: Index = serde_json::from_str(json).unwrap();
167 assert_eq!(index.name, "my-index");
168 assert_eq!(index.fields.len(), 1);
169 assert_eq!(index.fields[0].name, "id");
170 assert_eq!(index.fields[0].key, Some(true));
171 }
172
173 #[test]
174 fn test_index_deserialize_with_vector_search() {
175 let json = r#"{
176 "name": "vec-index",
177 "fields": [],
178 "vectorSearch": {
179 "algorithms": [{"name": "hnsw"}],
180 "profiles": [{"name": "default"}]
181 }
182 }"#;
183 let index: Index = serde_json::from_str(json).unwrap();
184 assert!(index.vector_search.is_some());
185 }
186
187 #[test]
188 fn test_index_extra_fields_preserved() {
189 let json = r#"{
190 "name": "idx",
191 "fields": [],
192 "customField": "hello"
193 }"#;
194 let index: Index = serde_json::from_str(json).unwrap();
195 assert_eq!(
196 index.extra.get("customField").and_then(|v| v.as_str()),
197 Some("hello")
198 );
199 }
200}