nimbus/
defaults.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use crate::error::Result;
6
7/// Simple trait to allow merging of similar objects.
8///
9/// Different names might be more applicable: merging, defaulting, patching.
10///
11/// In all cases: the `defaults` method takes a reference to a `self` and
12/// a `fallback`. The `self` acts as a patch over the `fallback`, and a new
13/// version is the result.
14///
15/// Implementations of the trait can error. In the case of recursive implementations,
16/// other implementations may catch and recover from the error, or propagate it.
17///
18/// Context: Feature JSON is used to configure a application feature.
19/// If a value is needed, the application provides a default.
20/// A rollout changes this default.
21pub trait Defaults {
22    fn defaults(&self, fallback: &Self) -> Result<Self>
23    where
24        Self: Sized;
25}
26
27impl<T: Defaults + Clone> Defaults for Option<T> {
28    fn defaults(&self, fallback: &Self) -> Result<Self> {
29        Ok(match (self, fallback) {
30            (Some(a), Some(b)) => Some(a.defaults(b)?),
31            (Some(_), None) => self.clone(),
32            _ => fallback.clone(),
33        })
34    }
35}
36
37use serde_json::{Map, Value};
38/// We implement https://datatracker.ietf.org/doc/html/rfc7396
39/// such that self is patching the fallback.
40/// The result is the patched object.
41///
42/// * If a self value is null, we take that to equivalent to a delete.
43/// * If both self and fallback are objects, we recursively patch.
44/// * If it exists in either in self or clone, then it is included
45/// * in the result.
46/// * If it exists in both, then we take the self version.
47impl Defaults for Value {
48    fn defaults(&self, fallback: &Self) -> Result<Self> {
49        Ok(match (self, fallback) {
50            (Value::Object(a), Value::Object(b)) => Value::Object(a.defaults(b)?),
51            (Value::Null, _) => fallback.to_owned(),
52            _ => self.to_owned(),
53        })
54    }
55}
56
57impl Defaults for Map<String, Value> {
58    fn defaults(&self, fallback: &Self) -> Result<Self> {
59        let mut map = self.clone();
60        for (k, fb) in fallback {
61            match map.get(k) {
62                Some(existing) if existing.is_null() => {
63                    map.remove(k);
64                }
65                Some(existing) => {
66                    // JSON merging should't error, so there'll be
67                    // nothing to propagate.
68                    map[k] = existing.defaults(fb)?;
69                }
70                _ => {
71                    map.insert(k.clone(), fb.clone());
72                }
73            };
74        }
75        Ok(map)
76    }
77}
78
79use std::collections::HashMap;
80/// Merge the two `HashMap`s, with self acting as the dominant
81/// of the two.
82///
83/// Nimbus' use case is to be merging data coming from the outside,
84/// we should not allow a bad merge to bring the whole system down.
85///
86/// Where values merging fails, we go with the newest version.
87impl<T: Defaults + Clone> Defaults for HashMap<String, T> {
88    fn defaults(&self, fallback: &Self) -> Result<Self> {
89        let mut map = self.clone();
90        for (k, fb) in fallback {
91            match map.get(k) {
92                Some(existing) => {
93                    // if we merged with fb without errors,
94                    if let Ok(v) = existing.defaults(fb) {
95                        map.insert(k.clone(), v);
96                    } // otherwise use the self value, without merging.
97                }
98                _ => {
99                    map.insert(k.clone(), fb.clone());
100                }
101            }
102        }
103        Ok(map)
104    }
105}