1use std::collections::HashMap;
25use std::hash::Hash;
26
27use reqwest::StatusCode;
28
29use serde::Serialize;
30use serde_json::{Map, Value};
31
32use crate::{error::EsError, operations::GenericResult, Client, EsResponse};
33
34pub type DocType<'a> = HashMap<&'a str, HashMap<&'a str, &'a str>>;
35pub type Mapping<'a> = HashMap<&'a str, DocType<'a>>;
36
37#[derive(Debug, Serialize)]
38pub struct Settings {
39 pub number_of_shards: u32,
40 pub analysis: Analysis,
41}
42
43#[derive(Debug, Serialize, Default)]
44pub struct Analysis {
45 pub filter: Map<String, Value>,
46 pub analyzer: Map<String, Value>,
47 pub tokenizer: Map<String, Value>,
48 pub char_filter: Map<String, Value>,
49}
50
51#[derive(Debug)]
53pub struct MappingOperation<'a, 'b> {
54 client: &'a mut Client,
56
57 index: &'b str,
59
60 mapping: Option<&'b Mapping<'b>>,
62
63 settings: Option<&'b Settings>,
66}
67
68impl<'a, 'b> MappingOperation<'a, 'b> {
69 pub fn new(client: &'a mut Client, index: &'b str) -> MappingOperation<'a, 'b> {
70 MappingOperation {
71 client,
72 index,
73 mapping: None,
74 settings: None,
75 }
76 }
77
78 pub fn with_mapping(&'b mut self, mapping: &'b Mapping) -> &'b mut Self {
80 self.mapping = Some(mapping);
81 self
82 }
83
84 pub fn with_settings(&'b mut self, settings: &'b Settings) -> &'b mut Self {
86 self.settings = Some(settings);
87 self
88 }
89
90 pub fn send(&'b mut self) -> Result<MappingResult, EsError> {
96 if self.mapping.is_none() && self.settings.is_none() {
98 return Ok(MappingResult);
99 }
100
101 if self.settings.is_some() {
102 let body = hashmap("settings", self.settings.unwrap());
103 let url = self.index.to_owned();
104 let _ = self.client.put_body_op(&url, &body)?;
105
106 let _ = self.client.wait_for_status("yellow", "5s");
107 }
108
109 if self.mapping.is_some() {
110 let _ = self.client.close_index(self.index);
111
112 for (entity, properties) in self.mapping.unwrap().iter() {
113 let body = hashmap("properties", properties);
114 let url = format!("{}/_mapping/{}", self.index, entity);
115 let _ = self.client.put_body_op(&url, &body)?;
116 }
117
118 let _ = self.client.open_index(self.index);
119 }
120
121 Ok(MappingResult)
122 }
123}
124
125impl Client {
126 pub fn open_index<'a>(&'a mut self, index: &'a str) -> Result<GenericResult, EsError> {
128 let url = format!("{}/_open", index);
129 let response = self.post_op(&url)?;
130
131 match response.status_code() {
132 StatusCode::OK => Ok(response.read_response()?),
133 status_code => Err(EsError::EsError(format!(
134 "Unexpected status: {}",
135 status_code
136 ))),
137 }
138 }
139
140 pub fn close_index<'a>(&'a mut self, index: &'a str) -> Result<GenericResult, EsError> {
142 let url = format!("{}/_close", index);
143 let response = self.post_op(&url)?;
144
145 match response.status_code() {
146 StatusCode::OK => Ok(response.read_response()?),
147 status_code => Err(EsError::EsError(format!(
148 "Unexpected status: {}",
149 status_code
150 ))),
151 }
152 }
153
154 pub fn wait_for_status<'a>(
157 &'a mut self,
158 status: &'a str,
159 timeout: &'a str,
160 ) -> Result<(), EsError> {
161 let url = format!(
162 "_cluster/health?wait_for_status={}&timeout={}",
163 status, timeout
164 );
165 let response = self.get_op(&url)?;
166
167 match response.status_code() {
168 StatusCode::OK => Ok(()),
169 status_code => Err(EsError::EsError(format!(
170 "Unexpected status: {}",
171 status_code
172 ))),
173 }
174 }
175}
176
177#[derive(Debug)]
179pub struct MappingResult;
180
181#[cfg(test)]
182pub mod tests {
183 use super::*;
184
185 #[derive(Debug, Serialize)]
186 pub struct Author {
187 pub name: String,
188 }
189
190 #[test]
191 fn test_mapping() {
192 let index_name = "tests_test_mapping";
193 let mut client = crate::tests::make_client();
194
195 let _ = client.delete_index(index_name);
198
199 let mapping = hashmap2(
200 "post",
201 hashmap2(
202 "created_at",
203 hashmap2("type", "date", "format", "date_time"),
204 "title",
205 hashmap2("type", "string", "index", "not_analyzed"),
206 ),
207 "author",
208 hashmap("name", hashmap("type", "string")),
209 );
210
211 let settings = Settings {
212 number_of_shards: 1,
213
214 analysis: Analysis {
215 filter: serde_json::json! ({
216 "autocomplete_filter": {
217 "type": "edge_ngram",
218 "min_gram": 1,
219 "max_gram": 2,
220 }
221 })
222 .as_object()
223 .expect("by construction 'autocomplete_filter' should be a map")
224 .clone(),
225 analyzer: serde_json::json! ({
226 "autocomplete": {
227 "type": "custom",
228 "tokenizer": "standard",
229 "filter": [ "lowercase", "autocomplete_filter"]
230 }
231 })
232 .as_object()
233 .expect("by construction 'autocomplete' should be a map")
234 .clone(),
235 char_filter: serde_json::json! ({
236 "char_filter": {
237 "type": "pattern_replace",
238 "pattern": ",",
239 "replacement": " "
240 }
241 })
242 .as_object()
243 .expect("by construction 'char_filter' should be a map")
244 .clone(),
245 tokenizer: serde_json::json! ({
246 })
247 .as_object()
248 .expect("by construction 'empty tokenizer' should be a map")
249 .clone(),
250 },
251 };
252
253 let result = MappingOperation::new(&mut client, index_name)
255 .with_mapping(&mapping)
256 .with_settings(&settings)
257 .send();
258 assert!(result.is_ok());
259
260 {
261 let result_wrapped = client
262 .index(index_name, "post")
263 .with_doc(&Author {
264 name: "Homu".to_owned(),
265 })
266 .send();
267
268 assert!(result_wrapped.is_ok());
269
270 let result = result_wrapped.unwrap();
271 assert!(result.created);
272 }
273 }
274}
275
276fn hashmap<K, V>(k: K, v: V) -> HashMap<K, V>
277where
278 K: Eq + Hash,
279{
280 let mut m = HashMap::with_capacity(1);
281 m.insert(k, v);
282 m
283}
284
285#[allow(dead_code)]
286fn hashmap2<K, V>(k1: K, v1: V, k2: K, v2: V) -> HashMap<K, V>
287where
288 K: Eq + Hash,
289{
290 let mut m = HashMap::with_capacity(2);
291 m.insert(k1, v1);
292 m.insert(k2, v2);
293 m
294}