#![cfg(feature = "config-derive")] use config_it::config::storage::GroupFindError; #[test] fn concepts() { use config_it::Template; // Every config template must implement `Clone` trait. #[derive(Template, Clone)] struct Profile { /// Doc comment will be used as description for the property. This will be included in /// the config property's metadata. #[config] pub name: String, #[config(max = 250)] pub age: u32, #[config(default = "unspecified", one_of = ["left", "right", "up", "down"])] pub position: String, } // Before doing anything with your config template, you should create storage instance. // Storage is the primary actor for centralized configuration management. let storage = config_it::create_storage(); // Assume that you have a config file with following content: // (Note that all 'group's are prefixed with '~'(this is configurable) to be distinguished // from properties) let content = serde_json::json!({ "~profile": { "~scorch": { "name": "Scorch", "age": 25, "position": "left" }, "~john": { "name": "John", "age": 30, "position": "invalid-value-here" } } }); let archive = serde_json::from_value(content).unwrap(); // You can import config file into storage. storage.import(archive); // A `Template` can be instantiated as `Group` type. let mut scorch = storage.create::(["profile", "scorch"]).unwrap(); let mut john = storage.create::(["profile", "john"]).unwrap(); // Before calling 'update' method on group, every property remain in default. assert_eq!(scorch.name, ""); assert_eq!(scorch.age, 0); assert_eq!(scorch.position, "unspecified"); // Calling 'update' method will update the property to the value in archive. assert!(scorch.update()); assert!(john.update()); // You can check dirty flag of individual property. assert!(scorch.consume_update(&scorch.name)); assert!(!scorch.consume_update(&scorch.name)); // Now the property values are updated. assert_eq!(scorch.name, "Scorch"); assert_eq!(scorch.age, 25); assert_eq!(scorch.position, "left"); assert_eq!(john.name, "John"); assert_eq!(john.age, 30); assert_eq!(john.position, "unspecified", "invalid value is ignored"); } #[test] fn serde_struct() { #[derive(config_it::Template, Clone)] struct Outer { #[config(default_expr = "Inner{name:Default::default(),age:0}")] inner: Inner, } #[derive(serde::Serialize, serde::Deserialize, Clone)] struct Inner { name: String, age: u32, } let storage = config_it::create_storage(); let mut outer = storage.create::(["outer"]).unwrap(); "John".clone_into(&mut outer.inner.name); outer.inner.age = 30; outer.commit_elem(&outer.inner, false); let archive = storage.exporter().collect(); let dump = serde_json::to_string(&archive).unwrap(); assert_eq!(dump, r#"{"~outer":{"inner":{"age":30,"name":"John"}}}"#); } #[test] fn find_or() { #[derive(config_it::Template, Clone)] struct TemplateA { #[config(default = "unspecified")] name: String, } #[derive(config_it::Template, Clone)] struct TemplateB { #[config(default = "unspecified")] name_b: String, } let storage = config_it::create_storage(); let path = || ["a"]; assert!(matches!(storage.find::(path()), Err(GroupFindError::PathNotFound))); let mut a = storage.create::(path()).unwrap(); assert!(a.update()); assert!(a.consume_update(&a.name)); assert!(a.name == "unspecified"); a.name.clear(); a.name.push_str("John"); a.commit_elem(&a.name, false); assert!(storage.create::(path()).is_err()); let mut a2 = storage.find_or_create::(path()).unwrap(); assert!(a2.update()); assert!(a2.consume_update(&a2.name)); assert!(a2.name == "John"); assert!(matches!(storage.find::(path()), Err(GroupFindError::MismatchedTypeID))); drop(storage); }