risingwave_common/config/
utils.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 super::*;
16
17/// Unrecognized fields in a config section. Generic over the config section type to provide better
18/// error messages.
19///
20/// The current implementation will log warnings if there are unrecognized fields.
21#[derive(Educe)]
22#[educe(Clone, Default)]
23pub struct Unrecognized<T: 'static> {
24    inner: BTreeMap<String, Value>,
25    _marker: std::marker::PhantomData<&'static T>,
26}
27
28impl<T> std::fmt::Debug for Unrecognized<T> {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        self.inner.fmt(f)
31    }
32}
33
34impl<T> Unrecognized<T> {
35    /// Returns all unrecognized fields as a map.
36    pub fn into_inner(self) -> BTreeMap<String, Value> {
37        self.inner
38    }
39}
40
41impl<'de, T> Deserialize<'de> for Unrecognized<T> {
42    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
43    where
44        D: serde::Deserializer<'de>,
45    {
46        let inner = BTreeMap::deserialize(deserializer)?;
47        if !inner.is_empty() {
48            tracing::warn!(
49                "unrecognized fields in `{}`: {:?}",
50                std::any::type_name::<T>(),
51                inner.keys()
52            );
53        }
54        Ok(Unrecognized {
55            inner,
56            _marker: std::marker::PhantomData,
57        })
58    }
59}
60
61impl<T> Serialize for Unrecognized<T> {
62    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
63    where
64        S: serde::Serializer,
65    {
66        self.inner.serialize(serializer)
67    }
68}
69
70pub fn load_config(path: &str, cli_override: impl OverrideConfig) -> RwConfig
71where
72{
73    let mut config = if path.is_empty() {
74        tracing::warn!("risingwave.toml not found, using default config.");
75        RwConfig::default()
76    } else {
77        let config_str = fs::read_to_string(path)
78            .with_context(|| format!("failed to open config file at `{path}`"))
79            .unwrap();
80        toml::from_str(config_str.as_str())
81            .context("failed to parse config file")
82            .unwrap()
83    };
84    cli_override.r#override(&mut config);
85    config
86}
87
88pub trait OverrideConfig {
89    fn r#override(&self, config: &mut RwConfig);
90}
91
92impl<T: OverrideConfig> OverrideConfig for &T {
93    fn r#override(&self, config: &mut RwConfig) {
94        T::r#override(self, config)
95    }
96}
97
98/// For non-user-facing components where the CLI arguments do not override the config file.
99#[derive(Clone, Copy)]
100pub struct NoOverride;
101
102impl OverrideConfig for NoOverride {
103    fn r#override(&self, _config: &mut RwConfig) {}
104}