Skip to content

Commit be54f10

Browse files
authored
fix: fixed unexpected error when configuration file is missing in default path (#245)
1 parent ad2afb9 commit be54f10

File tree

9 files changed

+133
-51
lines changed

9 files changed

+133
-51
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,5 @@ jobs:
1919
run: cargo build --verbose --benches
2020
- name: Run tests
2121
run: cargo test --verbose
22+
- name: Run executable
23+
run: cargo run

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ members = [".", "crate/encstr"]
44
[workspace.package]
55
repository = "https://github.com/pamburus/hl"
66
authors = ["Pavel Ivanov <[email protected]>"]
7-
version = "0.29.0"
7+
version = "0.29.1"
88
edition = "2021"
99
license = "MIT"
1010

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,7 @@ Arguments:
466466
[FILE]... Files to process
467467

468468
Options:
469-
--config <FILE> Configuration file path [env: HL_CONFIG=] [default: ~/.config/hl/config.yaml]
469+
--config <FILE> Configuration file path [env: HL_CONFIG=]
470470
-s, --sort Sort messages chronologically
471471
-F, --follow Follow input streams and sort messages chronologically during time frame set by --sync-interval-ms option
472472
--tail <N> Number of last messages to preload from each file in --follow mode [default: 10]

build/ci/coverage.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ set -e
55
export RUSTFLAGS="-C instrument-coverage"
66
export CARGO_TARGET_DIR="target/coverage"
77
export LLVM_PROFILE_FILE="target/coverage/test-%m-%p.profraw"
8+
export MAIN_EXECUTABLE="target/coverage/debug/hl"
89

910
LLVM_BIN=$(rustc --print sysroot)/lib/rustlib/$(rustc -vV | sed -n 's|host: ||p')/bin
1011

@@ -19,6 +20,7 @@ IGNORE=(
1920
)
2021

2122
function executables() {
23+
echo ${MAIN_EXECUTABLE:?}
2224
cargo test --tests --no-run --message-format=json \
2325
| jq -r 'select(.profile.test == true) | .filenames[]' \
2426
| grep -v dSYM -
@@ -36,6 +38,14 @@ function clean() {
3638

3739
function test() {
3840
cargo test --tests
41+
cargo build
42+
${MAIN_EXECUTABLE:?} > /dev/null
43+
${MAIN_EXECUTABLE:?} --config= --help > /dev/null
44+
${MAIN_EXECUTABLE:?} --config=etc/defaults/config-k8s.yaml > /dev/null
45+
${MAIN_EXECUTABLE:?} --config=etc/defaults/config-ecs.yaml > /dev/null
46+
${MAIN_EXECUTABLE:?} --shell-completions bash > /dev/null
47+
${MAIN_EXECUTABLE:?} --list-themes > /dev/null
48+
echo "" | ${MAIN_EXECUTABLE:?} --concurrency 4 > /dev/null
3949
}
4050

4151
function merge() {

src/cli.rs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,14 @@ use crate::{
1919
#[derive(Args)]
2020
pub struct BootstrapArgs {
2121
/// Configuration file path.
22-
#[arg(long, overrides_with = "config", value_name = "FILE", env = "HL_CONFIG", default_value = default_config_path(), num_args=1)]
23-
pub config: String,
22+
#[arg(
23+
long,
24+
overrides_with = "config",
25+
value_name = "FILE",
26+
env = "HL_CONFIG",
27+
num_args = 1
28+
)]
29+
pub config: Option<String>,
2430
}
2531

2632
/// JSON and logfmt log converter to human readable representation.
@@ -474,11 +480,3 @@ fn parse_non_zero_size(s: &str) -> std::result::Result<NonZeroUsize, NonZeroSize
474480
Err(NonZeroSizeParseError::ZeroSize)
475481
}
476482
}
477-
478-
fn default_config_path() -> clap::builder::OsStr {
479-
if let Some(dirs) = config::app_dirs() {
480-
dirs.config_dir.join("config.yaml").into_os_string().into()
481-
} else {
482-
"".into()
483-
}
484-
}

src/config.rs

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ use once_cell::sync::Lazy;
66
use platform_dirs::AppDirs;
77

88
// local imports
9-
use crate::{error::Result, settings::Settings};
9+
use crate::{
10+
error::Result,
11+
settings::{Settings, SourceFile},
12+
};
1013

1114
// ---
1215

@@ -18,12 +21,25 @@ pub fn default() -> &'static Settings {
1821
}
1922

