1use serde::{Deserialize, Serialize};
2use thiserror::Error;
3
4#[derive(Debug, Error)]
7#[non_exhaustive]
8pub enum Error {
9 #[error(transparent)]
13 Meilisearch(#[from] MeilisearchError),
14
15 #[error(transparent)]
16 MeilisearchCommunication(#[from] MeilisearchCommunicationError),
17 #[error("Error parsing response JSON: {}", .0)]
19 ParseError(#[from] serde_json::Error),
20
21 #[error("A task did not succeed in time.")]
23 Timeout,
24 #[error("Unable to generate a valid HTTP request. It probably comes from an invalid API key.")]
28 InvalidRequest,
29
30 #[error("You need to provide an api key to use the `{0}` method.")]
32 CantUseWithoutApiKey(String),
33 #[error("The provided api_key is invalid.")]
37 TenantTokensInvalidApiKey,
38 #[error("The provided expires_at is already expired.")]
40 TenantTokensExpiredSignature,
41
42 #[cfg(not(target_arch = "wasm32"))]
44 #[error("Impossible to generate the token, jsonwebtoken encountered an error: {}", .0)]
45 InvalidTenantToken(#[from] jsonwebtoken::errors::Error),
46
47 #[cfg(feature = "reqwest")]
49 #[error("HTTP request failed: {}", .0)]
50 HttpError(#[from] reqwest::Error),
51
52 #[error("Internal Error: could not parse the query parameters: {}", .0)]
54 Yaup(#[from] yaup::Error),
55
56 #[cfg(not(target_arch = "wasm32"))]
58 #[error("The uid of the token has bit an uuid4 format: {}", .0)]
59 Uuid(#[from] uuid::Error),
60
61 #[error("The uid provided to the token is not of version uuidv4")]
63 InvalidUuid4Version,
64
65 #[error(transparent)]
66 Other(Box<dyn std::error::Error + Send + Sync + 'static>),
67}
68
69#[derive(Debug, Clone, Deserialize, Error)]
70#[serde(rename_all = "camelCase")]
71pub struct MeilisearchCommunicationError {
72 pub status_code: u16,
73 pub message: Option<String>,
74 pub url: String,
75}
76
77impl std::fmt::Display for MeilisearchCommunicationError {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 write!(
80 f,
81 "MeilisearchCommunicationError: The server responded with a {}.",
82 self.status_code
83 )?;
84 if let Some(message) = &self.message {
85 write!(f, " {message}")?;
86 }
87 write!(f, "\nurl: {}", self.url)?;
88 Ok(())
89 }
90}
91
92#[derive(Debug, Clone, Deserialize, Error)]
93#[serde(rename_all = "camelCase")]
94#[error("Meilisearch {}: {}: {}. {}", .error_type, .error_code, .error_message, .error_link)]
95pub struct MeilisearchError {
96 #[serde(rename = "message")]
98 pub error_message: String,
99 #[serde(rename = "code")]
102 pub error_code: ErrorCode,
103 #[serde(rename = "type")]
105 pub error_type: ErrorType,
106 #[serde(rename = "link")]
108 pub error_link: String,
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
113#[serde(rename_all = "snake_case")]
114#[non_exhaustive]
115pub enum ErrorType {
116 InvalidRequest,
118 Internal,
120 Auth,
122
123 #[serde(other)]
126 Unknown,
127}
128
129impl std::fmt::Display for ErrorType {
130 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
131 write!(
132 fmt,
133 "{}",
134 serde_json::to_value(self).unwrap().as_str().unwrap()
136 )
137 }
138}
139
140#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
144#[serde(rename_all = "snake_case")]
145#[non_exhaustive]
146pub enum ErrorCode {
147 IndexCreationFailed,
148 IndexAlreadyExists,
149 IndexNotFound,
150 InvalidIndexUid,
151 InvalidState,
152 PrimaryKeyInferenceFailed,
153 IndexPrimaryKeyAlreadyPresent,
154 InvalidStoreFile,
155 MaxFieldsLimitExceeded,
156 MissingDocumentId,
157 InvalidDocumentId,
158 BadParameter,
159 BadRequest,
160 DatabaseSizeLimitReached,
161 DocumentNotFound,
162 InternalError,
163 InvalidApiKey,
164 MissingAuthorizationHeader,
165 TaskNotFound,
166 DumpNotFound,
167 MissingMasterKey,
168 NoSpaceLeftOnDevice,
169 PayloadTooLarge,
170 UnretrievableDocument,
171 SearchError,
172 UnsupportedMediaType,
173 DumpAlreadyProcessing,
174 DumpProcessFailed,
175 MissingContentType,
176 MalformedPayload,
177 InvalidContentType,
178 MissingPayload,
179 InvalidApiKeyDescription,
180 InvalidApiKeyActions,
181 InvalidApiKeyIndexes,
182 InvalidApiKeyExpiresAt,
183 ApiKeyNotFound,
184 MissingTaskFilters,
185 MissingIndexUid,
186 InvalidIndexOffset,
187 InvalidIndexLimit,
188 InvalidIndexPrimaryKey,
189 InvalidDocumentFilter,
190 MissingDocumentFilter,
191 InvalidDocumentFields,
192 InvalidDocumentLimit,
193 InvalidDocumentOffset,
194 InvalidDocumentGeoField,
195 InvalidSearchQ,
196 InvalidSearchOffset,
197 InvalidSearchLimit,
198 InvalidSearchPage,
199 InvalidSearchHitsPerPage,
200 InvalidSearchAttributesToRetrieve,
201 InvalidSearchAttributesToCrop,
202 InvalidSearchCropLength,
203 InvalidSearchAttributesToHighlight,
204 InvalidSearchShowMatchesPosition,
205 InvalidSearchFilter,
206 InvalidSearchSort,
207 InvalidSearchFacets,
208 InvalidSearchHighlightPreTag,
209 InvalidSearchHighlightPostTag,
210 InvalidSearchCropMarker,
211 InvalidSearchMatchingStrategy,
212 ImmutableApiKeyUid,
213 ImmutableApiKeyActions,
214 ImmutableApiKeyIndexes,
215 ImmutableExpiresAt,
216 ImmutableCreatedAt,
217 ImmutableUpdatedAt,
218 InvalidSwapDuplicateIndexFound,
219 InvalidSwapIndexes,
220 MissingSwapIndexes,
221 InvalidTaskTypes,
222 InvalidTaskUids,
223 InvalidTaskStatuses,
224 InvalidTaskLimit,
225 InvalidTaskFrom,
226 InvalidTaskCanceledBy,
227 InvalidTaskFilters,
228 TooManyOpenFiles,
229 IoError,
230 InvalidTaskIndexUids,
231 ImmutableIndexUid,
232 ImmutableIndexCreatedAt,
233 ImmutableIndexUpdatedAt,
234 InvalidSettingsDisplayedAttributes,
235 InvalidSettingsSearchableAttributes,
236 InvalidSettingsFilterableAttributes,
237 InvalidSettingsSortableAttributes,
238 InvalidSettingsRankingRules,
239 InvalidSettingsStopWords,
240 InvalidSettingsSynonyms,
241 InvalidSettingsDistinctAttributes,
242 InvalidSettingsTypoTolerance,
243 InvalidSettingsFaceting,
244 InvalidSettingsDictionary,
245 InvalidSettingsPagination,
246 InvalidTaskBeforeEnqueuedAt,
247 InvalidTaskAfterEnqueuedAt,
248 InvalidTaskBeforeStartedAt,
249 InvalidTaskAfterStartedAt,
250 InvalidTaskBeforeFinishedAt,
251 InvalidTaskAfterFinishedAt,
252 MissingApiKeyActions,
253 MissingApiKeyIndexes,
254 MissingApiKeyExpiresAt,
255 InvalidApiKeyLimit,
256 InvalidApiKeyOffset,
257
258 #[serde(other)]
261 Unknown,
262}
263
264pub const MEILISEARCH_VERSION_HINT: &str = "Hint: It might not be working because you're not up to date with the Meilisearch version that updated the get_documents_with method";
265
266impl std::fmt::Display for ErrorCode {
267 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
268 write!(
269 fmt,
270 "{}",
271 serde_json::to_value(self).unwrap().as_str().unwrap()
273 )
274 }
275}
276
277#[cfg(test)]
278mod test {
279 use super::*;
280
281 use jsonwebtoken::errors::ErrorKind::InvalidToken;
282 use meilisearch_test_macro::meilisearch_test;
283 use uuid::Uuid;
284
285 #[meilisearch_test]
286 async fn test_meilisearch_error() {
287 let error: MeilisearchError = serde_json::from_str(
288 r#"
289{
290 "message": "The cool error message.",
291 "code": "index_creation_failed",
292 "type": "internal",
293 "link": "https://the best link ever"
294}"#,
295 )
296 .unwrap();
297
298 assert_eq!(error.error_message, "The cool error message.");
299 assert_eq!(error.error_code, ErrorCode::IndexCreationFailed);
300 assert_eq!(error.error_type, ErrorType::Internal);
301 assert_eq!(error.error_link, "https://the best link ever");
302
303 let error: MeilisearchError = serde_json::from_str(
304 r#"
305{
306 "message": "",
307 "code": "An unknown error",
308 "type": "An unknown type",
309 "link": ""
310}"#,
311 )
312 .unwrap();
313
314 assert_eq!(error.error_code, ErrorCode::Unknown);
315 assert_eq!(error.error_type, ErrorType::Unknown);
316 }
317
318 #[meilisearch_test]
319 async fn test_error_message_parsing() {
320 let error: MeilisearchError = serde_json::from_str(
321 r#"
322{
323 "message": "The cool error message.",
324 "code": "index_creation_failed",
325 "type": "internal",
326 "link": "https://the best link ever"
327}"#,
328 )
329 .unwrap();
330
331 assert_eq!(error.to_string(), "Meilisearch internal: index_creation_failed: The cool error message.. https://the best link ever");
332
333 let error: MeilisearchCommunicationError = MeilisearchCommunicationError {
334 status_code: 404,
335 message: Some("Hint: something.".to_string()),
336 url: "http://localhost:7700/something".to_string(),
337 };
338
339 assert_eq!(
340 error.to_string(),
341 "MeilisearchCommunicationError: The server responded with a 404. Hint: something.\nurl: http://localhost:7700/something"
342 );
343
344 let error: MeilisearchCommunicationError = MeilisearchCommunicationError {
345 status_code: 404,
346 message: None,
347 url: "http://localhost:7700/something".to_string(),
348 };
349
350 assert_eq!(
351 error.to_string(),
352 "MeilisearchCommunicationError: The server responded with a 404.\nurl: http://localhost:7700/something"
353 );
354
355 let error = Error::Timeout;
356 assert_eq!(error.to_string(), "A task did not succeed in time.");
357
358 let error = Error::InvalidRequest;
359 assert_eq!(
360 error.to_string(),
361 "Unable to generate a valid HTTP request. It probably comes from an invalid API key."
362 );
363
364 let error = Error::TenantTokensInvalidApiKey;
365 assert_eq!(error.to_string(), "The provided api_key is invalid.");
366
367 let error = Error::TenantTokensExpiredSignature;
368 assert_eq!(
369 error.to_string(),
370 "The provided expires_at is already expired."
371 );
372
373 let error = Error::InvalidUuid4Version;
374 assert_eq!(
375 error.to_string(),
376 "The uid provided to the token is not of version uuidv4"
377 );
378
379 let error = Error::Uuid(Uuid::parse_str("67e55044").unwrap_err());
380 assert_eq!(error.to_string(), "The uid of the token has bit an uuid4 format: invalid length: expected length 32 for simple format, found 8");
381
382 let data = r#"
383 {
384 "name": "John Doe"
385 "age": 43,
386 }"#;
387
388 let error = Error::ParseError(serde_json::from_str::<String>(data).unwrap_err());
389 assert_eq!(
390 error.to_string(),
391 "Error parsing response JSON: invalid type: map, expected a string at line 2 column 8"
392 );
393
394 let error = Error::HttpError(
395 reqwest::Client::new()
396 .execute(reqwest::Request::new(
397 reqwest::Method::POST,
398 reqwest::Url::parse("https://meilisearch.gouv.fr").unwrap(),
400 ))
401 .await
402 .unwrap_err(),
403 );
404 assert_eq!(
405 error.to_string(),
406 "HTTP request failed: error sending request for url (https://meilisearch.gouv.fr/)"
407 );
408
409 let error = Error::InvalidTenantToken(jsonwebtoken::errors::Error::from(InvalidToken));
410 assert_eq!(
411 error.to_string(),
412 "Impossible to generate the token, jsonwebtoken encountered an error: InvalidToken"
413 );
414
415 let error = Error::Yaup(yaup::Error::Custom("Test yaup error".to_string()));
416 assert_eq!(
417 error.to_string(),
418 "Internal Error: could not parse the query parameters: Test yaup error"
419 );
420 }
421}