diff --git a/.code-samples.meilisearch.yaml b/.code-samples.meilisearch.yaml index 74442a9e..42a80302 100644 --- a/.code-samples.meilisearch.yaml +++ b/.code-samples.meilisearch.yaml @@ -690,7 +690,7 @@ get_all_keys_1: |- create_a_key_1: |- let mut key_options = KeyBuilder::new("Add documents: Products API key"); key_options.with_action(Action::DocumentsAdd) - .with_expires_at("2042-04-02T00:42:42Z") + .with_expires_at(time::macros::datetime!(2042 - 04 - 02 00:42:42 UTC)) .with_index("products"); let new_key = client.create_key(key_options).await.unwrap(); update_a_key_1: |- @@ -698,7 +698,7 @@ update_a_key_1: |- key.description = "Manage documents: Products/Reviews API key".to_string(); key.actions = vec![Action::DocumentsAdd, Action::DocumentsDelete]; key.indexes = vec!["products".to_string(), "reviews".to_string()]; - key.expires_at = Some("2042-04-02T00:42:42Z".to_string()); + key.expires_at = Some(time::macros::datetime!(2042 - 04 - 02 00:42:42 UTC)); let updated_key = client.update_key(&key); delete_a_key_1: |- let key = client.get_key("d0552b41536279a0ad88bd595327b96f01176a60c2243e906c52ac02375f9bc4").await.unwrap(); @@ -718,7 +718,7 @@ security_guide_create_key_1: |- let client = Client::new("http://localhost:7700", "masterKey"); let mut key_options = KeyBuilder::new("Search patient records key"); key_options.with_action(Action::Search) - .with_expires_at("2023-01-01T00:00:00Z") + .with_expires_at(time::macros::datetime!(2023 - 01 - 01 00:00:00 UTC)) .with_index("patient_medical_records"); let new_key = client.create_key(key_options).await.unwrap(); security_guide_list_keys_1: |- diff --git a/Cargo.toml b/Cargo.toml index 36c4e347..3fc209f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,9 +10,11 @@ repository = "https://github.com/meilisearch/meilisearch-sdk" [dependencies] async-trait = "0.1.51" -serde_json = "1.0" +iso8601-duration = "0.1.0" log = "0.4" serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +time = { version = "0.3.7", features = ["serde-well-known", "formatting", "parsing"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] futures = "0.3" diff --git a/src/client.rs b/src/client.rs index 49b8145e..023ad377 100644 --- a/src/client.rs +++ b/src/client.rs @@ -99,11 +99,11 @@ impl Client { /// # futures::executor::block_on(async move { /// // create the client /// let client = Client::new("http://localhost:7700", "masterKey"); - /// # let index = client.create_index("movies", None).await.unwrap().wait_for_completion(&client, None, None).await.unwrap().try_make_index(&client).unwrap(); + /// # let index = client.create_index("get_index", None).await.unwrap().wait_for_completion(&client, None, None).await.unwrap().try_make_index(&client).unwrap(); /// - /// // get the index named "movies" - /// let movies = client.get_index("movies").await.unwrap(); - /// assert_eq!(movies.as_ref(), "movies"); + /// // get the index named "get_index" + /// let index = client.get_index("get_index").await.unwrap(); + /// assert_eq!(index.as_ref(), "get_index"); /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// # }); /// ``` @@ -124,11 +124,11 @@ impl Client { /// # futures::executor::block_on(async move { /// // create the client /// let client = Client::new("http://localhost:7700", "masterKey"); - /// # let index = client.create_index("movies", None).await.unwrap().wait_for_completion(&client, None, None).await.unwrap().try_make_index(&client).unwrap(); + /// # let index = client.create_index("get_raw_index", None).await.unwrap().wait_for_completion(&client, None, None).await.unwrap().try_make_index(&client).unwrap(); /// - /// // get the index named "movies" - /// let movies = client.get_raw_index("movies").await.unwrap(); - /// assert_eq!(movies.uid, "movies"); + /// // get the index named "get_raw_index" + /// let raw_index = client.get_raw_index("get_raw_index").await.unwrap(); + /// assert_eq!(raw_index.uid, "get_raw_index"); /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// # }); /// ``` @@ -158,7 +158,7 @@ impl Client { /// let client = Client::new("http://localhost:7700", "masterKey"); /// /// // Create a new index called movies and access it - /// let task = client.create_index("movies", None).await.unwrap(); + /// let task = client.create_index("create_index", None).await.unwrap(); /// /// // Wait for the task to complete /// let task = task.wait_for_completion(&client, None, None).await.unwrap(); @@ -166,7 +166,7 @@ impl Client { /// // Try to get the inner index if the task succeeded /// let index = task.try_make_index(&client).unwrap(); /// - /// assert_eq!(index.as_ref(), "movies"); + /// assert_eq!(index.as_ref(), "create_index"); /// # index.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap(); /// # }); /// ``` @@ -634,6 +634,7 @@ mod tests { key::{Action, KeyBuilder}, }; use meilisearch_test_macro::meilisearch_test; + use time::OffsetDateTime; #[meilisearch_test] async fn test_get_keys(client: Client) { @@ -705,15 +706,20 @@ mod tests { #[meilisearch_test] async fn test_create_key(client: Client, description: String) { + let expires_at = OffsetDateTime::now_utc() + time::Duration::HOUR; let mut key = KeyBuilder::new(description.clone()); key.with_action(Action::DocumentsAdd) - .with_expires_at("3022-02-09T01:32:46Z") + .with_expires_at(expires_at.clone()) .with_index("*"); let key = client.create_key(key).await.unwrap(); assert_eq!(key.actions, vec![Action::DocumentsAdd]); assert_eq!(key.description, description); - assert_eq!(key.expires_at, Some("3022-02-09T01:32:46Z".to_string())); + // We can't compare the two timestamp directly because of some nanoseconds imprecision with the floats + assert_eq!( + key.expires_at.unwrap().unix_timestamp(), + expires_at.unix_timestamp() + ); assert_eq!(key.indexes, vec!["*".to_string()]); let keys = client.get_keys().await.unwrap(); @@ -722,9 +728,10 @@ mod tests { assert_eq!(remote_key.actions, vec![Action::DocumentsAdd]); assert_eq!(remote_key.description, description); + // We can't compare the two timestamp directly because of some nanoseconds imprecision with the floats assert_eq!( - remote_key.expires_at, - Some("3022-02-09T01:32:46Z".to_string()) + remote_key.expires_at.unwrap().unix_timestamp(), + expires_at.unix_timestamp() ); assert_eq!(remote_key.indexes, vec!["*".to_string()]); @@ -749,20 +756,6 @@ mod tests { )); */ - // ==> Invalid expires_at - let mut key = KeyBuilder::new(&description); - key.with_expires_at("That’s totally not a date"); - let error = client.create_key(key).await.unwrap_err(); - - assert!(matches!( - error, - Error::Meilisearch(MeilisearchError { - error_code: ErrorCode::InvalidApiKeyExpiresAt, - error_type: ErrorType::InvalidRequest, - .. - }) - )); - // ==> executing the action without enough right let no_right_key = KeyBuilder::new(&description); let no_right_key = client.create_key(no_right_key).await.unwrap(); @@ -789,18 +782,23 @@ mod tests { #[meilisearch_test] async fn test_update_key(client: Client, description: String) { + let expires_at = OffsetDateTime::now_utc() + time::Duration::HOUR; let key = KeyBuilder::new(description.clone()); let mut key = client.create_key(key).await.unwrap(); key.actions = vec![Action::DocumentsAdd]; - key.expires_at = Some("3022-02-09T01:32:46Z".to_string()); + key.expires_at = Some(expires_at); key.indexes = vec!["*".to_string()]; let key = client.update_key(key).await.unwrap(); assert_eq!(key.actions, vec![Action::DocumentsAdd]); assert_eq!(key.description, description); - assert_eq!(key.expires_at, Some("3022-02-09T01:32:46Z".to_string())); + // We can't compare the two timestamp directly because of some nanoseconds imprecision with the floats + assert_eq!( + key.expires_at.unwrap().unix_timestamp(), + expires_at.unix_timestamp() + ); assert_eq!(key.indexes, vec!["*".to_string()]); let keys = client.get_keys().await.unwrap(); @@ -809,9 +807,10 @@ mod tests { assert_eq!(remote_key.actions, vec![Action::DocumentsAdd]); assert_eq!(remote_key.description, description); + // We can't compare the two timestamp directly because of some nanoseconds imprecision with the floats assert_eq!( - remote_key.expires_at, - Some("3022-02-09T01:32:46Z".to_string()) + remote_key.expires_at.unwrap().unix_timestamp(), + expires_at.unix_timestamp() ); assert_eq!(remote_key.indexes, vec!["*".to_string()]); @@ -821,7 +820,7 @@ mod tests { #[meilisearch_test] async fn test_error_update_key(mut client: Client, description: String) { let key = KeyBuilder::new(description.clone()); - let mut key = client.create_key(key).await.unwrap(); + let key = client.create_key(key).await.unwrap(); // ==> Invalid index name /* TODO: uncomment once meilisearch fix this bug: https://github.com/meilisearch/meilisearch/issues/2158 @@ -838,20 +837,6 @@ mod tests { )); */ - // ==> Invalid expires_at - key.expires_at = Some("That’s totally not a date".to_string()); - let error = client.update_key(&key).await.unwrap_err(); - - assert!(matches!( - error, - Error::Meilisearch(MeilisearchError { - error_code: ErrorCode::InvalidApiKeyExpiresAt, - error_type: ErrorType::InvalidRequest, - .. - }) - )); - key.expires_at = None; - // ==> executing the action without enough right let no_right_key = KeyBuilder::new(&description); let no_right_key = client.create_key(no_right_key).await.unwrap(); diff --git a/src/indexes.rs b/src/indexes.rs index ab1bcd20..e880144c 100644 --- a/src/indexes.rs +++ b/src/indexes.rs @@ -2,14 +2,17 @@ use crate::{client::Client, document::*, errors::Error, request::*, search::*, t use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::json; use std::{collections::HashMap, fmt::Display, time::Duration}; +use time::OffsetDateTime; #[derive(Deserialize, Debug)] #[allow(non_snake_case)] pub struct JsonIndex { pub uid: String, pub primaryKey: Option, - pub createdAt: String, // TODO: use a chrono date - pub updatedAt: String, // TODO: use a chrono date + #[serde(with = "time::serde::rfc3339")] + pub createdAt: OffsetDateTime, + #[serde(with = "time::serde::rfc3339")] + pub updatedAt: OffsetDateTime, } impl JsonIndex { diff --git a/src/key.rs b/src/key.rs index 94dcd258..8b2b7cbd 100644 --- a/src/key.rs +++ b/src/key.rs @@ -1,4 +1,5 @@ use serde::{Deserialize, Serialize}; +use time::OffsetDateTime; use crate::{client::Client, errors::Error}; @@ -10,17 +11,17 @@ use crate::{client::Client, errors::Error}; pub struct Key { #[serde(skip_serializing_if = "Vec::is_empty")] pub actions: Vec, - #[serde(skip_serializing)] - pub created_at: String, // TODO: use a chrono date + #[serde(skip_serializing, with = "time::serde::rfc3339")] + pub created_at: OffsetDateTime, pub description: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub expires_at: Option, // TODO: use a chrono date + #[serde(with = "time::serde::rfc3339::option")] + pub expires_at: Option, #[serde(skip_serializing_if = "Vec::is_empty")] pub indexes: Vec, #[serde(skip_serializing)] pub key: String, - #[serde(skip_serializing)] - pub updated_at: String, // TODO: use a chrono date + #[serde(skip_serializing, with = "time::serde::rfc3339")] + pub updated_at: OffsetDateTime, } impl AsRef for Key { @@ -59,7 +60,8 @@ impl AsRef for Key { pub struct KeyBuilder { pub actions: Vec, pub description: String, - pub expires_at: Option, // TODO: use a chrono date + #[serde(with = "time::serde::rfc3339::option")] + pub expires_at: Option, pub indexes: Vec, } @@ -115,11 +117,13 @@ impl KeyBuilder { /// /// ``` /// # use meilisearch_sdk::{key::KeyBuilder}; + /// use time::{OffsetDateTime, Duration}; /// let mut builder = KeyBuilder::new("My little lovely test key"); - /// builder.with_expires_at("3022-02-09T10:35:58Z".to_string()); + /// // create a key that expires in two weeks from now + /// builder.with_expires_at(OffsetDateTime::now_utc() + Duration::WEEK * 2); /// ``` - pub fn with_expires_at(&mut self, expires_at: impl AsRef) -> &mut Self { - self.expires_at = Some(expires_at.as_ref().to_string()); + pub fn with_expires_at(&mut self, expires_at: OffsetDateTime) -> &mut Self { + self.expires_at = Some(expires_at); self } diff --git a/src/tasks.rs b/src/tasks.rs index f399ea3f..246c8b5e 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -1,5 +1,6 @@ -use serde::Deserialize; +use serde::{Deserialize, Deserializer}; use std::time::Duration; +use time::OffsetDateTime; use crate::{ client::Client, errors::Error, errors::MeilisearchError, indexes::Index, settings::Settings, @@ -57,13 +58,26 @@ impl AsRef for FailedTask { } } +fn deserialize_duration<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let s = String::deserialize(deserializer)?; + let iso_duration = iso8601_duration::Duration::parse(&s).map_err(serde::de::Error::custom)?; + Ok(iso_duration.to_std()) +} + #[derive(Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct ProcessedTask { - pub duration: String, // TODO deserialize to Duration - pub enqueued_at: String, // TODO deserialize to datetime - pub started_at: String, // TODO deserialize to datetime - pub finished_at: String, // TODO deserialize to datetime + #[serde(deserialize_with = "deserialize_duration")] + pub duration: Duration, + #[serde(with = "time::serde::rfc3339")] + pub enqueued_at: OffsetDateTime, + #[serde(with = "time::serde::rfc3339")] + pub started_at: OffsetDateTime, + #[serde(with = "time::serde::rfc3339")] + pub finished_at: OffsetDateTime, pub index_uid: String, #[serde(flatten)] pub update_type: TaskType, @@ -79,7 +93,8 @@ impl AsRef for ProcessedTask { #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] pub struct EnqueuedTask { - pub enqueued_at: String, // TODO deserialize to datetime + #[serde(with = "time::serde::rfc3339")] + pub enqueued_at: OffsetDateTime, pub index_uid: String, #[serde(flatten)] pub update_type: TaskType, @@ -392,7 +407,13 @@ mod test { } #[test] - fn test_deserialize_enqueued_task() { + fn test_deserialize_task() { + let datetime = OffsetDateTime::parse( + "2022-02-03T13:02:38.369634Z", + &::time::format_description::well_known::Rfc3339, + ) + .unwrap(); + let task: Task = serde_json::from_str( r#" { @@ -415,7 +436,7 @@ mod test { uid: 12, } } - if enqueued_at == "2022-02-03T13:02:38.369634Z" && index_uid == "mieli")); + if enqueued_at == datetime && index_uid == "mieli")); let task: Task = serde_json::from_str( r#" @@ -482,9 +503,11 @@ mod test { }) }, uid: 14, + duration, .. } } + if duration == Duration::from_secs_f32(10.848957061) )); }