use std::time::Instant;
use cfgmatic_merge::{Merge, MergeBehavior, MergeOptions, TrackedLayer};
use crate::config::MergeStrategy;
use crate::domain::{Format, ParsedContent, RawContent, Result, Source, SourceError};
pub fn load_source_content(
source: &dyn Source,
default_format: Option<Format>,
) -> Result<ParsedContent> {
source.validate()?;
let raw = source.load_raw()?;
parse_raw_content(&raw, source.detect_format(), default_format)
}
pub fn parse_raw_content(
raw: &RawContent,
detected_format: Option<Format>,
default_format: Option<Format>,
) -> Result<ParsedContent> {
let format = normalize_format(detected_format).or(default_format);
if let Some(format) = format {
parse_raw_as_format(raw, format)
} else {
let content = raw.as_str()?;
if let Some(format) = Format::from_content(content.as_ref()) {
return format.parse(content.as_ref());
}
Err(SourceError::unsupported("cannot detect format"))
}
}
pub fn parse_raw_as_format(raw: &RawContent, format: Format) -> Result<ParsedContent> {
let content = raw.as_str()?;
format.parse(content.as_ref())
}
pub fn merge_contents(
contents: Vec<ParsedContent>,
strategy: MergeStrategy,
) -> Result<ParsedContent> {
let mut iter = contents.into_iter();
let Some(first) = iter.next() else {
return Ok(ParsedContent::Null);
};
let options = merge_options(strategy);
let mut merged = serde_json::to_value(first)
.map_err(|error| SourceError::serialization(&error.to_string()))?;
for next in iter {
let next = serde_json::to_value(next)
.map_err(|error| SourceError::serialization(&error.to_string()))?;
merged = merged
.merge(next, &options)
.map_err(|error| SourceError::custom(&error.to_string()))?;
}
Ok(ParsedContent::from_json(merged))
}
pub fn to_tracked_layer(layer: &crate::application::SourceLayer) -> Result<TrackedLayer> {
let content = serde_json::to_value(&layer.content)
.map_err(|error| SourceError::serialization(&error.to_string()))?;
Ok(TrackedLayer::new(
layer.source_id(),
layer.priority,
layer.registration_index,
content,
))
}
fn normalize_format(format: Option<Format>) -> Option<Format> {
format.and_then(|format| (format != Format::Unknown).then_some(format))
}
pub(super) fn merge_options(strategy: MergeStrategy) -> MergeOptions {
let behavior = match strategy {
MergeStrategy::Replace => MergeBehavior::Replace,
MergeStrategy::Deep => MergeBehavior::Deep,
MergeStrategy::Shallow => MergeBehavior::Shallow,
MergeStrategy::Strict => MergeBehavior::Error,
};
MergeOptions::new()
.behavior(behavior)
.strict(matches!(strategy, MergeStrategy::Strict))
}
#[must_use]
pub fn elapsed_millis_u64(start: Instant) -> u64 {
u64::try_from(start.elapsed().as_millis()).unwrap_or(u64::MAX)
}