Repository: FuelLabs/fuelup (rust)

Issue:
refactor `Settings` to derive `Deserialize`
As mentioned [here](https://github.com/FuelLabs/fuelup/pull/104#pullrequestreview-1048861769) in #104 

Refactor `Settings` type to derive `Deserialize` rather than doing manual parsing.



Pull request description:
`Settings` serialization/deserialization
closes #115 

- Implements serialization/de-serialization for `Settings`
- Added tests for reading/writing `Settings`
- [x] parse `Settings`
- [x] write into settings file
- [x] stringify `Settings`

Patch:
diff --git a/src/settings.rs b/src/settings.rs
index 0bcda356..bf4642f9 100644
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -1,7 +1,8 @@
+use serde::{Deserialize, Serialize};
 use std::{cell::RefCell, path::PathBuf};
-use toml_edit::{value, Document, Table, Value};
+use toml_edit::{de, ser, Document};
 
-use anyhow::{bail, Result};
+use anyhow::Result;
 
 use crate::file;
 
@@ -20,7 +21,7 @@ impl SettingsFile {
 
     fn write_settings(&self) -> Result<()> {
         let s = self.cache.borrow().as_ref().unwrap().clone();
-        file::write_file(&self.path, &s.stringify())?;
+        file::write_file(&self.path, &s.to_string()?)?;
         Ok(())
     }
 
@@ -61,56 +62,84 @@ impl SettingsFile {
     }
 }
 
-#[derive(Clone, Default)]
+#[derive(Debug, Clone, Default, Deserialize, Serialize)]
 pub struct Settings {
     pub default_toolchain: Option<String>,
 }
 
-fn take_value(table: &mut Table, key: &str) -> Result<Value> {
-    if let Some(i) = table.remove(key) {
-        i.into_value()
-            .or_else(|_| bail!("Item is None for key: '{}'", key))
-    } else {
-        bail!("missing key: '{}'", key)
+impl Settings {
+    pub(crate) fn parse(toml: &str) -> Result<Self> {
+        let settings: Settings = de::from_str(toml)?;
+        Ok(settings)
     }
-}
 
-fn get_opt_string(table: &mut Table, key: &str) -> Result<Option<String>> {
-    if let Ok(v) = take_value(table, key) {
-        if let Value::String(s) = v {
-            Ok(Some(s.to_string()))
-        } else {
-            bail!("Expected string, got {}", key)
-        }
-    } else {
-        Ok(None)
+    pub(crate) fn to_string(&self) -> Result<String> {
+        Ok(self.to_toml()?.to_string())
+    }
+
+    pub(crate) fn to_toml(&self) -> std::result::Result<Document, ser::Error> {
+        ser::to_document(&self)
     }
 }
 
-impl Settings {
-    pub(crate) fn from_toml(mut document: Document) -> Result<Self> {
-        Ok(Self {
-            default_toolchain: get_opt_string(document.as_table_mut(), "default_toolchain")?,
-        })
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::file::read_file;
+
+    pub(crate) fn with_mock_fuelup_dir<F>(f: F) -> Result<()>
+    where
+        F: FnOnce(tempfile::TempDir) -> Result<()>,
+    {
+        let mock_fuelup_dir = tempfile::tempdir()?;
+        f(mock_fuelup_dir)
     }
 
-    pub(crate) fn parse(toml: &str) -> Result<Self> {
-        let document = toml.parse::<Document>().expect("Invalid toml document");
-        Self::from_toml(document)
+    #[test]
+    fn parse_settings() {
+        let settings_path = std::env::current_dir()
+            .unwrap()
+            .join("tests/settings-example.toml");
+
+        let settings_file = read_file("settings-example", &settings_path).unwrap();
+        let settings = Settings::parse(&settings_file).unwrap();
+
+        assert_eq!(
+            settings.default_toolchain.unwrap(),
+            "latest-x86_64-apple-darwin"
+        )
     }
 
-    pub(crate) fn stringify(self) -> String {
-        self.into_toml().to_string()
+    #[test]
+    fn write_settings() -> Result<()> {
+        with_mock_fuelup_dir(|dir| {
+            let settings_path = dir.path().join("settings-example-dst.toml");
+
+            let settings_file = SettingsFile::new(PathBuf::from(&settings_path));
+            let new_default_toolchain = String::from("new-default-toolchain");
+
+            settings_file.with_mut(|s| {
+                s.default_toolchain = Some(new_default_toolchain.clone());
+                Ok(())
+            })?;
+
+            let settings =
+                Settings::parse(&read_file("settings-example-dst", &settings_path).unwrap())
+                    .unwrap();
+            assert_eq!(settings.default_toolchain.unwrap(), new_default_toolchain);
+            Ok(())
+        })
     }
 
-    pub(crate) fn into_toml(self) -> Table {
-        let mut table = Table::new();
+    #[test]
+    fn settings_into_string() {
+        let expected_toml = r#"default_toolchain = "yet-another-default-toolchain"
+"#;
 
-        if let Some(v) = self.default_toolchain {
-            table["default_toolchain"] = value(v);
-        }
+        let settings = Settings {
+            default_toolchain: Some("yet-another-default-toolchain".to_string()),
+        };
 
-        table.fmt();
-        table
+        assert_eq!(settings.to_string().unwrap(), expected_toml);
     }
 }


Test patch:
diff --git a/tests/settings-example.toml b/tests/settings-example.toml
new file mode 100644
index 00000000..1c3ca6cd
--- /dev/null
+++ b/tests/settings-example.toml
@@ -0,0 +1,1 @@
+default_toolchain = "latest-x86_64-apple-darwin"