risingwave_common/config/
mutate.rs

1// Copyright 2025 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use anyhow::Context;
16use itertools::Itertools as _;
17use serde::Serialize;
18use toml::{Table, Value};
19
20def_anyhow_newtype! {
21    /// Error type for mutating a configuration [`toml::Table`].
22    pub ConfigMutateError,
23
24    toml::ser::Error => transparent,
25}
26
27enum Op {
28    Upsert(Value),
29    Delete,
30}
31
32/// Mutate a [`toml::Table`] at the given dot-separated path.
33///
34/// Returns an error if the path is invalid.
35fn mutate(map: &mut Table, path: &str, op: Op) -> Result<(), ConfigMutateError> {
36    let segments = path.split('.').collect_vec();
37    let (key, segments) = segments.split_last().context("empty path")?;
38
39    let mut map = map;
40    for segment in segments {
41        use toml::map::Entry;
42
43        let sub_map = match map.entry(segment.to_owned()) {
44            Entry::Occupied(entry) => entry.into_mut(),
45            Entry::Vacant(entry) => match &op {
46                // Create new tables if not exists.
47                Op::Upsert(_) => entry.insert(Value::Table(Table::new())),
48                // Delete from a non-existing path is a no-op.
49                Op::Delete => return Ok(()),
50            },
51        }
52        .as_table_mut()
53        .with_context(|| format!("expect a table at {segment}"))?;
54
55        map = sub_map;
56    }
57
58    match op {
59        Op::Upsert(value) => {
60            map.insert(key.to_string(), value);
61        }
62        Op::Delete => {
63            map.remove(*key);
64        }
65    }
66
67    Ok(())
68}
69
70/// Extension trait for [`toml::Table`] to mutate values at a given dot-separated path.
71#[easy_ext::ext(TomlTableMutateExt)]
72impl Table {
73    /// Upsert a value at the given dot-separated path.
74    ///
75    /// Returns an error if the path or the value is invalid.
76    pub fn upsert(&mut self, path: &str, value: impl Serialize) -> Result<(), ConfigMutateError> {
77        let value = Value::try_from(value)?;
78        mutate(self, path, Op::Upsert(value))
79    }
80
81    /// Delete a value at the given path.
82    ///
83    /// Returns an error if the path is invalid.
84    pub fn delete(&mut self, path: &str) -> Result<(), ConfigMutateError> {
85        mutate(self, path, Op::Delete)
86    }
87}