1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394
//!
//! A crate for asynchronous centralized configuration management.
//!
//! # Usage
//!
//! You can define 'Template' which defines set of grouped properties,
//! which should be updated at once. Only `config_it` decorated properties will be counted
//! as part of given group and will be managed.
//!
//! `Template` must implement `Clone` and `Default` to be treated as valid config
//! group data.
//!
//! You should create `Storage` to create config group instances(`Group<T:Template>`).
//! `Storage` is the primary actor for centralized configuration management. Config groups can
//! be instantiated based on storage instance.
//!
//! # Example usage
//!
//! ```
//! /// This is a 'Template' struct, which is minimal unit of
//! /// instantiation. Put required properties to configure
//! /// your program.
//! ///
//! /// All 'Template' classes must be 'Clone'able, and
//! /// 'Default'able.
//! ///
//! /// (Trying to finding way to remove 'Default' constraint.
//! /// However, 'Clone' will always be required.)
//! #[derive(config_it::Template, Clone, Default)]
//! struct MyConfig {
//! /// If you expose any field as config property, the
//! /// field must be marked with `config_it` attribute.
//! #[config_it]
//! string_field: String,
//!
//! /// You can also specify default value, or min/max
//! /// constraints for this field.
//! #[config_it(default = 3, min = 1, max = 5)]
//! int_field: i32,
//!
//! /// This field will be aliased as 'alias'.
//! ///
//! /// > **Warning** Don't use `~(tilde)` characters in
//! /// > alias name. In current implementation, `~` is
//! /// > used to indicate group object in archive
//! /// > representation during serialization.
//! #[config_it(alias = "alias")]
//! non_alias: f32,
//!
//! /// Only specified set of values are allowed for
//! /// this field, however, default field can be
//! /// excluded from this set.
//! #[config_it(default = "default", one_of("a", "b", "c"))]
//! one_of_field: String,
//!
//! /// Any 'serde' compatible type can be used as config field.
//! #[config_it]
//! c_string_type: Box<std::ffi::CStr>,
//!
//! /// This will find value from environment variable
//! /// `MY_ENV_VAR`. Currently, only values that can be
//! /// `TryParse`d from `str` are supported.
//! ///
//! /// Environment variables are imported when the
//! /// group is firstly instantiated.
//! /// i.e. call to `Storage::create_group`
//! #[config_it(env = "MY_ARRAY_VAR")]
//! env_var: i64,
//!
//! /// Complicated default value are represented as expression.
//! #[config_it(default_expr = "[1,2,3,4,5].into()")]
//! array_init: Vec<i32>,
//!
//! /// This field is not part of config_it system.
//! _not_part_of: (),
//!
//! /// This field won't be imported or exported from
//! /// archiving operation
//! #[config_it(no_import, no_export)]
//! no_imp_exp: Vec<f64>,
//!
//! /// `transient` flag is equivalent to `no_import` and
//! /// `no_export` flags.
//! #[config_it(transient)]
//! no_imp_exp_2: Vec<f64>,
//! }
//!
//! // USAGE ///////////////////////////////////////////////////////////////////////////////////////
//!
//! // 1. Storage
//! //
//! // Storage is basic and most important class to drive
//! // the whole config_it system. Before you can use any
//! // of the features, you must create a storage instance.
//! let (storage, driver_task) = config_it::create_storage();
//!
//! // `[config_it::create_storage]` returns a tuple of
//! // `(Storage, Task)`. `Storage` is the handle to the
//! // storage, and `Task` is the driver task that must
//! // be spawned to drive the storage operations(actor).
//! // You can spawn the task using any async runtime.
//! //
//! // Basically, config_it is designed to be used with
//! // async runtime, we're run this example under async
//! // environment.
//! let mut local = futures::executor::LocalPool::new();
//! let spawn = local.spawner();
//!
//! // Storage driver task must be running somewhere.
//! use futures::task::SpawnExt;
//! spawn.spawn(driver_task).unwrap();
//!
//! // before starting this, let's set environment variable to see if it works.
//! std::env::set_var("MY_ARRAY_VAR", "123");
//!
//! // Let's get into async
//! local.run_until(async {
//! // 2. Groups and Templates
//! //
//! // A group is an instance of a template. You can
//! // create multiple groups from a single template.
//! // Each group has its own set of properties, and
//! // can be configured independently.
//! //
//! // When instantiating a group, you must provide a
//! // path to the group. Path is a list of short string
//! // tokens, which is used to identify the group. You
//! // can use any string as path, but it's recommended
//! // to use a short string, which does not contain any
//! // special characters. (Since it usually encoded as a
//! // key of a key-value store of some kind of data
//! // serialization formats, such as JSON, YAML, etc.)
//! let path = &["path", "to", "my", "group"];
//! let mut group = storage.create_group::<MyConfig>(path).await.unwrap();
//!
//! // Note, duplicated path name is not allowed.
//! assert!(storage.create_group::<MyConfig>(path).await.is_err());
//!
//! // `update()` call to group, will check for asynchronously
//! // queued updates, and apply changes to the group instance.
//! // Since this is the first call to update,
//! //
//! // You can understand `update()` as clearing dirty flag.
//! assert!(group.update() == true);
//!
//! // After `update()`, as long as there's no new update,
//! // `update()` will return false.
//! assert!(group.update() == false);
//!
//! // Every individual properties has their own dirty flag.
//! assert!(true == group.check_elem_update(&group.array_init));
//! assert!(true == group.check_elem_update(&group.c_string_type));
//! assert!(true == group.check_elem_update(&group.env_var));
//! assert!(true == group.check_elem_update(&group.no_imp_exp));
//! assert!(true == group.check_elem_update(&group.no_imp_exp_2));
//! assert!(true == group.check_elem_update(&group.non_alias));
//! assert!(true == group.check_elem_update(&group.int_field));
//! assert!(true == group.check_elem_update(&group.one_of_field));
//! assert!(true == group.check_elem_update(&group.string_field));
//!
//! assert!(false == group.check_elem_update(&group.array_init));
//! assert!(false == group.check_elem_update(&group.c_string_type));
//! assert!(false == group.check_elem_update(&group.env_var));
//! assert!(false == group.check_elem_update(&group.no_imp_exp));
//! assert!(false == group.check_elem_update(&group.no_imp_exp_2));
//! assert!(false == group.check_elem_update(&group.int_field));
//! assert!(false == group.check_elem_update(&group.non_alias));
//! assert!(false == group.check_elem_update(&group.one_of_field));
//! assert!(false == group.check_elem_update(&group.string_field));
//!
//! // Any field that wasn't marked as 'config_it' attribute will not be part of
//! // config_it system.
//!
//! // // Invoking next line will panic:
//! // group.check_elem_update(&group.nothing_here);
//!
//! // 3. Properties
//! //
//! // You can access each field of the group instance in common deref manner.
//! assert!(group.string_field == "");
//! assert!(group.array_init == &[1, 2, 3, 4, 5]);
//! assert!(group.env_var == 123);
//!
//! // 4. Importing and Exporting
//! //
//! // You can export the whole storage using 'Export' method.
//! // (currently, there is no way to export a specific group
//! // instance. To separate groups into different archiving
//! // categories, you can use multiple storage instances)
//! let archive = storage.export(Default::default()).await.unwrap();
//!
//! // `config_it::Archive` implements `serde::Serialize` and
//! // `serde::Deserialize`. You can use it to serialize/
//! // deserialize the whole storage.
//! let yaml = serde_yaml::to_string(&archive).unwrap();
//! let json = serde_json::to_string_pretty(&archive).unwrap();
//! // println!("{}", yaml);
//! // OUTPUT:
//! //
//! // ~path: # all path tokens of group hierarchy are prefixed with '~'
//! // ~to: # (in near future, this will be made customizable)
//! // ~my:
//! // ~group:
//! // alias: 0.0
//! // array_init:
//! // - 1
//! // - 2
//! // - 3
//! // - 4
//! // - 5
//! // c_string_type: []
//! // env_var: 0
//! // int_field: 3
//! // one_of_field: default
//! // string_field: ''
//! //
//!
//! // println!("{}", json);
//! // OUTPUT:
//! // {
//! // "~path": {
//! // "~to": {
//! // "~my": {
//! // "~group": {
//! // "alias": 0.0,
//! // "array_init": [
//! // 1,
//! // 2,
//! // 3,
//! // 4,
//! // 5
//! // ],
//! // "c_string_type": [],
//! // "env_var": 0,
//! // "int_field": 3,
//! // "one_of_field": "default",
//! // "string_field": ""
//! // }
//! // }
//! // }
//! // }
//! // }
//!
//! // Importing is similar to exporting. You can import a
//! // whole storage from an archive. For this, you should
//! // create a new archive. Archive can be created using serde either.
//! use indoc::indoc;
//! let yaml = indoc!(r##"
//! ~path:
//! ~to:
//! ~my:
//! ~group:
//! alias: 3.14
//! array_init:
//! - 1
//! - 145
//! int_field: 3 # If there's no change, it won't be updated. This behavior can be overridden by import options.
//! env_var: 59
//! one_of_field: "hello" # This is not in the 'one_of' list...
//! "##);
//!
//! println!("{}", yaml);
//!
//! let archive: config_it::Archive = serde_yaml::from_str(yaml).unwrap();
//! storage.import(archive, Default::default()).await.unwrap();
//! storage.fence().await; // Since import operation is asynchronous, you must fence
//! // to make sure all changes are applied.
//!
//! // Now, let's check if the changes are applied.
//! assert!(group.update() == true);
//!
//! // Data update is regardless of the individual properties' dirty flag control.
//! // Data is modified only when `group.update()` is called.
//! assert!(group.non_alias == 3.14); // That was aliased property
//! assert!(group.array_init == [1, 145]);
//! assert!(group.env_var == 59);
//! assert!(group.int_field == 3); // No change
//! assert!(group.one_of_field == "default"); // Not in the 'one_of' list. no change.
//!
//! // Only updated properties' dirty flag will be set.
//! assert!(true == group.check_elem_update(&group.non_alias));
//! assert!(true == group.check_elem_update(&group.array_init));
//! assert!(true == group.check_elem_update(&group.env_var));
//!
//! // Since this property had no change, dirty flag was not set.
//! assert!(false == group.check_elem_update(&group.int_field));
//!
//! // Since this property was not in the 'one_of' list, it was ignored.
//! assert!(false == group.check_elem_update(&group.one_of_field));
//!
//! // These were simply not in the list.
//! assert!(false == group.check_elem_update(&group.c_string_type));
//! assert!(false == group.check_elem_update(&group.no_imp_exp));
//! assert!(false == group.check_elem_update(&group.no_imp_exp_2));
//! assert!(false == group.check_elem_update(&group.string_field));
//!
//! // 5. Other features
//!
//! // 5.1. Watch update
//! // When group is possible to updated, you can be notified
//! // through asynchronous channel. This is useful when you
//! // want to immediately response to any configuration updates.
//! let mut monitor = group.watch_update();
//! assert!(false == monitor.try_recv().is_ok());
//!
//! let archive: config_it::Archive = serde_yaml::from_str(yaml).unwrap();
//! storage
//! .import(
//! archive,
//! config_it::ImportOptions {
//! apply_as_patch: false, // This will force all properties to be updated.
//! ..Default::default()
//! },
//! )
//! .await
//! .unwrap();
//!
//! assert!(true == monitor.recv().await.is_ok());
//! assert!(group.update());
//!
//! // 5.2. Commit
//! // Any property value changes on group is usually local,
//! // however, if you want to
//! // archive those changes, you can commit it.
//! group.int_field = 15111; // This does not affected by
//! // constraint and visible from export,
//! // however, in next time you import
//! // it from exported archive,
//! // its constraint will be applied.
//!
//! // If you set the second boolean parameter 'true', it will
//! // be notified to 'monitor'
//! group.commit_elem(&group.int_field, false);
//! let archive = storage.export(Default::default()).await.unwrap();
//!
//! assert!(
//! archive.find_path(path.iter().map(|x| *x)).unwrap().values["int_field"]
//! .as_i64()
//! .unwrap()
//! == 15111
//! );
//!
//! // As the maximum value of 'int_field' is 5, in next import, it will be 5.
//! storage
//! .import(
//! archive,
//! config_it::ImportOptions {
//! // Since we create patch from archive content ...
//! // Need to forcibly invalidate all
//! apply_as_patch: false,
//! ..Default::default()
//! },
//! )
//! .await
//! .unwrap();
//! storage.fence().await;
//!
//! assert!(group.update());
//! assert!(group.int_field == 5);
//!
//! // 5.3. Monitor
//! //
//! // All events to update storage can be monitored though
//! // this channel.
//! //
//! // As this is advanced topic, and currently its design
//! // is not finalized, just give a look for fun and don't
//! // use it in production.
//! let _ch = storage.monitor_open_replication_channel().await;
//! });
//! ```
//!
pub mod archive;
pub mod config;
pub mod core;
pub mod entity;
pub mod storage;
pub use compact_str::CompactString;
pub use archive::Archive;
pub use config::Group;
pub use config::Template;
pub use storage::ExportOptions;
pub use storage::ImportOptions;
pub use storage::Storage;
pub use storage::create as create_storage;
/// Required by `config_it::Template` macro.
pub use lazy_static::lazy_static;
/// Primary macro for defining configuration group template.
pub use macros::Template;