1use derive_more::Display;
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7use crate::errors::{ProfileError, Result};
8use crate::merge::Driver;
9use crate::name::ProfileName;
10use crate::resolve::Layer;
11
12#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
14#[serde(deny_unknown_fields, rename_all = "kebab-case")]
15pub struct MergeSpec {
16 #[serde(default, skip_serializing_if = "Option::is_none")]
18 pub on_conflict: Option<OnConflict>,
19 #[serde(default, skip_serializing_if = "Option::is_none")]
23 #[schemars(with = "Option<String>")]
24 pub driver: Option<Driver>,
25}
26
27#[derive(
29 Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Display,
30)]
31#[serde(rename_all = "kebab-case")]
32#[display(rename_all = "kebab-case")]
33pub enum OnConflict {
34 #[default]
36 ApplyOthers,
37 ApplyNone,
39}
40
41#[derive(Clone, Debug, Default)]
43pub struct Settings {
44 pub on_conflict: OnConflict,
46 pub driver: Driver,
48}
49
50#[derive(Clone, Debug)]
52pub struct Suggestion {
53 pub layer: ProfileName,
55 pub driver: Driver,
57}
58
59impl Settings {
60 pub(crate) fn compose(layers: &[Layer], target: &MergeSpec) -> Result<Self> {
62 let on_conflict = match target.on_conflict {
63 Some(value) => value,
64 None => {
65 agreed(layers, "merge.on-conflict", |merge| merge.on_conflict)?.unwrap_or_default()
66 },
67 };
68 let driver = target.driver.clone().unwrap_or_default();
69 Ok(Self { on_conflict, driver })
70 }
71
72 pub(crate) fn suggestions(layers: &[Layer], target: &MergeSpec) -> Vec<Suggestion> {
74 if target.driver.is_some() {
75 return Vec::new();
76 }
77 layers
78 .iter()
79 .filter_map(|layer| {
80 let driver = layer.merge().driver.clone()?;
81 Some(Suggestion { layer: layer.name().clone(), driver })
82 })
83 .collect()
84 }
85}
86
87fn agreed<T: Copy + PartialEq>(
89 layers: &[Layer], key: &'static str, get: fn(&MergeSpec) -> Option<T>,
90) -> Result<Option<T>> {
91 let mut agreed: Option<(T, &Layer)> = None;
92 for layer in layers {
93 let Some(value) = get(layer.merge()) else {
94 continue;
95 };
96 match agreed {
97 Some((first, by)) if first != value => {
98 let (first, second) = (by.name().clone(), layer.name().clone());
99 return Err(ProfileError::Setting { key: key.to_owned(), first, second }.into());
100 },
101 Some(_) => {},
102 None => agreed = Some((value, layer)),
103 }
104 }
105 Ok(agreed.map(|(value, _)| value))
106}