diff --git a/meilidb-data/src/database/index/common_index.rs b/meilidb-data/src/database/index/common_index.rs index d102bb3bb..04fe39bd3 100644 --- a/meilidb-data/src/database/index/common_index.rs +++ b/meilidb-data/src/database/index/common_index.rs @@ -1,3 +1,4 @@ +use std::ops::Deref; use serde::de::DeserializeOwned; use serde::Serialize; use super::Error; @@ -6,6 +7,14 @@ use std::marker::PhantomData; #[derive(Clone)] pub struct CommonIndex(pub crate::CfTree); +impl Deref for CommonIndex { + type Target = crate::CfTree; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + impl CommonIndex { pub fn get(&self, key: K) -> Result, Error> where T: DeserializeOwned, diff --git a/meilidb-data/src/database/index/mod.rs b/meilidb-data/src/database/index/mod.rs index 9ee2e6804..0194b5d51 100644 --- a/meilidb-data/src/database/index/mod.rs +++ b/meilidb-data/src/database/index/mod.rs @@ -61,17 +61,24 @@ pub enum UpdateType { #[derive(Clone, Serialize, Deserialize)] pub struct DetailedDuration { - main: Duration, + pub main: Duration, } #[derive(Clone, Serialize, Deserialize)] -pub struct UpdateStatus { +pub struct UpdateResult { pub update_id: u64, pub update_type: UpdateType, pub result: Result<(), String>, pub detailed_duration: DetailedDuration, } +#[derive(Clone, Serialize, Deserialize)] +pub enum UpdateStatus { + Enqueued, + Processed(UpdateResult), + Unknown, +} + fn spawn_update_system(index: Index, subscription: Receiver<()>) -> thread::JoinHandle<()> { thread::spawn(move || { let mut subscription = subscription.into_iter(); @@ -115,7 +122,7 @@ fn spawn_update_system(index: Index, subscription: Receiver<()>) -> thread::Join }; let detailed_duration = DetailedDuration { main: duration }; - let status = UpdateStatus { + let status = UpdateResult { update_id, update_type, result: result.map_err(|e| e.to_string()), @@ -180,7 +187,7 @@ pub struct Index { updates_id: Arc, updates_index: crate::CfTree, updates_results_index: crate::CfTree, - update_callback: Arc>>, + update_callback: Arc>>, } pub(crate) struct Cache { @@ -266,7 +273,7 @@ impl Index { } pub fn set_update_callback(&self, callback: F) - where F: Fn(UpdateStatus) + Send + Sync + 'static + where F: Fn(UpdateResult) + Send + Sync + 'static { self.update_callback.store(Some(Arc::new(Box::new(callback)))); } @@ -316,6 +323,18 @@ impl Index { self.cache.load().schema.clone() } + pub fn ranked_map(&self) -> RankedMap { + self.cache.load().ranked_map.clone() + } + + pub fn synonyms_index(&self) -> SynonymsIndex { + self.synonyms_index.clone() + } + + pub fn synonyms_set(&self) -> Arc { + self.cache.load().synonyms.clone() + } + pub fn custom_settings(&self) -> CustomSettingsIndex { self.custom_settings_index.clone() } @@ -343,36 +362,35 @@ impl Index { pub fn update_status( &self, update_id: u64, - ) -> Result, Error> + ) -> Result { let update_id = update_id.to_be_bytes(); match self.updates_results_index.get(update_id)? { Some(value) => { let value = bincode::deserialize(&value)?; - Ok(Some(value)) + Ok(UpdateStatus::Processed(value)) }, - None => Ok(None), + None => { + match self.updates_index.get(update_id)? { + Some(_) => Ok(UpdateStatus::Enqueued), + None => Ok(UpdateStatus::Unknown), + } + } } } pub fn update_status_blocking( &self, update_id: u64, - ) -> Result + ) -> Result { - // if we find the update result return it now - if let Some(result) = self.update_status(update_id)? { - return Ok(result) - } - loop { - if self.updates_results_index.get(&update_id.to_be_bytes())?.is_some() { break } + if let Some(value) = self.updates_results_index.get(&update_id.to_be_bytes())? { + let value = bincode::deserialize(&value)?; + return Ok(value) + } std::thread::sleep(Duration::from_millis(300)); } - - // the thread has been unblocked, it means that the update result - // has been inserted in the tree, retrieve it - Ok(self.update_status(update_id)?.unwrap()) } pub fn documents_ids(&self) -> Result { diff --git a/meilidb-data/src/database/mod.rs b/meilidb-data/src/database/mod.rs index b1391ac49..c4ff406a5 100644 --- a/meilidb-data/src/database/mod.rs +++ b/meilidb-data/src/database/mod.rs @@ -12,7 +12,11 @@ mod update; use crate::CfTree; pub use self::error::Error; -pub use self::index::{Index, CustomSettingsIndex, CommonIndex, RankingOrdering, StopWords, RankingOrder, DistinctField, RankingRules}; +pub use self::index::{ + Index, CustomSettingsIndex, CommonIndex, RankingOrdering, + StopWords, RankingOrder, DistinctField, RankingRules, + UpdateType, DetailedDuration, UpdateResult, UpdateStatus +}; pub use self::update::DocumentsAddition; pub use self::update::DocumentsDeletion; diff --git a/meilidb-data/src/lib.rs b/meilidb-data/src/lib.rs index 168311755..8d313c855 100644 --- a/meilidb-data/src/lib.rs +++ b/meilidb-data/src/lib.rs @@ -7,7 +7,12 @@ mod ranked_map; mod serde; pub use self::cf_tree::{CfTree, CfIter}; -pub use self::database::{Database, Index, CustomSettingsIndex, RankingOrdering, StopWords, RankingOrder, DistinctField, RankingRules}; +pub use self::database::{ + Database, Index, CustomSettingsIndex, RankingOrdering, + StopWords, RankingOrder, DistinctField, RankingRules, + UpdateType, DetailedDuration, UpdateResult, UpdateStatus, + Error, +}; pub use self::number::Number; pub use self::ranked_map::RankedMap; pub use self::serde::{compute_document_id, extract_document_id, value_to_string}; diff --git a/meilidb-data/tests/common.rs b/meilidb-data/tests/common.rs new file mode 100644 index 000000000..8d0aadda7 --- /dev/null +++ b/meilidb-data/tests/common.rs @@ -0,0 +1,15 @@ +use meilidb_data::{Database}; +use meilidb_data::Index; +use meilidb_schema::{SchemaBuilder, DISPLAYED, INDEXED}; + +pub fn simple_index() -> Index { + let tmp_dir = tempfile::tempdir().unwrap(); + let database = Database::open(&tmp_dir).unwrap(); + + let mut builder = SchemaBuilder::with_identifier("objectId"); + builder.new_attribute("objectId", DISPLAYED | INDEXED); + builder.new_attribute("title", DISPLAYED | INDEXED); + let schema = builder.build(); + + database.create_index("hello", schema).unwrap() +} diff --git a/meilidb-data/tests/custom_settings_index.rs b/meilidb-data/tests/custom_settings_index.rs new file mode 100644 index 000000000..9363be9d8 --- /dev/null +++ b/meilidb-data/tests/custom_settings_index.rs @@ -0,0 +1,43 @@ +#[macro_use] extern crate maplit; + +mod common; + +use big_s::S; +use meilidb_data::RankingOrdering; + +#[test] +fn stop_words() { + let index = common::simple_index(); + let stop_words = hashset!{ S("le"), S("la"), S("les"), }; + index.custom_settings().set_stop_words(&stop_words).unwrap(); + let ret_stop_words = index.custom_settings().get_stop_words().unwrap().unwrap(); + assert_eq!(ret_stop_words, stop_words); +} + +#[test] +fn ranking_order() { + let index = common::simple_index(); + let ranking_order = vec![S("SumOfTypos"), S("NumberOfWords"), S("WordsProximity"), S("SumOfWordsAttribute"), S("SumOfWordsPosition"), S("Exact"), S("DocumentId")]; + index.custom_settings().set_ranking_order(&ranking_order).unwrap(); + let ret_ranking_orderer = index.custom_settings().get_ranking_order().unwrap().unwrap(); + assert_eq!(ret_ranking_orderer, ranking_order); +} + +#[test] +fn distinct_field() { + let index = common::simple_index(); + let distinct_field = S("title"); + index.custom_settings().set_distinct_field(&distinct_field).unwrap(); + let ret_distinct_field = index.custom_settings().get_distinct_field().unwrap().unwrap(); + assert_eq!(ret_distinct_field, distinct_field); +} + +#[test] +fn ranking_rules() { + let index = common::simple_index(); + let ranking_rules = hashmap!{ S("objectId") => RankingOrdering::Asc }; + index.custom_settings().set_ranking_rules(&ranking_rules).unwrap(); + let ret_ranking_rules = index.custom_settings().get_ranking_rules().unwrap().unwrap(); + assert_eq!(ret_ranking_rules, ranking_rules); +} + diff --git a/meilidb-data/tests/database.rs b/meilidb-data/tests/database.rs new file mode 100644 index 000000000..b716b2ea7 --- /dev/null +++ b/meilidb-data/tests/database.rs @@ -0,0 +1,67 @@ +#[macro_use] extern crate maplit; + +mod common; + +use std::sync::atomic::{AtomicBool, Ordering::Relaxed}; +use std::sync::Arc; + +use big_s::S; +use serde_json::json; + +#[test] +fn database_stats() { + let index = common::simple_index(); + let as_been_updated = Arc::new(AtomicBool::new(false)); + + let as_been_updated_clone = as_been_updated.clone(); + index.set_update_callback(move |_| as_been_updated_clone.store(true, Relaxed)); + + let doc1 = json!({ "objectId": 123, "title": "hello" }); + + let mut addition = index.documents_addition(); + addition.update_document(&doc1); + let update_id = addition.finalize().unwrap(); + let status = index.update_status_blocking(update_id).unwrap(); + assert!(as_been_updated.swap(false, Relaxed)); + assert!(status.result.is_ok()); + let stats = index.stats().unwrap(); + let repartition = hashmap!{ + S("objectId") => 1u64, + S("title") => 1u64, + }; + assert_eq!(stats.number_of_documents, 1); + assert_eq!(stats.documents_fields_repartition, repartition); + + let doc2 = json!({ "objectId": 456, "title": "world" }); + + let mut addition = index.documents_addition(); + addition.update_document(&doc2); + let update_id = addition.finalize().unwrap(); + let status = index.update_status_blocking(update_id).unwrap(); + assert!(as_been_updated.swap(false, Relaxed)); + assert!(status.result.is_ok()); + let stats = index.stats().unwrap(); + let repartition = hashmap!{ + S("objectId") => 2u64, + S("title") => 2u64, + }; + assert_eq!(stats.number_of_documents, 2); + assert_eq!(stats.documents_fields_repartition, repartition); + + + let doc3 = json!({ "objectId": 789 }); + + let mut addition = index.documents_addition(); + addition.update_document(&doc3); + let update_id = addition.finalize().unwrap(); + let status = index.update_status_blocking(update_id).unwrap(); + assert!(as_been_updated.swap(false, Relaxed)); + assert!(status.result.is_ok()); + let stats = index.stats().unwrap(); + let repartition = hashmap!{ + S("objectId") => 3u64, + S("title") => 2u64, + }; + assert_eq!(stats.number_of_documents, 3); + assert_eq!(stats.documents_fields_repartition, repartition); +} diff --git a/meilidb-data/tests/index.rs b/meilidb-data/tests/index.rs new file mode 100644 index 000000000..4d7a7658b --- /dev/null +++ b/meilidb-data/tests/index.rs @@ -0,0 +1,99 @@ +mod common; + +use std::sync::atomic::{AtomicBool, Ordering::Relaxed}; +use std::sync::Arc; + +use serde_json::json; + +#[test] +fn insert_delete_document() { + let index = common::simple_index(); + let as_been_updated = Arc::new(AtomicBool::new(false)); + + let as_been_updated_clone = as_been_updated.clone(); + index.set_update_callback(move |_| as_been_updated_clone.store(true, Relaxed)); + + let doc1 = json!({ "objectId": 123, "title": "hello" }); + + let mut addition = index.documents_addition(); + addition.update_document(&doc1); + let update_id = addition.finalize().unwrap(); + let status = index.update_status_blocking(update_id).unwrap(); + assert!(as_been_updated.swap(false, Relaxed)); + assert!(status.result.is_ok()); + assert_eq!(index.number_of_documents(), 1); + + let docs = index.query_builder().query("hello", 0..10).unwrap(); + assert_eq!(docs.len(), 1); + assert_eq!(index.document(None, docs[0].id).unwrap().as_ref(), Some(&doc1)); + + let mut deletion = index.documents_deletion(); + deletion.delete_document(&doc1).unwrap(); + let update_id = deletion.finalize().unwrap(); + let status = index.update_status_blocking(update_id).unwrap(); + assert!(as_been_updated.swap(false, Relaxed)); + assert!(status.result.is_ok()); + assert_eq!(index.number_of_documents(), 0); + + let docs = index.query_builder().query("hello", 0..10).unwrap(); + assert_eq!(docs.len(), 0); +} + +#[test] +fn replace_document() { + let index = common::simple_index(); + let as_been_updated = Arc::new(AtomicBool::new(false)); + + let as_been_updated_clone = as_been_updated.clone(); + index.set_update_callback(move |_| as_been_updated_clone.store(true, Relaxed)); + + let doc1 = json!({ "objectId": 123, "title": "hello" }); + let doc2 = json!({ "objectId": 123, "title": "coucou" }); + + let mut addition = index.documents_addition(); + addition.update_document(&doc1); + let update_id = addition.finalize().unwrap(); + let status = index.update_status_blocking(update_id).unwrap(); + assert!(as_been_updated.swap(false, Relaxed)); + assert!(status.result.is_ok()); + assert_eq!(index.number_of_documents(), 1); + + let docs = index.query_builder().query("hello", 0..10).unwrap(); + assert_eq!(docs.len(), 1); + assert_eq!(index.document(None, docs[0].id).unwrap().as_ref(), Some(&doc1)); + + let mut addition = index.documents_addition(); + addition.update_document(&doc2); + let update_id = addition.finalize().unwrap(); + let status = index.update_status_blocking(update_id).unwrap(); + assert!(as_been_updated.swap(false, Relaxed)); + assert!(status.result.is_ok()); + assert_eq!(index.number_of_documents(), 1); + + let docs = index.query_builder().query("hello", 0..10).unwrap(); + assert_eq!(docs.len(), 0); + + let docs = index.query_builder().query("coucou", 0..10).unwrap(); + assert_eq!(docs.len(), 1); + assert_eq!(index.document(None, docs[0].id).unwrap().as_ref(), Some(&doc2)); +} + +#[test] +fn documents_ids() { + let index = common::simple_index(); + + let doc1 = json!({ "objectId": 123, "title": "hello" }); + let doc2 = json!({ "objectId": 456, "title": "world" }); + let doc3 = json!({ "objectId": 789 }); + + let mut addition = index.documents_addition(); + addition.update_document(&doc1); + addition.update_document(&doc2); + addition.update_document(&doc3); + let update_id = addition.finalize().unwrap(); + let status = index.update_status_blocking(update_id).unwrap(); + assert!(status.result.is_ok()); + + let documents_ids_count = index.documents_ids().unwrap().count(); + assert_eq!(documents_ids_count, 3); +} diff --git a/meilidb-data/tests/updates.rs b/meilidb-data/tests/updates.rs deleted file mode 100644 index d106769cb..000000000 --- a/meilidb-data/tests/updates.rs +++ /dev/null @@ -1,215 +0,0 @@ -#[macro_use] extern crate maplit; - -use std::sync::atomic::{AtomicBool, Ordering::Relaxed}; -use std::sync::Arc; - -use big_s::S; -use serde_json::json; -use meilidb_data::{Database, RankingOrdering}; -use meilidb_schema::{Schema, SchemaBuilder, DISPLAYED, INDEXED}; - -fn simple_schema() -> Schema { - let mut builder = SchemaBuilder::with_identifier("objectId"); - builder.new_attribute("objectId", DISPLAYED | INDEXED); - builder.new_attribute("title", DISPLAYED | INDEXED); - builder.build() -} - -#[test] -fn insert_delete_document() { - let tmp_dir = tempfile::tempdir().unwrap(); - let database = Database::open(&tmp_dir).unwrap(); - - let as_been_updated = Arc::new(AtomicBool::new(false)); - - let schema = simple_schema(); - let index = database.create_index("hello", schema).unwrap(); - - let as_been_updated_clone = as_been_updated.clone(); - index.set_update_callback(move |_| as_been_updated_clone.store(true, Relaxed)); - - let doc1 = json!({ "objectId": 123, "title": "hello" }); - - let mut addition = index.documents_addition(); - addition.update_document(&doc1); - let update_id = addition.finalize().unwrap(); - let status = index.update_status_blocking(update_id).unwrap(); - assert!(as_been_updated.swap(false, Relaxed)); - assert!(status.result.is_ok()); - assert_eq!(index.number_of_documents(), 1); - - let docs = index.query_builder().query("hello", 0..10).unwrap(); - assert_eq!(docs.len(), 1); - assert_eq!(index.document(None, docs[0].id).unwrap().as_ref(), Some(&doc1)); - - let mut deletion = index.documents_deletion(); - deletion.delete_document(&doc1).unwrap(); - let update_id = deletion.finalize().unwrap(); - let status = index.update_status_blocking(update_id).unwrap(); - assert!(as_been_updated.swap(false, Relaxed)); - assert!(status.result.is_ok()); - assert_eq!(index.number_of_documents(), 0); - - let docs = index.query_builder().query("hello", 0..10).unwrap(); - assert_eq!(docs.len(), 0); -} - -#[test] -fn replace_document() { - let tmp_dir = tempfile::tempdir().unwrap(); - let database = Database::open(&tmp_dir).unwrap(); - - let as_been_updated = Arc::new(AtomicBool::new(false)); - - let schema = simple_schema(); - let index = database.create_index("hello", schema).unwrap(); - - let as_been_updated_clone = as_been_updated.clone(); - index.set_update_callback(move |_| as_been_updated_clone.store(true, Relaxed)); - - let doc1 = json!({ "objectId": 123, "title": "hello" }); - let doc2 = json!({ "objectId": 123, "title": "coucou" }); - - let mut addition = index.documents_addition(); - addition.update_document(&doc1); - let update_id = addition.finalize().unwrap(); - let status = index.update_status_blocking(update_id).unwrap(); - assert!(as_been_updated.swap(false, Relaxed)); - assert!(status.result.is_ok()); - assert_eq!(index.number_of_documents(), 1); - - let docs = index.query_builder().query("hello", 0..10).unwrap(); - assert_eq!(docs.len(), 1); - assert_eq!(index.document(None, docs[0].id).unwrap().as_ref(), Some(&doc1)); - - let mut addition = index.documents_addition(); - addition.update_document(&doc2); - let update_id = addition.finalize().unwrap(); - let status = index.update_status_blocking(update_id).unwrap(); - assert!(as_been_updated.swap(false, Relaxed)); - assert!(status.result.is_ok()); - assert_eq!(index.number_of_documents(), 1); - - let docs = index.query_builder().query("hello", 0..10).unwrap(); - assert_eq!(docs.len(), 0); - - let docs = index.query_builder().query("coucou", 0..10).unwrap(); - assert_eq!(docs.len(), 1); - assert_eq!(index.document(None, docs[0].id).unwrap().as_ref(), Some(&doc2)); -} - -#[test] -fn database_stats() { - let tmp_dir = tempfile::tempdir().unwrap(); - let database = Database::open(&tmp_dir).unwrap(); - - let as_been_updated = Arc::new(AtomicBool::new(false)); - - let schema = simple_schema(); - let index = database.create_index("hello", schema).unwrap(); - - let as_been_updated_clone = as_been_updated.clone(); - index.set_update_callback(move |_| as_been_updated_clone.store(true, Relaxed)); - - let doc1 = json!({ "objectId": 123, "title": "hello" }); - - let mut addition = index.documents_addition(); - addition.update_document(&doc1); - let update_id = addition.finalize().unwrap(); - let status = index.update_status_blocking(update_id).unwrap(); - assert!(as_been_updated.swap(false, Relaxed)); - assert!(status.result.is_ok()); - let stats = index.stats().unwrap(); - let repartition = hashmap!{ - S("objectId") => 1u64, - S("title") => 1u64, - }; - assert_eq!(stats.number_of_documents, 1); - assert_eq!(stats.documents_fields_repartition, repartition); - - let doc2 = json!({ "objectId": 456, "title": "world" }); - - let mut addition = index.documents_addition(); - addition.update_document(&doc2); - let update_id = addition.finalize().unwrap(); - let status = index.update_status_blocking(update_id).unwrap(); - assert!(as_been_updated.swap(false, Relaxed)); - assert!(status.result.is_ok()); - let stats = index.stats().unwrap(); - let repartition = hashmap!{ - S("objectId") => 2u64, - S("title") => 2u64, - }; - assert_eq!(stats.number_of_documents, 2); - assert_eq!(stats.documents_fields_repartition, repartition); - - - let doc3 = json!({ "objectId": 789 }); - - let mut addition = index.documents_addition(); - addition.update_document(&doc3); - let update_id = addition.finalize().unwrap(); - let status = index.update_status_blocking(update_id).unwrap(); - assert!(as_been_updated.swap(false, Relaxed)); - assert!(status.result.is_ok()); - let stats = index.stats().unwrap(); - let repartition = hashmap!{ - S("objectId") => 3u64, - S("title") => 2u64, - }; - assert_eq!(stats.number_of_documents, 3); - assert_eq!(stats.documents_fields_repartition, repartition); -} - -#[test] -fn custom_settings() { - let tmp_dir = tempfile::tempdir().unwrap(); - let database = Database::open(&tmp_dir).unwrap(); - - let schema = simple_schema(); - let index = database.create_index("hello", schema).unwrap(); - - let stop_words = hashset!{ S("le"), S("la"), S("les"), }; - let ranking_order = vec![S("SumOfTypos"), S("NumberOfWords"), S("WordsProximity"), S("SumOfWordsAttribute"), S("SumOfWordsPosition"), S("Exact"), S("DocumentId")]; - let distinct_field = S("title"); - let ranking_rules = hashmap!{ S("objectId") => RankingOrdering::Asc }; - - index.custom_settings().set_stop_words(&stop_words).unwrap(); - index.custom_settings().set_ranking_order(&ranking_order).unwrap(); - index.custom_settings().set_distinct_field(&distinct_field).unwrap(); - index.custom_settings().set_ranking_rules(&ranking_rules).unwrap(); - - let ret_stop_words = index.custom_settings().get_stop_words().unwrap().unwrap(); - let ret_ranking_orderer = index.custom_settings().get_ranking_order().unwrap().unwrap(); - let ret_distinct_field = index.custom_settings().get_distinct_field().unwrap().unwrap(); - let ret_ranking_rules = index.custom_settings().get_ranking_rules().unwrap().unwrap(); - - assert_eq!(ret_stop_words, stop_words); - assert_eq!(ret_ranking_orderer, ranking_order); - assert_eq!(ret_distinct_field, distinct_field); - assert_eq!(ret_ranking_rules, ranking_rules); -} - -#[test] -fn documents_ids() { - let tmp_dir = tempfile::tempdir().unwrap(); - let database = Database::open(&tmp_dir).unwrap(); - - let schema = simple_schema(); - let index = database.create_index("hello", schema).unwrap(); - - let doc1 = json!({ "objectId": 123, "title": "hello" }); - let doc2 = json!({ "objectId": 456, "title": "world" }); - let doc3 = json!({ "objectId": 789 }); - - let mut addition = index.documents_addition(); - addition.update_document(&doc1); - addition.update_document(&doc2); - addition.update_document(&doc3); - let update_id = addition.finalize().unwrap(); - let status = index.update_status_blocking(update_id).unwrap(); - assert!(status.result.is_ok()); - - let documents_ids_count = index.documents_ids().unwrap().count(); - assert_eq!(documents_ids_count, 3); -} diff --git a/meilidb-schema/src/lib.rs b/meilidb-schema/src/lib.rs index 4655e2f07..5109b33e1 100644 --- a/meilidb-schema/src/lib.rs +++ b/meilidb-schema/src/lib.rs @@ -1,6 +1,4 @@ use std::collections::{HashMap, BTreeMap}; -use std::io::{Read, Write}; -use std::error::Error; use std::{fmt, u16}; use std::ops::BitOr; use std::sync::Arc; @@ -15,13 +13,13 @@ pub const RANKED: SchemaProps = SchemaProps { displayed: false, indexed: fals #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct SchemaProps { #[serde(default)] - displayed: bool, + pub displayed: bool, #[serde(default)] - indexed: bool, + pub indexed: bool, #[serde(default)] - ranked: bool, + pub ranked: bool, } impl SchemaProps {