use std::borrow::Cow;
use std::sync::Arc;
use camino::Utf8PathBuf;
use serde_json::Value;
use crate::{OrthoError, OrthoResult};
use super::MergeLayer;
#[derive(Default)]
pub struct MergeComposer {
layers: Vec<MergeLayer<'static>>,
}
impl MergeComposer {
#[must_use]
pub const fn new() -> Self {
Self { layers: Vec::new() }
}
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
Self {
layers: Vec::with_capacity(capacity),
}
}
pub fn push_defaults(&mut self, value: Value) {
self.push_layer(MergeLayer::defaults(Cow::Owned(value)));
}
pub fn push_file(&mut self, value: Value, path: Option<Utf8PathBuf>) {
self.push_layer(MergeLayer::file(Cow::Owned(value), path));
}
pub fn push_environment(&mut self, value: Value) {
self.push_layer(MergeLayer::environment(Cow::Owned(value)));
}
pub fn push_cli(&mut self, value: Value) {
self.push_layer(MergeLayer::cli(Cow::Owned(value)));
}
pub fn push_layer(&mut self, layer: MergeLayer<'static>) {
self.layers.push(layer);
}
#[must_use]
pub fn layers(self) -> Vec<MergeLayer<'static>> {
self.layers
}
}
#[derive(Debug)]
pub struct LayerComposition {
layers: Vec<MergeLayer<'static>>,
errors: Vec<Arc<OrthoError>>,
}
impl LayerComposition {
#[must_use]
#[expect(
clippy::missing_const_for_fn,
reason = "Constructing Vec-based compositions requires allocation"
)]
pub fn new(layers: Vec<MergeLayer<'static>>, errors: Vec<Arc<OrthoError>>) -> Self {
Self { layers, errors }
}
#[must_use]
pub fn into_parts(self) -> (Vec<MergeLayer<'static>>, Vec<Arc<OrthoError>>) {
(self.layers, self.errors)
}
#[must_use]
#[expect(
clippy::missing_const_for_fn,
reason = "Borrowing the error buffer is not const in stable Rust"
)]
pub fn has_errors(&self) -> bool {
!self.errors.is_empty()
}
fn errors_to_result<T>(mut errors: Vec<Arc<OrthoError>>) -> OrthoResult<T> {
if errors.len() == 1 {
Err(errors.remove(0))
} else {
Err(Arc::new(OrthoError::aggregate(errors)))
}
}
pub fn into_merge_result<T, F>(self, merge: F) -> OrthoResult<T>
where
F: FnOnce(Vec<MergeLayer<'static>>) -> OrthoResult<T>,
{
let (layers, mut errors) = self.into_parts();
match merge(layers) {
Ok(cfg) => {
if errors.is_empty() {
Ok(cfg)
} else {
Self::errors_to_result(errors)
}
}
Err(err) => {
errors.push(err);
Self::errors_to_result(errors)
}
}
}
}
impl IntoIterator for MergeComposer {
type Item = MergeLayer<'static>;
type IntoIter = std::vec::IntoIter<MergeLayer<'static>>;
fn into_iter(self) -> Self::IntoIter {
self.layers.into_iter()
}
}