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