pub enum Value {
Null,
Bool(bool),
Integer(i128),
Float(f64),
String(String),
Array(Vec<Value>),
Table(BTreeMap<String, Value>),
}Expand description
One resolved configuration value, owned.
What Snapshot::to_value returns. This is
configuration handover, not a diagnostic: real values, secrets
included, exactly like deserializing into a struct — the paths-only
rule governs what this crate prints, not what it hands the program.
Which is why Debug is hand-written and shape-only: the same data
sits inside Snapshot, whose Debug prints keys
and never values, and {:?} in a log line is exactly how resolved
secrets leak. Read values through the enum; print them on purpose or
not at all.
There is deliberately no Display. A schemaless configuration has
no #[config(secret)] to derive a redaction list from, so a type that
rendered itself into {} would put a password wherever a program
formats a value it did not inspect — and it would do it in the one shape
(format!, write!, a template) where nothing looks like a decision.
The ways out are all explicit: the accessors, get_as,
render for a document, and Serialize for a
serializer the caller chose.
Variants§
Null
An explicit null (or unit) in a source.
Bool(bool)
A boolean.
Integer(i128)
Any integer a source can express.
i128, so every i64 and u64 fits without a sign decision at
this boundary. The one unrepresentable case — a u128 above
i128::MAX — arrives as Value::Float, lossily; a configuration
value up there is measuring something no unit this crate knows
about.
Float(f64)
A floating-point number.
String(String)
A string; a single character in a source arrives as one too.
Array(Vec<Value>)
A sequence.
Table(BTreeMap<String, Value>)
A table, keyed by field name.
Implementations§
Source§impl Value
impl Value
Sourcepub fn get(&self, path: &str) -> Option<&Value>
pub fn get(&self, path: &str) -> Option<&Value>
The value at a dotted path below this one, if every step exists.
Steps are table keys; anything else — an array, a leaf — ends the
walk with None. The empty path is this value itself.
Sourcepub fn get_as<T: DeserializeOwned>(&self, path: &str) -> Result<T, Error>
pub fn get_as<T: DeserializeOwned>(&self, path: &str) -> Result<T, Error>
The value at a dotted path, deserialized into T.
The convenient door onto a schemaless configuration, and the more
expensive one. get walks the tree and hands back a
borrow; this walks it, rebuilds the value figment’s deserializer
wants and runs serde over it — on every call. Measured against
the borrowed read on the same machine in the same run
(benches/read_path.rs), that is around a third again as long for a
scalar, and it allocates whatever the value it hands back owns: a
number, nothing; a String, one.
The bigger reason to prefer the accessors is not the nanoseconds but
the Result: get_as is a conversion that can fail at every read,
which is a diagnostic-grade shape. Use it where a value is read once
at startup or per reload — a serde type the accessors cannot express,
a Vec<String>, a struct for one sub-tree — and
get plus as_i64 and friends on a
request path. It is the same trade
Snapshot::get makes, written down where the
schemaless reader will meet it.
use dynamic_config::{Format, Value};
let document = Value::parse(r#"{"pool": {"max_size": 32}}"#, Format::Json).unwrap();
assert_eq!(document.get_as::<u16>("pool.max_size").unwrap(), 32);§Errors
ErrorKind::Missing when nothing
supplies path — including a path that walks through a scalar —
and ErrorKind::Type when what is there
cannot become T. The message names the path and the kind of thing
that was there, never the value.
Sourcepub fn as_integer(&self) -> Option<i128>
pub fn as_integer(&self) -> Option<i128>
The integer here, at the width this crate stores it in.
None for a float, even one that is a whole number: which of the two
a source wrote is part of the configuration here, and
Value::Integer’s i128 is what makes that distinction free of a
sign decision.
Sourcepub fn as_i64(&self) -> Option<i64>
pub fn as_i64(&self) -> Option<i64>
The integer here as an i64, or None if it is not one or does not
fit.
Narrowing rather than saturating: a port number that does not fit is a configuration mistake, and a clamped one is that mistake made silent.
Sourcepub fn as_u64(&self) -> Option<u64>
pub fn as_u64(&self) -> Option<u64>
The integer here as a u64, or None if it is not one, is negative,
or does not fit.
Sourcepub fn as_float(&self) -> Option<f64>
pub fn as_float(&self) -> Option<f64>
The float here, or None if this is anything else — an integer
included, for the reason as_integer gives.
Sourcepub fn as_str(&self) -> Option<&str>
pub fn as_str(&self) -> Option<&str>
The string here, borrowed, or None if this is anything else.
Sourcepub fn as_array(&self) -> Option<&[Value]>
pub fn as_array(&self) -> Option<&[Value]>
The sequence here, borrowed, or None if this is anything else.
Sourcepub fn as_table(&self) -> Option<&BTreeMap<String, Value>>
pub fn as_table(&self) -> Option<&BTreeMap<String, Value>>
The table here, borrowed, or None if this is anything else.
Sourcepub fn leaf_paths(&self) -> Vec<String>
pub fn leaf_paths(&self) -> Vec<String>
The dotted path of every leaf, in order.
What a schemaless configuration has instead of a field list: the keys
that are actually there, learned at runtime. The same walk
Snapshot::leaf_paths performs, on
the tree a reader already holds — an array is a leaf, because its
elements are values rather than configuration keys, and so is an
empty table, which would otherwise vanish from the listing.
Paths carry no values, so this is the one listing of a resolved configuration that is safe to log.
A tree that is not a table has no paths: a document has named keys at its root.
Sourcepub fn parse(text: &str, format: Format) -> Result<Self, Error>
pub fn parse(text: &str, format: Format) -> Result<Self, Error>
Parses one format document into a tree.
The way in to the parsing this crate already owns, for code that has
documents to combine before the loader sees them: a store crate that
reads several keys under a prefix, a tool that folds a fragment
directory into one file. Without it the only way to merge two documents
outside this crate is to depend on serde_json, toml and serde_yaml
directly and reimplement what the json / toml / yaml features are
already compiling.
No section mapping is applied: the result is the document as written, top-level keys and all. Sections are what the loader does with a document, and a merge happens below that line.
use dynamic_config::{Format, Value};
let mut document = Value::parse(r#"{"db": {"host": "a"}}"#, Format::Json).unwrap();
document.merge(Value::parse(r#"{"db": {"port": 5432}}"#, Format::Json).unwrap());
assert_eq!(document.get("db.host"), Some(&Value::String("a".into())));
assert_eq!(document.get("db.port"), Some(&Value::Integer(5432)));§Errors
ErrorKind::Parse if the text is not a valid
format document, and ErrorKind::Backend
if this build has that format’s feature off. The message is stripped the
same way every other backend failure here is — the key and the kind of
thing that was there, never the value.
Sourcepub fn merge(&mut self, other: Value)
pub fn merge(&mut self, other: Value)
Merges other over this value: later wins, tables deep.
The rule the crate already teaches for files, applied to two trees.
Where both sides have a table the merge descends into it; anywhere else
other replaces what was there, arrays included — a later document
supplying tags = ["b"] means those tags and not the earlier ones, which
is what every layer in this crate already means by it.
Sourcepub fn overlapping_paths(&self, other: &Value) -> Vec<String>
pub fn overlapping_paths(&self, other: &Value) -> Vec<String>
Every leaf path both trees supply — what merge would
silently resolve.
For the caller whose documents are meant to be disjoint: keys read from a prefix are sections nobody intended to overlap, so an overlap there is a deployment bug worth an error rather than a merge. Paths in sorted order, and paths only — this is a diagnostic, so it names what collided and never what either side held.
A path where both sides hold a table is not a collision; the tables merge. A path where either side holds an array or a scalar is.
Sourcepub fn render(&self, format: Format) -> Result<String, Error>
pub fn render(&self, format: Format) -> Result<String, Error>
Renders this tree as the text of a format document.
The way back out, so a merged tree can be handed to something that takes
text — Fetched::new, a file, a socket.
§Errors
ErrorKind::Backend if this build has that
format’s feature off, and ErrorKind::Type if
the tree is not a table — a document has named keys at its root — or
holds something the format cannot express, such as a null in TOML.
Trait Implementations§
Source§impl<'de> Deserialize<'de> for Value
The impl that makes a configuration with no struct behind it possible.
impl<'de> Deserialize<'de> for Value
The impl that makes a configuration with no struct behind it possible.
DeserializeOwned is the only bound the engine puts on a configuration
type, so this one line is what turns Dynamic<Value>,
Builder::values and load::<Value> from
“would not compile” into the schemaless shape — with layering, watching,
the last-known-good cache and the reload hooks all working unchanged,
because none of them ever knew what T was.
Deliberately deserialize_any: a configuration value is whatever the
source said it was, which is the one place in serde where self-describing
is the right answer. The two numeric edges match the walk in from the
resolved tree exactly — every integer widens to i128, and the one
unrepresentable case (a u128 above i128::MAX) arrives as a float —
so a value that reaches this type through serde and one that reaches it
by walking the resolved tree are the same value.
Source§fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error>
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error>
Source§impl Hash for Value
Hand-written because f64 is not Hash, and because what a fingerprint
wants from a float is not what arithmetic wants: hashing through
f64::to_bits makes -0.0 and 0.0 hash differently, which is right
here — they are different bytes in the file, and the cache’s question is
“is this the same document”, not “is this the same number”.
impl Hash for Value
Hand-written because f64 is not Hash, and because what a fingerprint
wants from a float is not what arithmetic wants: hashing through
f64::to_bits makes -0.0 and 0.0 hash differently, which is right
here — they are different bytes in the file, and the cache’s question is
“is this the same document”, not “is this the same number”.
That is also why this is deliberately not consistent with PartialEq
in the two places IEEE 754 is not: -0.0 == 0.0 while their hashes
differ, and no NaN equals itself while every NaN payload hashes
stably. Value is not Eq for exactly those reasons, so there is no
Hash/Eq contract to break.
Source§impl Serialize for Value
The way back out through serde, for crate::save and
crate::changed_paths — the two surfaces that take T: Serialize and
would otherwise be the only ones a schemaless configuration could not
reach.
impl Serialize for Value
The way back out through serde, for crate::save and
crate::changed_paths — the two surfaces that take T: Serialize and
would otherwise be the only ones a schemaless configuration could not
reach.
Integers narrow exactly as the walk back out narrows them, and for the
same reason: this type widens every integer on the way in so the boundary
needs no sign decision, while a serializer does — toml refuses an
i128 whatever the number in it is.
This is handover, like crate::Snapshot::to_value and unlike
Debug: it emits real values, secrets included, because that is what
serializing a configuration means. The paths-only rule governs what this
crate prints, not what a caller asks it to write.
impl StructuralPartialEq for Value
Auto Trait Implementations§
impl Freeze for Value
impl RefUnwindSafe for Value
impl Send for Value
impl Sync for Value
impl Unpin for Value
impl UnsafeUnpin for Value
impl UnwindSafe for Value
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> Paint for Twhere
T: ?Sized,
impl<T> Paint for Twhere
T: ?Sized,
Source§fn fg(&self, value: Color) -> Painted<&T>
fn fg(&self, value: Color) -> Painted<&T>
Returns a styled value derived from self with the foreground set to
value.
This method should be used rarely. Instead, prefer to use color-specific
builder methods like red() and
green(), which have the same functionality but are
pithier.
§Example
Set foreground color to white using fg():
use yansi::{Paint, Color};
painted.fg(Color::White);Set foreground color to white using white().
use yansi::Paint;
painted.white();Source§fn bright_black(&self) -> Painted<&T>
fn bright_black(&self) -> Painted<&T>
Source§fn bright_red(&self) -> Painted<&T>
fn bright_red(&self) -> Painted<&T>
Source§fn bright_green(&self) -> Painted<&T>
fn bright_green(&self) -> Painted<&T>
Source§fn bright_yellow(&self) -> Painted<&T>
fn bright_yellow(&self) -> Painted<&T>
Source§fn bright_blue(&self) -> Painted<&T>
fn bright_blue(&self) -> Painted<&T>
Source§fn bright_magenta(&self) -> Painted<&T>
fn bright_magenta(&self) -> Painted<&T>
Source§fn bright_cyan(&self) -> Painted<&T>
fn bright_cyan(&self) -> Painted<&T>
Source§fn bright_white(&self) -> Painted<&T>
fn bright_white(&self) -> Painted<&T>
Source§fn bg(&self, value: Color) -> Painted<&T>
fn bg(&self, value: Color) -> Painted<&T>
Returns a styled value derived from self with the background set to
value.
This method should be used rarely. Instead, prefer to use color-specific
builder methods like on_red() and
on_green(), which have the same functionality but
are pithier.
§Example
Set background color to red using fg():
use yansi::{Paint, Color};
painted.bg(Color::Red);Set background color to red using on_red().
use yansi::Paint;
painted.on_red();Source§fn on_primary(&self) -> Painted<&T>
fn on_primary(&self) -> Painted<&T>
Source§fn on_magenta(&self) -> Painted<&T>
fn on_magenta(&self) -> Painted<&T>
Source§fn on_bright_black(&self) -> Painted<&T>
fn on_bright_black(&self) -> Painted<&T>
Source§fn on_bright_red(&self) -> Painted<&T>
fn on_bright_red(&self) -> Painted<&T>
Source§fn on_bright_green(&self) -> Painted<&T>
fn on_bright_green(&self) -> Painted<&T>
Source§fn on_bright_yellow(&self) -> Painted<&T>
fn on_bright_yellow(&self) -> Painted<&T>
Source§fn on_bright_blue(&self) -> Painted<&T>
fn on_bright_blue(&self) -> Painted<&T>
Source§fn on_bright_magenta(&self) -> Painted<&T>
fn on_bright_magenta(&self) -> Painted<&T>
Source§fn on_bright_cyan(&self) -> Painted<&T>
fn on_bright_cyan(&self) -> Painted<&T>
Source§fn on_bright_white(&self) -> Painted<&T>
fn on_bright_white(&self) -> Painted<&T>
Source§fn attr(&self, value: Attribute) -> Painted<&T>
fn attr(&self, value: Attribute) -> Painted<&T>
Enables the styling Attribute value.
This method should be used rarely. Instead, prefer to use
attribute-specific builder methods like bold() and
underline(), which have the same functionality
but are pithier.
§Example
Make text bold using attr():
use yansi::{Paint, Attribute};
painted.attr(Attribute::Bold);Make text bold using using bold().
use yansi::Paint;
painted.bold();Source§fn rapid_blink(&self) -> Painted<&T>
fn rapid_blink(&self) -> Painted<&T>
Source§fn quirk(&self, value: Quirk) -> Painted<&T>
fn quirk(&self, value: Quirk) -> Painted<&T>
Enables the yansi Quirk value.
This method should be used rarely. Instead, prefer to use quirk-specific
builder methods like mask() and
wrap(), which have the same functionality but are
pithier.
§Example
Enable wrapping using .quirk():
use yansi::{Paint, Quirk};
painted.quirk(Quirk::Wrap);Enable wrapping using wrap().
use yansi::Paint;
painted.wrap();Source§fn clear(&self) -> Painted<&T>
👎Deprecated since 1.0.1: renamed to resetting() due to conflicts with Vec::clear().
The clear() method will be removed in a future release.
fn clear(&self) -> Painted<&T>
renamed to resetting() due to conflicts with Vec::clear().
The clear() method will be removed in a future release.
Source§fn whenever(&self, value: Condition) -> Painted<&T>
fn whenever(&self, value: Condition) -> Painted<&T>
Conditionally enable styling based on whether the Condition value
applies. Replaces any previous condition.
See the crate level docs for more details.
§Example
Enable styling painted only when both stdout and stderr are TTYs:
use yansi::{Paint, Condition};
painted.red().on_yellow().whenever(Condition::STDOUTERR_ARE_TTY);