2023
/// Load settings from the given file or the default configuration file per platform.
21-
pub fn load(path: String) -> Result<Settings> {
22-
if path.is_empty() {
24+
pub fn load(path: Option<&str>) -> Result<Settings> {
25+
let mut default = None;
26+
let (filename, required) = path.map(|p| (p, true)).unwrap_or_else(|| {
27+
(
28+
if let Some(dirs) = app_dirs() {
29+
default = Some(dirs.config_dir.join("config.yaml").to_string_lossy().to_string());
30+
default.as_deref().unwrap()
31+
} else {
32+
""
33+
},
34+
false,
35+
)
36+
});
37+
38+
if filename.is_empty() {
2339
return Ok(Default::default());
2440
}
2541

26-
Settings::load(&path)
42+
Settings::load(SourceFile::new(filename).required(required).into())
2743
}
2844

2945
/// Get the application platform-specific directories.
@@ -49,3 +65,33 @@ pub mod global {
4965
&RESOLVED
5066
}
5167
}
68+
69+
#[cfg(test)]
70+
mod tests {
71+
use super::*;
72+
use crate::settings::Settings;
73+
74+
#[test]
75+
fn test_default() {
76+
assert_eq!(default().theme, "universal");
77+
}
78+
79+
#[test]
80+
fn test_load_empty_filename() {
81+
let settings = super::load(Some("")).unwrap();
82+
assert_eq!(settings, Settings::default());
83+
}
84+
85+
#[test]
86+
fn test_load_k8s() {
87+
let settings = super::load(Some("etc/defaults/config-k8s.yaml")).unwrap();
88+
assert_eq!(settings.fields.predefined.time.0.names, &["ts"]);
89+
assert_eq!(settings.fields.predefined.message.0.names, &["msg"]);
90+
assert_eq!(settings.fields.predefined.level.variants.len(), 2);
91+
}
92+
93+
#[test]
94+
fn test_load_auto() {
95+
super::load(None).unwrap();
96+
}
97+
}

src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use hl::{
3131
// ---
3232

3333
fn run() -> Result<()> {
34-
let settings = config::load(cli::BootstrapOpt::parse().args.config)?;
34+
let settings = config::load(cli::BootstrapOpt::parse().args.config.as_deref())?;
3535
config::global::initialize(settings.clone());
3636

3737
let opt = cli::Opt::parse();

src/settings.rs

Lines changed: 58 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ use crate::level::Level;
1717
// ---
1818

1919
static DEFAULT_SETTINGS_RAW: &str = include_str!("../etc/defaults/config.yaml");
20-
static DEFAULT_SETTINGS: Lazy<Settings> = Lazy::new(|| Settings::load_from_str("", FileFormat::Yaml));
20+
static DEFAULT_SETTINGS: Lazy<Settings> = Lazy::new(|| Settings::load(Source::Str("", FileFormat::Yaml)).unwrap());
2121

2222
// ---
2323

24-
#[derive(Debug, Deserialize, Clone)]
24+
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
2525
#[serde(rename_all = "kebab-case")]
2626
pub struct Settings {
2727
pub fields: Fields,
@@ -33,22 +33,15 @@ pub struct Settings {
3333
}
3434

3535
impl Settings {
36-
pub fn load(filename: &str) -> Result<Self, Error> {
37-
Ok(Config::builder()
38-
.add_source(File::from_str(DEFAULT_SETTINGS_RAW, FileFormat::Yaml))
39-
.add_source(File::with_name(filename))
40-
.build()?
41-
.try_deserialize()?)
42-
}
43-
44-
pub fn load_from_str(value: &str, format: FileFormat) -> Self {
45-
Config::builder()
46-
.add_source(File::from_str(DEFAULT_SETTINGS_RAW, FileFormat::Yaml))
47-
.add_source(File::from_str(value, format))
48-
.build()
49-
.unwrap()
50-
.try_deserialize()
51-
.unwrap()
36+
pub fn load(source: Source) -> Result<Self, Error> {
37+
let builder = Config::builder().add_source(File::from_str(DEFAULT_SETTINGS_RAW, FileFormat::Yaml));
38+
let builder = match source {
39+
Source::File(SourceFile { filename, required }) => {
40+
builder.add_source(File::with_name(filename).required(required))
41+
}
42+
Source::Str(value, format) => builder.add_source(File::from_str(value, format)),
43+
};
44+
Ok(builder.build()?.try_deserialize()?)
5245
}
5346
}
5447

@@ -66,7 +59,40 @@ impl Default for &'static Settings {
6659

6760
// ---
6861

69-
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
62+
pub enum Source<'a> {
63+
File(SourceFile<'a>),
64+
Str(&'a str, FileFormat),
65+
}
66+
67+
impl<'a> From<SourceFile<'a>> for Source<'a> {
68+
fn from(file: SourceFile<'a>) -> Self {
69+
Self::File(file)
70+
}
71+
}
72+
73+
// ---
74+
75+
pub struct SourceFile<'a> {
76+
filename: &'a str,
77+
required: bool,
78+
}
79+
80+
impl<'a> SourceFile<'a> {
81+
pub fn new(filename: &'a str) -> Self {
82+
Self {
83+
filename,
84+
required: true,
85+
}
86+
}
87+
88+
pub fn required(self, required: bool) -> Self {
89+
Self { required, ..self }
90+
}
91+
}
92+
93+
// ---
94+
95+
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq)]
7096
pub struct Fields {
7197
pub predefined: PredefinedFields,
7298
pub ignore: Vec<String>,
@@ -75,7 +101,7 @@ pub struct Fields {
75101

76102
// ---
77103

78-
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
104+
#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq, Eq)]
79105
#[serde(rename_all = "kebab-case")]
80106
pub struct PredefinedFields {
81107
pub time: TimeField,
@@ -100,7 +126,7 @@ impl Default for TimeField {
100126

101127
// ---
102128

103-
#[derive(Debug, Serialize, Deserialize, Clone)]
129+
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
104130
pub struct LevelField {
105131
pub show: FieldShowOption,
106132
pub variants: Vec<LevelFieldVariant>,
@@ -123,7 +149,7 @@ impl Default for LevelField {
123149

124150
// ---
125151

126-
#[derive(Debug, Serialize, Deserialize, Clone)]
152+
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
127153
pub struct LevelFieldVariant {
128154
pub names: Vec<String>,
129155
#[serde(default, serialize_with = "ordered_map_serialize")]
@@ -133,8 +159,8 @@ pub struct LevelFieldVariant {
133159

134160
// ---
135161

136-
#[derive(Debug, Serialize, Deserialize, Deref, Clone)]
137-
pub struct MessageField(Field);
162+
#[derive(Debug, Serialize, Deserialize, Deref, Clone, PartialEq, Eq)]
163+
pub struct MessageField(pub Field);
138164

139165
impl Default for MessageField {
140166
fn default() -> Self {
@@ -144,7 +170,7 @@ impl Default for MessageField {
144170

145171
// ---
146172

147-
#[derive(Debug, Serialize, Deserialize, Deref, Clone)]
173+
#[derive(Debug, Serialize, Deserialize, Deref, Clone, PartialEq, Eq)]
148174
pub struct LoggerField(Field);
149175

150176
impl Default for LoggerField {
@@ -155,7 +181,7 @@ impl Default for LoggerField {
155181

156182
// ---
157183

158-
#[derive(Debug, Serialize, Deserialize, Deref, Clone)]
184+
#[derive(Debug, Serialize, Deserialize, Deref, Clone, PartialEq, Eq)]
159185
pub struct CallerField(Field);
160186

161187
impl Default for CallerField {
@@ -166,7 +192,7 @@ impl Default for CallerField {
166192

167193
// ---
168194

169-
#[derive(Debug, Serialize, Deserialize, Deref, Clone)]
195+
#[derive(Debug, Serialize, Deserialize, Deref, Clone, PartialEq, Eq)]
170196
pub struct CallerFileField(Field);
171197

172198
impl Default for CallerFileField {
@@ -177,7 +203,7 @@ impl Default for CallerFileField {
177203

178204
// ---
179205

180-
#[derive(Debug, Serialize, Deserialize, Deref, Clone)]
206+
#[derive(Debug, Serialize, Deserialize, Deref, Clone, PartialEq, Eq)]
181207
pub struct CallerLineField(Field);
182208

183209
impl Default for CallerLineField {
@@ -206,7 +232,7 @@ impl Field {
206232

207233
// ---
208234

209-
#[derive(Clone, Debug, Default, Deserialize)]
235+
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq)]
210236
#[serde(rename_all = "kebab-case")]
211237
pub struct Formatting {
212238
pub punctuation: Punctuation,
@@ -215,7 +241,7 @@ pub struct Formatting {
215241

216242
// ---
217243

218-
#[derive(Clone, Debug, Deserialize)]
244+
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
219245
#[serde(rename_all = "kebab-case")]
220246
pub enum FlattenOption {
221247
Never,
@@ -239,7 +265,7 @@ impl Default for FieldShowOption {
239265

240266
// ---
241267

242-
#[derive(Debug, Deserialize, Clone)]
268+
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
243269
#[serde(rename_all = "kebab-case")]
244270
pub struct Punctuation {
245271
pub logger_name_separator: String,
@@ -338,7 +364,7 @@ mod tests {
338364

339365
#[test]
340366
fn test_load_settings_k8s() {
341-
let settings = Settings::load("etc/defaults/config-k8s.yaml").unwrap();
367+
let settings = Settings::load(SourceFile::new("etc/defaults/config-k8s.yaml").into()).unwrap();
342368
assert_eq!(
343369
settings.fields.predefined.time,
344370
TimeField(Field {

0 commit comments

Comments
 (0)