LCOV - code coverage report
Current view: top level - pageserver/src/config - ignored_fields.rs (source / functions) Coverage Total Hit
Test: 98683a8629f0f7f0031d02e04512998d589d76ea.info Lines: 99.2 % 123 122
Test Date: 2025-04-11 16:58:57 Functions: 100.0 % 11 11

            Line data    Source code
       1              : //! Check for fields in the on-disk config file that were ignored when
       2              : //! deserializing [`pageserver_api::config::ConfigToml`].
       3              : //!
       4              : //! This could have been part of the [`pageserver_api::config`] module,
       5              : //! but the way we identify unused fields in this module
       6              : //! is specific to the format (TOML) and the implementation of the
       7              : //! deserialization for that format ([`toml_edit`]).
       8              : 
       9              : use std::collections::HashSet;
      10              : 
      11              : use itertools::Itertools;
      12              : 
      13              : /// Pass in the user-specified config and the re-serialized [`pageserver_api::config::ConfigToml`].
      14              : /// The returned [`Paths`] contains the paths to the fields that were ignored by deserialization
      15              : /// of the [`pageserver_api::config::ConfigToml`].
      16           16 : pub fn find(user_specified: toml_edit::DocumentMut, reserialized: toml_edit::DocumentMut) -> Paths {
      17           16 :     let user_specified = paths(user_specified);
      18           16 :     let reserialized = paths(reserialized);
      19           32 :     fn paths(doc: toml_edit::DocumentMut) -> HashSet<String> {
      20           32 :         let mut out = Vec::new();
      21           32 :         let mut visitor = PathsVisitor::new(&mut out);
      22           32 :         visitor.visit_table_like(doc.as_table());
      23           32 :         HashSet::from_iter(out)
      24           32 :     }
      25              : 
      26           16 :     let mut ignored = HashSet::new();
      27              : 
      28              :     // O(n) because of HashSet
      29           48 :     for path in user_specified {
      30           32 :         if !reserialized.contains(&path) {
      31           16 :             ignored.insert(path);
      32           16 :         }
      33              :     }
      34              : 
      35           16 :     Paths {
      36           16 :         paths: ignored
      37           16 :             .into_iter()
      38           16 :             // sort lexicographically for deterministic output
      39           16 :             .sorted()
      40           16 :             .collect(),
      41           16 :     }
      42           16 : }
      43              : 
      44              : pub struct Paths {
      45              :     pub paths: Vec<String>,
      46              : }
      47              : 
      48              : struct PathsVisitor<'a> {
      49              :     stack: Vec<String>,
      50              :     out: &'a mut Vec<String>,
      51              : }
      52              : 
      53              : impl<'a> PathsVisitor<'a> {
      54           32 :     fn new(out: &'a mut Vec<String>) -> Self {
      55           32 :         Self {
      56           32 :             stack: Vec::new(),
      57           32 :             out,
      58           32 :         }
      59           32 :     }
      60              : 
      61           72 :     fn visit_table_like(&mut self, table_like: &dyn toml_edit::TableLike) {
      62           96 :         for (entry, item) in table_like.iter() {
      63           96 :             self.stack.push(entry.to_string());
      64           96 :             self.visit_item(item);
      65           96 :             self.stack.pop();
      66           96 :         }
      67           72 :     }
      68              : 
      69           96 :     fn visit_item(&mut self, item: &toml_edit::Item) {
      70           96 :         match item {
      71            0 :             toml_edit::Item::None => (),
      72           64 :             toml_edit::Item::Value(value) => self.visit_value(value),
      73           24 :             toml_edit::Item::Table(table) => {
      74           24 :                 self.visit_table_like(table);
      75           24 :             }
      76            8 :             toml_edit::Item::ArrayOfTables(array_of_tables) => {
      77            8 :                 for (i, table) in array_of_tables.iter().enumerate() {
      78            8 :                     self.stack.push(format!("[{i}]"));
      79            8 :                     self.visit_table_like(table);
      80            8 :                     self.stack.pop();
      81            8 :                 }
      82              :             }
      83              :         }
      84           96 :     }
      85              : 
      86           72 :     fn visit_value(&mut self, value: &toml_edit::Value) {
      87           72 :         match value {
      88              :             toml_edit::Value::String(_)
      89              :             | toml_edit::Value::Integer(_)
      90              :             | toml_edit::Value::Float(_)
      91              :             | toml_edit::Value::Boolean(_)
      92           56 :             | toml_edit::Value::Datetime(_) => self.out.push(self.stack.join(".")),
      93            8 :             toml_edit::Value::Array(array) => {
      94            8 :                 for (i, value) in array.iter().enumerate() {
      95            8 :                     self.stack.push(format!("[{i}]"));
      96            8 :                     self.visit_value(value);
      97            8 :                     self.stack.pop();
      98            8 :                 }
      99              :             }
     100            8 :             toml_edit::Value::InlineTable(inline_table) => self.visit_table_like(inline_table),
     101              :         }
     102           72 :     }
     103              : }
     104              : 
     105              : #[cfg(test)]
     106              : pub(crate) mod tests {
     107              : 
     108           16 :     fn test_impl(original: &str, parsed: &str, expect: [&str; 1]) {
     109           16 :         let original: toml_edit::DocumentMut = original.parse().expect("parse original config");
     110           16 :         let parsed: toml_edit::DocumentMut = parsed.parse().expect("parse re-serialized config");
     111           16 : 
     112           16 :         let super::Paths { paths: actual } = super::find(original, parsed);
     113           16 :         assert_eq!(actual, &expect);
     114           16 :     }
     115              : 
     116              :     #[test]
     117            4 :     fn top_level() {
     118            4 :         test_impl(
     119            4 :             r#"
     120            4 :                 [a]
     121            4 :                 b = 1
     122            4 :                 c = 2
     123            4 :                 d = 3
     124            4 :             "#,
     125            4 :             r#"
     126            4 :                 [a]
     127            4 :                 b = 1
     128            4 :                 c = 2
     129            4 :             "#,
     130            4 :             ["a.d"],
     131            4 :         );
     132            4 :     }
     133              : 
     134              :     #[test]
     135            4 :     fn nested() {
     136            4 :         test_impl(
     137            4 :             r#"
     138            4 :                 [a.b.c]
     139            4 :                 d = 23
     140            4 :             "#,
     141            4 :             r#"
     142            4 :                 [a]
     143            4 :                 e = 42
     144            4 :             "#,
     145            4 :             ["a.b.c.d"],
     146            4 :         );
     147            4 :     }
     148              : 
     149              :     #[test]
     150            4 :     fn array_of_tables() {
     151            4 :         test_impl(
     152            4 :             r#"
     153            4 :                 [[a]]
     154            4 :                 b = 1
     155            4 :                 c = 2
     156            4 :                 d = 3
     157            4 :             "#,
     158            4 :             r#"
     159            4 :                 [[a]]
     160            4 :                 b = 1
     161            4 :                 c = 2
     162            4 :             "#,
     163            4 :             ["a.[0].d"],
     164            4 :         );
     165            4 :     }
     166              : 
     167              :     #[test]
     168            4 :     fn array() {
     169            4 :         test_impl(
     170            4 :             r#"
     171            4 :             foo = [ {bar = 23} ]
     172            4 :             "#,
     173            4 :             r#"
     174            4 :             foo = [ { blup = 42 }]
     175            4 :             "#,
     176            4 :             ["foo.[0].bar"],
     177            4 :         );
     178            4 :     }
     179              : }
        

Generated by: LCOV version 2.1-beta