hocon/adapters/mod.rs
1//! Read config formats owned by *other* programs as HOCON.
2//!
3//! Each adapter returns a fully resolved [`Config`](crate::Config) you place
4//! under your own document with [`Config::with_fallback`](crate::Config::with_fallback),
5//! so a `${...}` can reach into it:
6//!
7//! ```ignore
8//! let base = hocon::adapters::env::load(hocon::adapters::env::Options {
9//! prefix: "APP_".into(),
10//! ..Default::default()
11//! })?;
12//! let cfg = hocon::parse_string_with_options(src, opts_without_resolution)?;
13//! let merged = cfg.with_fallback(&base).resolve(Default::default())?;
14//! ```
15//!
16//! Deferring resolution matters: the plain `parse` resolves as it goes, so a
17//! `${...}` aimed at the fallback would fail before the fallback is attached.
18//!
19//! Every adapter is behind a cargo feature, so the crate's default build still
20//! depends on `indexmap` alone. `properties` and `env` need nothing extra;
21//! `jsonc`, `toml` and `yaml` pull one crate each.
22//!
23//! Foreign data stays data: a `${a.b}` in an ingested value is literal text,
24//! never a reference (spec F0.2). Ingestion is AST-level — a document is
25//! decoded and turned into a value tree, never rendered to HOCON text.
26//!
27//! See `docs/specs/format-ingestion-mapping.md` in the hocon scope.
28
29use crate::value::HoconValue;
30use crate::Config;
31
32#[cfg(feature = "adapters-env")]
33pub mod env;
34#[cfg(feature = "adapters-jsonc")]
35pub mod jsonc;
36#[cfg(feature = "adapters-properties")]
37pub mod properties;
38#[cfg(feature = "adapters-toml")]
39pub mod toml;
40#[cfg(feature = "adapters-yaml")]
41pub mod yaml;
42
43/// Drop a leading UTF-8 BOM (spec F0.9).
44///
45/// Windows editors emit one, and a BOM left in place becomes part of the first
46/// key: `a: 1` yields the key `"\u{feff}a"`, so a lookup of `a` misses and the
47/// value is silently unreachable. That plausible-but-wrong output is the
48/// failure mode this spec most wants to avoid. The core HOCON parser already
49/// ignores U+FEFF, so this only brings the adapters into line.
50pub(crate) fn strip_bom(input: &str) -> &str {
51 input.strip_prefix('\u{feff}').unwrap_or(input)
52}
53
54/// Wrap an already-built object tree as a resolved `Config`.
55pub(crate) fn config_from_object(root: HoconValue, origin: Option<&str>) -> Config {
56 match root {
57 HoconValue::Object(fields) => Config::new_with_meta(fields, origin.map(|s| s.to_owned())),
58 _ => unreachable!("callers pass an object"),
59 }
60}
61
62/// The error every adapter reports.
63#[derive(Debug, Clone, PartialEq, Eq)]
64pub struct AdapterError {
65 /// Human-readable description, citing the spec item where one applies.
66 pub message: String,
67}
68
69impl std::fmt::Display for AdapterError {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 write!(f, "{}", self.message)
72 }
73}
74
75impl std::error::Error for AdapterError {}
76
77impl AdapterError {
78 pub(crate) fn new(message: impl Into<String>) -> Self {
79 Self {
80 message: message.into(),
81 }
82 }
83}