LCOV - code coverage report
Current view: top level - pageserver/src/config - ignored_fields.rs (source / functions) Coverage Total Hit
Test: aca806cab4756d7eb6a304846130f4a73a5d5393.info Lines: 99.2 % 123 122
Test Date: 2025-04-24 20:31:15 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           48 : pub fn find(user_specified: toml_edit::DocumentMut, reserialized: toml_edit::DocumentMut) -> Paths {
      17           48 :     let user_specified = paths(user_specified);
      18           48 :     let reserialized = paths(reserialized);
      19           96 :     fn paths(doc: toml_edit::DocumentMut) -> HashSet<String> {
      20           96 :         let mut out = Vec::new();
      21           96 :         let mut visitor = PathsVisitor::new(&mut out);
      22           96 :         visitor.visit_table_like(doc.as_table());
      23           96 :         HashSet::from_iter(out)
      24           96 :     }
      25              : 
      26           48 :     let mut ignored = HashSet::new();
      27              : 
      28              :     // O(n) because of HashSet
      29          144 :     for path in user_specified {
      30           96 :         if !reserialized.contains(&path) {
      31           48 :             ignored.insert(path);
      32           48 :         }
      33              :     }
      34              : 
      35           48 :     Paths {
      36           48 :         paths: ignored
      37           48 :             .into_iter()
      38           48 :             // sort lexicographically for deterministic output
      39           48 :             .sorted()
      40           48 :             .collect(),
      41           48 :     }
      42           48 : }
      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           96 :     fn new(out: &'a mut Vec<String>) -> Self {
      55           96 :         Self {
      56           96 :             stack: Vec::new(),
      57           96 :             out,
      58           96 :         }
      59           96 :     }
      60              : 
      61          216 :     fn visit_table_like(&mut self, table_like: &dyn toml_edit::TableLike) {
      62          288 :         for (entry, item) in table_like.iter() {
      63          288 :             self.stack.push(entry.to_string());
      64          288 :             self.visit_item(item);
      65          288 :             self.stack.pop();
      66          288 :         }
      67          216 :     }
      68              : 
      69          288 :     fn visit_item(&mut self, item: &toml_edit::Item) {
      70          288 :         match item {
      71            0 :             toml_edit::Item::None => (),
      72          192 :             toml_edit::Item::Value(value) => self.visit_value(value),
      73           72 :             toml_edit::Item::Table(table) => {
      74           72 :                 self.visit_table_like(table);
      75           72 :             }
      76           24 :             toml_edit::Item::ArrayOfTables(array_of_tables) => {
      77           24 :                 for (i, table) in array_of_tables.iter().enumerate() {
      78           24 :                     self.stack.push(format!("[{i}]"));
      79           24 :                     self.visit_table_like(table);
      80           24 :                     self.stack.pop();
      81           24 :                 }
      82              :             }
      83              :         }
      84          288 :     }
      85              : 
      86          216 :     fn visit_value(&mut self, value: &toml_edit::Value) {
      87          216 :         match value {
      88              :             toml_edit::Value::String(_)
      89              :             | toml_edit::Value::Integer(_)
      90              :             | toml_edit::Value::Float(_)
      91              :             | toml_edit::Value::Boolean(_)
      92          168 :             | toml_edit::Value::Datetime(_) => self.out.push(self.stack.join(".")),
      93           24 :             toml_edit::Value::Array(array) => {
      94           24 :                 for (i, value) in array.iter().enumerate() {
      95           24 :                     self.stack.push(format!("[{i}]"));
      96           24 :                     self.visit_value(value);
      97           24 :                     self.stack.pop();
      98           24 :                 }
      99              :             }
     100           24 :             toml_edit::Value::InlineTable(inline_table) => self.visit_table_like(inline_table),
     101              :         }
     102          216 :     }
     103              : }
     104              : 
     105              : #[cfg(test)]
     106              : pub(crate) mod tests {
     107              : 
     108           48 :     fn test_impl(original: &str, parsed: &str, expect: [&str; 1]) {
     109           48 :         let original: toml_edit::DocumentMut = original.parse().expect("parse original config");
     110           48 :         let parsed: toml_edit::DocumentMut = parsed.parse().expect("parse re-serialized config");
     111           48 : 
     112           48 :         let super::Paths { paths: actual } = super::find(original, parsed);
     113           48 :         assert_eq!(actual, &expect);
     114           48 :     }
     115              : 
     116              :     #[test]
     117           12 :     fn top_level() {
     118           12 :         test_impl(
     119           12 :             r#"
     120           12 :                 [a]
     121           12 :                 b = 1
     122           12 :                 c = 2
     123           12 :                 d = 3
     124           12 :             "#,
     125           12 :             r#"
     126           12 :                 [a]
     127           12 :                 b = 1
     128           12 :                 c = 2
     129           12 :             "#,
     130           12 :             ["a.d"],
     131           12 :         );
     132           12 :     }
     133              : 
     134              :     #[test]
     135           12 :     fn nested() {
     136           12 :         test_impl(
     137           12 :             r#"
     138           12 :                 [a.b.c]
     139           12 :                 d = 23
     140           12 :             "#,
     141           12 :             r#"
     142           12 :                 [a]
     143           12 :                 e = 42
     144           12 :             "#,
     145           12 :             ["a.b.c.d"],
     146           12 :         );
     147           12 :     }
     148              : 
     149              :     #[test]
     150           12 :     fn array_of_tables() {
     151           12 :         test_impl(
     152           12 :             r#"
     153           12 :                 [[a]]
     154           12 :                 b = 1
     155           12 :                 c = 2
     156           12 :                 d = 3
     157           12 :             "#,
     158           12 :             r#"
     159           12 :                 [[a]]
     160           12 :                 b = 1
     161           12 :                 c = 2
     162           12 :             "#,
     163           12 :             ["a.[0].d"],
     164           12 :         );
     165           12 :     }
     166              : 
     167              :     #[test]
     168           12 :     fn array() {
     169           12 :         test_impl(
     170           12 :             r#"
     171           12 :             foo = [ {bar = 23} ]
     172           12 :             "#,
     173           12 :             r#"
     174           12 :             foo = [ { blup = 42 }]
     175           12 :             "#,
     176           12 :             ["foo.[0].bar"],
     177           12 :         );
     178           12 :     }
     179              : }
        

Generated by: LCOV version 2.1-beta