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 inner(&self) -> &BTreeMap<String, Value> {
37        &self.inner
38    }
39
40    /// Returns all unrecognized fields as a map.
41    pub fn into_inner(self) -> BTreeMap<String, Value> {
42        self.inner
43    }
44}
45
46impl<'de, T> Deserialize<'de> for Unrecognized<T> {
47    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
48    where
49        D: serde::Deserializer<'de>,
50    {
51        let inner = BTreeMap::deserialize(deserializer)?;
52        if !inner.is_empty() {
53            tracing::warn!(
54                "unrecognized fields in `{}`: {:?}",
55                std::any::type_name::<T>(),
56                inner.keys()
57            );
58        }
59        Ok(Unrecognized {
60            inner,
61            _marker: std::marker::PhantomData,
62        })
63    }
64}
65
66impl<T> Serialize for Unrecognized<T> {
67    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
68    where
69        S: serde::Serializer,
70    {
71        self.inner.serialize(serializer)
72    }
73}
74
75pub fn load_config(path: &str, cli_override: impl OverrideConfig) -> RwConfig
76where
77{
78    let mut config = if path.is_empty() {
79        tracing::warn!("risingwave.toml not found, using default config.");
80        RwConfig::default()
81    } else {
82        let config_str = fs::read_to_string(path)
83            .with_context(|| format!("failed to open config file at `{path}`"))
84            .unwrap();
85        toml::from_str(config_str.as_str())
86            .context("failed to parse config file")
87            .unwrap()
88    };
89    cli_override.r#override(&mut config);
90    config
91}
92
93pub trait OverrideConfig {
94    fn r#override(&self, config: &mut RwConfig);
95}
96
97impl<T: OverrideConfig> OverrideConfig for &T {
98    fn r#override(&self, config: &mut RwConfig) {
99        T::r#override(self, config)
100    }
101}
102
103/// For non-user-facing components where the CLI arguments do not override the config file.
104#[derive(Clone, Copy)]
105pub struct NoOverride;
106
107impl OverrideConfig for NoOverride {
108    fn r#override(&self, _config: &mut RwConfig) {}
109}