Skip to main content

hocon/
lib.rs

1//! # hocon
2//!
3//! Full [Lightbend HOCON specification](https://github.com/lightbend/config/blob/main/HOCON.md)-compliant
4//! parser for Rust.
5//!
6//! ## Quick Example
7//!
8//! ```rust
9//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
10//! let config = hocon::parse(r#"
11//!     server {
12//!         host = "localhost"
13//!         port = 8080
14//!     }
15//! "#)?;
16//!
17//! assert_eq!(config.get_string("server.host")?, "localhost");
18//! assert_eq!(config.get_i64("server.port")?, 8080);
19//! # Ok(())
20//! # }
21//! ```
22//!
23//! ## Parsing
24//!
25//! - [`parse`] -- parse a HOCON string into a [`Config`].
26//! - [`parse_file`] -- parse a HOCON file. Include directives are resolved
27//!   relative to the file's directory.
28//! - [`parse_with_env`] / [`parse_file_with_env`] -- parse with a custom
29//!   environment variable map instead of inheriting the process environment.
30//!
31//! ## Accessing Values
32//!
33//! [`Config`] provides typed getters that accept dot-separated paths:
34//!
35//! | Method | Return type |
36//! |--------|-------------|
37//! | [`Config::get_string`] | `Result<String, ConfigError>` |
38//! | [`Config::get_i64`] | `Result<i64, ConfigError>` |
39//! | [`Config::get_f64`] | `Result<f64, ConfigError>` |
40//! | [`Config::get_bool`] | `Result<bool, ConfigError>` |
41//! | [`Config::get_config`] | `Result<Config, ConfigError>` |
42//! | [`Config::get_list`] | `Result<Vec<HoconValue>, ConfigError>` |
43//! | [`Config::get_duration`] | `Result<Duration, ConfigError>` |
44//! | [`Config::get_bytes`] | `Result<i64, ConfigError>` |
45//!
46//! Each typed getter has an `_option` variant (e.g., [`Config::get_string_option`])
47//! that returns `Option<T>` instead.
48//!
49//! ## Duration and Byte-Size Values
50//!
51//! ```rust
52//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
53//! let config = hocon::parse(r#"
54//!     timeout = 30 seconds
55//!     max-upload = 512 MB
56//! "#)?;
57//!
58//! let timeout = config.get_duration("timeout")?;
59//! let max_upload = config.get_bytes("max-upload")?;
60//! # Ok(())
61//! # }
62//! ```
63//!
64//! Duration units: `ns`, `us`, `ms`, `s`/`seconds`, `m`/`minutes`, `h`/`hours`, `d`/`days`.
65//!
66//! Byte-size units: `B`, `KB`, `KiB`, `MB`, `MiB`, `GB`, `GiB`, `TB`, `TiB`
67//! (and their long forms like `megabytes`, `mebibytes`).
68//!
69//! ## Serde Deserialization
70//!
71//! With the `serde` feature enabled, deserialize a [`Config`] (or sub-config)
72//! into any type implementing `serde::Deserialize`:
73//!
74//! ```rust,ignore
75//! use serde::Deserialize;
76//!
77//! #[derive(Deserialize)]
78//! struct Server {
79//!     host: String,
80//!     port: u16,
81//! }
82//!
83//! let server: Server = config.get_config("server")?.deserialize()?;
84//! ```
85//!
86//! ## Include Files
87//!
88//! HOCON supports `include` directives to compose configuration from multiple files:
89//!
90//! ```hocon
91//! include "defaults.conf"
92//!
93//! server.port = 9090  # override a value from defaults
94//! ```
95//!
96//! When parsing with [`parse_file`], include paths are resolved relative to the
97//! file being parsed.
98//!
99//! ## Error Types
100//!
101//! - [`HoconError`] -- unified error returned by parse functions. Wraps:
102//!   - [`ParseError`] -- syntax errors during lexing or parsing (includes line/column).
103//!   - [`ResolveError`] -- substitution resolution failures, cycle detection.
104//!   - `std::io::Error` -- file I/O errors (top-level file read; include file errors appear as [`ResolveError`]).
105//! - [`ConfigError`] -- missing keys or type mismatches when accessing values.
106//!
107//! ## HOCON Specification
108//!
109//! For the full specification, see the
110//! [Lightbend HOCON spec](https://github.com/lightbend/config/blob/main/HOCON.md).
111
112pub mod config;
113pub mod error;
114pub(crate) mod lexer;
115pub(crate) mod parser;
116pub(crate) mod properties;
117pub(crate) mod resolver;
118pub mod value;
119
120#[cfg(feature = "serde")]
121pub mod serde;
122
123pub use config::Config;
124pub use error::{ConfigError, HoconError, ParseError, ResolveError};
125pub use value::{HoconValue, ScalarType, ScalarValue};
126
127#[cfg(feature = "serde")]
128pub use serde::DeserializeError;
129
130use std::collections::HashMap;
131use std::path::Path;
132
133/// Parse a HOCON string into a Config.
134pub fn parse(input: &str) -> Result<Config, HoconError> {
135    parse_with_env(input, &std::env::vars().collect())
136}
137
138/// Parse a HOCON file into a Config.
139pub fn parse_file<P: AsRef<Path>>(path: P) -> Result<Config, HoconError> {
140    parse_file_with_env(path, &std::env::vars().collect())
141}
142
143/// Parse a HOCON file with a custom environment variable map.
144pub fn parse_file_with_env<P: AsRef<Path>>(
145    path: P,
146    env: &HashMap<String, String>,
147) -> Result<Config, HoconError> {
148    let path = path.as_ref();
149    let content = std::fs::read_to_string(path)
150        .map_err(|e| std::io::Error::new(e.kind(), format!("{}: {}", path.display(), e)))?;
151    let tokens = lexer::tokenize(&content)?;
152    let ast = parser::parse_tokens(&tokens)?;
153    let mut opts = resolver::ResolveOptions::new(env.clone());
154    if let Some(dir) = path.parent() {
155        opts = opts.with_base_dir(dir.to_path_buf());
156    }
157    let value = resolver::resolve(ast, &opts)?;
158    match value {
159        HoconValue::Object(fields) => Ok(Config::new(fields)),
160        _ => Err(HoconError::Parse(ParseError {
161            message: "root must be an object".into(),
162            line: 1,
163            col: 1,
164        })),
165    }
166}
167
168/// Parse a HOCON string with a custom environment variable map.
169pub fn parse_with_env(input: &str, env: &HashMap<String, String>) -> Result<Config, HoconError> {
170    let tokens = lexer::tokenize(input)?;
171    let ast = parser::parse_tokens(&tokens)?;
172    let opts = resolver::ResolveOptions::new(env.clone());
173    let value = resolver::resolve(ast, &opts)?;
174    match value {
175        HoconValue::Object(fields) => Ok(Config::new(fields)),
176        _ => Err(HoconError::Parse(ParseError {
177            message: "root must be an object".into(),
178            line: 1,
179            col: 1,
180        })),
181    }
182}