inline_config/
lib.rs

1//! Effortlessly embed config as static data and access with any compatible data structures.
2//!
3//! ## Example
4//!
5//! Below is a basic example illustrating how to declare a static config item and access data from it.
6//!
7//! ```
8//! use inline_config::{Get, toml_config, path};
9//!
10//! toml_config! {
11//!     // Just looks like a typical static item declaration.
12//!     // Apart from the static item, a type `MyConfig` will be generated as well.
13//!     // Including a file from disk is also possible, see `examples/include.rs`.
14//!     pub static MY_CONFIG: MyConfig = r#"
15//!         title = "TOML example"
16//!
17//!         [server]
18//!         owner = "Tom"
19//!         timeout = 2000
20//!         ports = [ 8000, 8001, 8002 ]
21//!     "# + r#"
22//!         [server]
23//!         timeout = 5000
24//!     "#;
25//! }
26//!
27//! // Multiple types may be compatible. As a cost, type annotation is always required.
28//! let title: &str = MY_CONFIG.get(path!(title));
29//! assert_eq!("TOML example", title);
30//! let title: String = MY_CONFIG.get(path!(title));
31//! assert_eq!("TOML example", title);
32//!
33//! // A deeper path.
34//! let owner: &str = MY_CONFIG.get(path!(server.owner));
35//! assert_eq!("Tom", owner);
36//!
37//! // Any numerical types.
38//! let timeout: u32 = MY_CONFIG.get(path!(server.timeout));
39//! assert_eq!(5000, timeout);
40//! let timeout: f32 = MY_CONFIG.get(path!(server.timeout));
41//!
42//! // A homogeneous array can be accessed as `Vec<T>`.
43//! let ports: Vec<u64> = MY_CONFIG.get(path!(server.ports));
44//! assert_eq!([8000, 8001, 8002].to_vec(), ports);
45//! ```
46//!
47//! ## Config block specification
48//!
49//! A config block may contain any number of config items, in the form illustrated below:
50//!
51//! ```ignore
52//! <FORMAT>_config! {
53//!     <VIS> static <IDENT>: <TYPE> = <SRC>;
54//!     <VIS> static <IDENT>: <TYPE> = <SRC> + <SRC> + <SRC>;
55//! }
56//! ```
57//!
58//! Each declaration is simply a typical static item with a new type to be generated.
59//! `<IDENT>`s shall not collide; `<TYPE>`s shall not collide.
60//! After expansion the following symbols are brought into scope:
61//!
62//! * Static items, one for each `<IDENT>`;
63//! * Type items, one for each `<TYPE>`, deriving traits `Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd`;
64//! * Mod items, one for each `__<IDENT:lower>` (for internal usage).
65//!
66//! The expression part looks like a sum of sources.
67//! This is where overwriting takes place. All variants are completely overwritten except for tables, which got merged recursively.
68//! Every `<SRC>` takes one of the following forms:
69//!
70//! * `r#"name = "Tom""#` - an inline literal config.
71//! * `include_config!("example_config.toml")` - a file inclusion. The path is resolved relative to the current file (similar to [`include_str!()`]).
72//! * `include_config_env!("$CARGO_MANIFEST_DIR/examples/example_config.toml")` - also a file inclusion, but environment variables of form `$ENV_VAR` are interpolated. Escape `$` with `$$`.
73//!
74//! The support of environment variable interpolation is to aid any code analyzer to locate files,
75//! as environment variables like `$CARGO_MANIFEST_DIR` and `$OUT_DIR` resolve to absolute paths.
76//! This is mostly inspired by [include_dir](https://docs.rs/include_dir/latest/include_dir/) crate.
77//!
78//! ## Compatible types
79//!
80//! Internally, data from config sources are parsed into one of the eight variants:
81//! nil, booleans, unsigned integers, signed integers, floats, strings, arrays, tables.
82//! Each of them has a specific storage representation, and have different compatible types.
83//!
84//! | Representation variant | Storage | Compatible types |
85//! |---|---|---|
86//! | Nil | `()` | (See [option types](#option-types)) |
87//! | Boolean | `bool` | `bool` |
88//! | Unsigned Integer | `u64` | `i8`, `i16`, `i32`, `i64`, `i128`, `isize`,<br>`u8`, `u16`, `u32`, `u64`, `u128`, `usize`,<br>`f32`, `f64` |
89//! | Signed Integer | `i64` | `i8`, `i16`, `i32`, `i64`, `i128`, `isize`,<br>`f32`, `f64` |
90//! | Float | `OrderedFloat<f64>`<sup>1</sup> | `f32`, `f64` |
91//! | String | `&'static str` | `&str`, `String` |
92//! | Array | Structs | `Vec<T>` if homogeneous,<br>User-defined structs with unnamed fields |
93//! | Table | Structs | `BTreeMap<&str, T>` if homogeneous,<br>`BTreeMap<String, T>` if homogeneous,<br>`IndexMap<&str, T>` if homogeneous<sup>2</sup>,<br>`IndexMap<String, T>` if homogeneous<sup>2</sup>,<br>User-defined structs with named fields |
94//!
95//! Footnotes:
96//! 1. `f64` does not implement `Eq`, `Ord`, `Hash` traits, but [`ordered_float::OrderedFloat<f64>`] does.
97//! 2. Only available when enabling `indexmap` feature flag.
98//!
99//! ### Container types
100//!
101//! Arrays and tables are both "containers" in the sense of containing children data, therefore you can use [`path!()`] to access children data.
102//! The only difference between the two containers is that arrays have unnamed but ordered fields, while tables have named but unamed fields.
103//! This suggests you should use indices when accessing a field of an array, but use names when accessing a field of a table.
104//!
105//! Note that they are inhomogeneous in general (children are of different types).
106//! You need to define custom types and derive [`ConfigData`] if you want to access structured data.
107//! Define structs with unnamed fields to model an array, while structs with named fields to model a table.
108//! Specially, in the case when they do contain homogeneous data,
109//! arrays can be accessed as `Vec<T>`, and tables can be accessed as `BTreeMap<&str, T>` or `BTreeMap<String, T>`,
110//! as long as the representation of children can be accessed as `T`.
111//! For containers, this type compatibility comes with a recursive sense.
112//! There's a relevant concept from functional programming, known as [transmogrifying](https://docs.rs/frunk/0.4.4/frunk/#transmogrifying).
113//!
114//! ### Option types
115//!
116//! Some of config formats support null value.
117//! We cannot directly store it as `Option<T>`, as we are not able to tell what `T` is by looking at a literal null.
118//! The following behaviors are implemented.
119//!
120//! * When requesting `Option<T>` from null, return `None`;
121//! * When requesting `T` from null, return `T::default()` if `T: Default`.
122//!
123//! For consistency, you can also request `Option<T>` from a non-null value as long as `T` can be accessed from it,
124//! and the result will be additionally wrapped by a `Some`.
125//!
126//! ## Feature flags
127//!
128//! * `json` - supports JSON file format. Enabled by default.
129//! * `yaml` - supports YAML file format. Enabled by default.
130//! * `toml` - supports TOML file format. Enabled by default.
131//! * `indexmap` - enables preserving orders of tables.
132
133mod convert;
134mod key;
135mod repr;
136
137/// Declares static variables containing config data in JSON format.
138#[cfg(feature = "json")]
139pub use inline_config_macros::json_config;
140
141/// Declares static variables containing config data in YAML format.
142#[cfg(feature = "yaml")]
143pub use inline_config_macros::yaml_config;
144
145/// Declares static variables containing config data in TOML format.
146#[cfg(feature = "toml")]
147pub use inline_config_macros::toml_config;
148
149/// Constructs a path with which one accesses a nested-in piece of data from config.
150///
151/// A path can be constructed by a sequence of keys, separated by `.`.
152/// A key can be either an index (access an array field) or a name (access a table field).
153/// The name may be quoted if it is not a valid identifier (e.g. contains `-`).
154pub use inline_config_macros::path;
155
156/// The type version of [`path!()`]. Used in type parameters of [`Get`].
157pub use inline_config_macros::Path;
158
159/// Defines a data structure that can be converted directly from a compatible container.
160///
161/// One needs to ensure all field types, if containing custom types, shall inherit [`ConfigData`] as well.
162/// Use structs with unnamed fields to access from arrays; use structs with named fields to access from tables.
163/// The fields do not necessarily need to be "full" - it may only contain a subset of fields in source data.
164///
165/// To avoid non-identifier key names occurred in source config (e.g. contains `-`), use `#[config_data(rename = "...")]` on certain fields.
166///
167/// ```
168/// use inline_config::ConfigData;
169///
170/// #[derive(ConfigData)]
171/// struct MyStruct {
172///     name: String, // matches "name"
173///     #[config_data(rename = "date-of-birth")]
174///     date_of_birth: String, // matches "date-of-birth"
175///     r#mod: String, // matches "mod"
176/// }
177/// ```
178pub use inline_config_macros::ConfigData;
179
180/// A trait modeling type compatibility.
181///
182/// A type bound `C: Get<P, T>` means the data at path `P` from config `C` is compatible with and can be converted into `T`.
183///
184/// This trait is not meant to be custom implemented; all implementations are induced from `config!()` macro.
185pub trait Get<P, T> {
186    fn get(&'static self, path: P) -> T;
187}
188
189#[doc(hidden)]
190pub mod __private {
191    pub use crate::convert::*;
192    pub use crate::key::*;
193    pub use crate::repr::*;
194
195    pub use ordered_float::OrderedFloat;
196
197    #[cfg(feature = "indexmap")]
198    pub use indexmap::IndexMap;
199}