use ortho_config::{MapEnv, MergeLayer, OrthoResult, SharedEnvSource, load_config_file_as_chain};
use std::borrow::Cow;
use std::io;
use std::path::Path;
use std::sync::Arc;
use super::command::Cli;
#[path = "discovery_environment.rs"]
mod environment;
pub use environment::{EnvProvider, StdEnvProvider};
#[path = "discovery_diagnostics.rs"]
mod diagnostics;
#[path = "discovery_json.rs"]
mod json;
#[path = "discovery_layers.rs"]
mod layers;
#[path = "discovery_paths.rs"]
mod paths;
#[path = "discovery_selector.rs"]
mod selector;
#[path = "discovery_trace.rs"]
mod trace;
#[path = "discovery_telemetry.rs"]
mod telemetry;
use diagnostics::{BoundedConfigPath, ConfigLoadFailureKind, ConfigLoadWarning};
use layers::collect_file_layers_with_normalizer_and_trace;
use paths::{FsPathNormalizer, PathNormalizer};
#[cfg(test)]
use selector::{
ConfigPathResolution, env_config_path, explicit_config_path_with_env, resolve_config_selector,
};
#[cfg(test)]
use std::path::PathBuf;
pub use telemetry::record_discovery_outcome;
use trace::{DiscoveryDiagnostics, DiscoveryTrace, FileLayerTrace};
#[path = "discovery_merge_layers.rs"]
mod merge_layers;
pub(crate) use merge_layers::push_discovered_file_layers;
const CONFIG_ENV_VAR: &str = "NETSUKE_CONFIG";
const DISCOVERY_ENV_KEYS: [&str; 7] = [
CONFIG_ENV_VAR,
"HOME",
"USERPROFILE",
"XDG_CONFIG_HOME",
"XDG_CONFIG_DIRS",
"APPDATA",
"LOCALAPPDATA",
];
pub struct DiscoveredLayers {
layers: Vec<MergeLayer<'static>>,
json_preference: bool,
errors: Vec<Arc<ortho_config::OrthoError>>,
diagnostics: DiscoveryDiagnostics,
}
impl DiscoveredLayers {
#[cfg(test)]
pub(crate) fn layers(&self) -> &[MergeLayer<'static>] {
&self.layers
}
pub(crate) fn first_error(&self) -> Option<&Arc<ortho_config::OrthoError>> {
self.errors.first()
}
pub(crate) const fn json_preference(&self) -> bool {
self.json_preference
}
pub(crate) fn into_parts(
self,
) -> (Vec<MergeLayer<'static>>, Vec<Arc<ortho_config::OrthoError>>) {
(self.layers, self.errors)
}
}
pub struct DiscoveryOutcome {
layers: DiscoveredLayers,
}
impl DiscoveryOutcome {
#[cfg(test)]
pub(crate) fn layers(&self) -> &[MergeLayer<'static>] {
self.layers.layers()
}
pub(crate) fn first_error(&self) -> Option<&Arc<ortho_config::OrthoError>> {
self.layers.first_error()
}
pub(crate) const fn json_preference(&self) -> bool {
self.layers.json_preference()
}
#[must_use]
pub fn into_layers(self) -> DiscoveredLayers {
self.layers
}
pub fn emit_diagnostics(&self) {
self.layers.diagnostics.emit();
}
}
pub(crate) fn discover_file_layers(cli: &Cli, env: &impl EnvProvider) -> DiscoveryOutcome {
discover_file_layers_with_normalizer(cli, env, &FsPathNormalizer)
}
fn discover_file_layers_with_normalizer(
cli: &Cli,
env: &impl EnvProvider,
normalizer: &impl PathNormalizer,
) -> DiscoveryOutcome {
let (trace, load_warning, outcome) = collect_file_layers_with_env(cli, env, normalizer);
let diagnostics = DiscoveryDiagnostics::new(trace, load_warning);
let layers = match outcome {
Ok(discovered_layers) => {
let (layers, json_preference) =
layers::retain_layers_and_resolve_json(discovered_layers);
DiscoveredLayers {
layers,
json_preference,
errors: Vec::new(),
diagnostics,
}
}
Err(error) => DiscoveredLayers {
layers: Vec::new(),
json_preference: Cli::default().json,
errors: vec![error],
diagnostics,
},
};
DiscoveryOutcome { layers }
}
fn collect_file_layers_with_env(
cli: &Cli,
env: &impl EnvProvider,
normalizer: &impl PathNormalizer,
) -> (
DiscoveryTrace,
Option<ConfigLoadWarning>,
OrthoResult<Vec<MergeLayer<'static>>>,
) {
let resolution = selector::resolve_config_selector(cli.config.clone(), env);
let (file_layers, load_warning, outcome) = resolution.path.as_deref().map_or_else(
|| {
let (project_scope, outcome) = collect_file_layers_with_normalizer_and_trace(
cli.directory.as_deref().map(camino::Utf8Path::as_std_path),
normalizer,
discovery_env_source(env),
);
(FileLayerTrace::Automatic { project_scope }, None, outcome)
},
|path| {
let (load_warning, outcome) = load_layers_from_path_with_warning(path);
(
FileLayerTrace::Explicit {
path: BoundedConfigPath::from_path(Some(path)),
},
load_warning,
outcome,
)
},
);
(
DiscoveryTrace::new(&resolution, file_layers),
load_warning,
outcome,
)
}
pub(crate) fn discovery_env_source(env: &impl EnvProvider) -> SharedEnvSource {
let mut source = MapEnv::new();
for key in DISCOVERY_ENV_KEYS {
if let Some(value) = env.get(key) {
source.insert(key, value);
}
}
Arc::new(source)
}
#[cfg(test)]
pub(crate) fn load_layers_from_path(path: &Path) -> OrthoResult<Vec<MergeLayer<'static>>> {
let (load_warning, result) = load_layers_from_path_with_warning(path);
if let Some(warning) = load_warning {
warning.emit();
}
result
}
fn load_layers_from_path_with_warning(
path: &Path,
) -> (
Option<ConfigLoadWarning>,
OrthoResult<Vec<MergeLayer<'static>>>,
) {
match load_config_file_as_chain(path) {
Ok(Some(chain)) => (
None,
Ok(chain
.values
.into_iter()
.map(|(value, layer_path)| MergeLayer::file(Cow::Owned(value), Some(layer_path)))
.collect()),
),
Ok(None) => {
let error = Arc::new(ortho_config::OrthoError::File {
path: path.to_path_buf(),
source: Box::new(io::Error::new(
io::ErrorKind::NotFound,
"explicit configuration file not found",
)),
});
(
Some(ConfigLoadWarning::new(path, ConfigLoadFailureKind::Missing)),
Err(error),
)
}
Err(error) => (
Some(ConfigLoadWarning::new(
path,
ConfigLoadFailureKind::LoadError,
)),
Err(error),
),
}
}
pub(crate) fn collect_diag_file_layers_with_env(
cli: &Cli,
env: &impl EnvProvider,
) -> DiscoveryOutcome {
discover_file_layers(cli, env)
}
#[cfg(test)]
#[path = "discovery_event_assertions.rs"]
mod event_assertions;
#[cfg(test)]
#[path = "discovery_tracing_tests.rs"]
mod tracing_tests;
#[cfg(test)]
#[path = "discovery_helper_proptests.rs"]
mod helper_proptests;
#[cfg(test)]
#[path = "discovery_layer_replay_tests.rs"]
mod layer_replay_tests;
#[cfg(test)]
#[path = "discovery_layer_selector_tests.rs"]
mod layer_selector_tests;
#[cfg(test)]
#[path = "discovery_layer_tests.rs"]
mod layer_tests;
#[cfg(test)]
#[path = "discovery_replay_proptests.rs"]
mod replay_proptests;
#[cfg(test)]
#[path = "config_path_precedence_tests.rs"]
mod config_path_precedence_tests;
#[cfg(test)]
#[path = "discovery_unit_tests.rs"]
mod unit_tests;