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
//!
//! 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` trait.
//!
//! 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.
//!
//! Any property implements `serde::Serialize`, `serde::DeserializeOwned` can be used as
//! configuration property.
//!
//! # Attributes
//!
//! Any field decorated with attribute `#[config_it]` or `#[config]` will be treated as
//! configuration property. You can specify additional constraints for the property by
//! adding additional attributes inside parenthesis.
//!
//! - `default = <value>`
//! - Specify default value for the property. as the <value> expression is converted into
//! field type using `.try_into().unwrap()` expression, you should specify un-fallible
//! expression here. This is to support convenient value elevation from `&str` to `String`,
//! or similar owning conversions.
//! - `default_expr = "<expr>"`
//! - Specify complicated value expression here. To specify string literal here, you have
//! to escape double quotes(`"`) with backslash(`\`). As this attribute converts given
//! expression into token tree directly, you can write any valid rust expression here.
//! - `alias = "<alias>"`
//! - Specify alias name for the property. This is useful when you want to use different
//! name for the property in config file, but want to use original name in code.
//! - `one_of(<value>, <value>, ...)`
//! - Specify set of allowed values for the property. This is useful when you want to
//! restrict the value of the property to specific set of values. You can also specify
//! default value as one of the allowed values.
//! - Default value can be out of the allowed set, and can be excluded from the allowed set.
//! In this case, setting value back to default value will not be allowed.
//! - `min = <value>`, `max=<value>`
//! - Constrain the value of the property to be within given range. Any type which implements
//! `Ord` can have min/max constraints.
//! - `env = "<env_var>"`
//! - Specify environment variable name to import value from. If the environment variable
//! is not set, the default value will be used. `TryParse` trait is used to convert
//! environment variable value into property type.
//! - `no_import`
//! - Do not update value from imported archive. This is useful when mixed with `env` flag,
//! which will keep its value as imported environment variable even after the archive is
//! imported.
//! - `no_export`
//! - Do not export value to archive.
//! - `transient`
//! - Value won't be archived, and won't be imported from archive.
//! - `hidden`
//! - Hints to monitoring system that this property should not be visible.
//!
//! Any field that are not marked with attribute `#[config_it]` or `#[config]` will not be
//! treated as configuration property, however, to construct a valid `Template`, all fields
//! must be config-default-constructive.
//!
//! To specify default value for the field, you can use `#[nocfg = "<value_expr>"]` attribute.
//! This uses same rule with `default_expr` attribute, and you can use any valid rust expression
//! here.
//!
//! # Usage
//!
//! ## Creating config template
//! ```
//! 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, runner) = config_it::create_storage();
//!
//! // To run the storage, you should spawn a task with runner (the second return value)
//! std::thread::spawn(move || futures::executor::block_on(runner));
//!
//! // 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();
//!
//! // It is recommended to manipulate config group within async context.
//! futures::executor::block_on(async {
//! // You can import config file into storage.
//! // NOTE: storage is thread-safe handle to the inner storage actor, as you can freely
//! // clone it and send it to use it from multiple different threads.
//! storage.import(archive, Default::default()).await.unwrap();
//!
//! // As the import operation simply transmits request to the actor, you should wait
//! // for the actual import job to be done.
//! storage.fence().await;
//!
//! // A `Template` can be instantiated as `Group<T:Template>` type.
//! let mut scorch = storage
//! .create::<Profile>(["profile", "scorch"])
//! .await
//! .unwrap();
//! let mut john = storage
//! .create::<Profile>(["profile", "john"])
//! .await
//! .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() == true);
//! assert!(john.update() == true);
//!
//! // You can check dirty flag of individual property.
//! assert!(scorch.consume_update(&scorch.name) == true);
//! assert!(scorch.consume_update(&scorch.name) == false);
//!
//! // 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");
//!
//! storage.close().unwrap();
//! });
//! ```
//!
//! ## Config property with serde
//!
//! Any type implements `Clone`, `serde::Serialize` and `serde::Deserialize` can be used as
//! config property. Default trait can be omitted if you provide default value for the property.
//!
//! ```
//! #[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, runner) = config_it::create_storage();
//! let task = async {
//! let mut outer = storage.create::<Outer>(["outer"]).await.unwrap();
//! outer.inner.name = "John".to_owned();
//! outer.inner.age = 30;
//!
//! // You can feedback local change to the storage using `commit_elem`
//! outer.commit_elem(&outer.inner, false);
//!
//! // You can retrieve the archive from storage using `export`
//! let archive = storage.export(Default::default()).await.unwrap();
//!
//! let dump = serde_json::to_string(&archive).unwrap();
//! assert_eq!(dump, r#"{"~outer":{"inner":{"age":30,"name":"John"}}}"#);
//!
//! storage.close().unwrap();
//! };
//!
//! futures::executor::block_on(async {
//! futures::join!(runner, task);
//! });
//! ```
//!
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 archive::CategoryRule as ArchiveCategoryRule;
pub use async_broadcast::Receiver as BroadcastReceiver;
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;
pub use config::WatchUpdate;
pub use schemars::schema::RootSchema as Schema;
pub use serde_json::Value as ArchiveValue;
pub extern crate schemars;
pub extern crate serde;