1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
use std::path::Path;

use credent_model::{Profile, Profiles};

use crate::{AppName, CredentialsFile, Error};

/// Reads credentials from the user's configuration directory.
#[derive(Debug)]
pub struct CredentialsFileLoader;

impl CredentialsFileLoader {
    /// Returns the default profile credentials stored in the user's
    /// configuration directory.
    ///
    /// The path differs depending on the user's operating system:
    ///
    /// * `Windows`: `C:\Users\%USER%\AppData\Roaming\<app>\credentials`
    /// * `Linux`: `$XDG_CONFIG_HOME` or `$HOME/.config/<app>/credentials`
    /// * `OS X`: `$HOME/Library/Application Support/<app>/credentials`
    ///
    /// # Parameters
    ///
    /// * `app_name`: Name of the application whose credentials to load.
    pub async fn load(app_name: AppName<'_>) -> Result<Option<Profile>, Error> {
        let credentials_path = CredentialsFile::path(app_name)?;
        if credentials_path.exists() {
            Self::load_profile(app_name, Profile::DEFAULT_NAME).await
        } else {
            Ok(None)
        }
    }

    /// Returns the profile credentials stored in the user's configuration
    /// directory.
    ///
    /// The path differs depending on the user's operating system:
    ///
    /// * `Windows`: `C:\Users\%USER%\AppData\Roaming\<app>\credentials`
    /// * `Linux`: `$XDG_CONFIG_HOME` or `$HOME/.config/<app>/credentials`
    /// * `OS X`: `$HOME/Library/Application Support/<app>/credentials`
    ///
    /// # Parameters
    ///
    /// * `app_name`: Name of the application whose credentials to load.
    /// * `profile_name`: Which profile's credentials to load.
    pub async fn load_profile(
        app_name: AppName<'_>,
        profile_name: &str,
    ) -> Result<Option<Profile>, Error> {
        Self::load_all(app_name)
            .await
            .map(|profiles_result| {
                profiles_result.map(|profiles| {
                    profiles
                        .0
                        .into_iter()
                        .find(|profile| profile.name == profile_name)
                })
            })
            .map(Option::flatten)
    }

    /// Returns all profile credentials stored in the user's configuration
    /// directory.
    ///
    /// The path differs depending on the user's operating system:
    ///
    /// * `Windows`: `C:\Users\%USER%\AppData\Roaming\<app>\credentials`
    /// * `Linux`: `$XDG_CONFIG_HOME` or `$HOME/.config/<app>/credentials`
    /// * `OS X`: `$HOME/Library/Application Support/<app>/credentials`
    ///
    /// # Parameters
    ///
    /// * `app_name`: Name of the application whose credentials to load.
    pub async fn load_all(app_name: AppName<'_>) -> Result<Option<Profiles>, Error> {
        let credentials_path = CredentialsFile::path(app_name)?;
        if credentials_path.exists() {
            Self::load_file(credentials_path.as_ref()).await.map(Some)
        } else {
            Ok(None)
        }
    }

    /// Loads all credential profiles from the given file.
    ///
    /// # Parameters
    ///
    /// * `credentials_path`: File to load credentials from.
    pub async fn load_file(credentials_path: &Path) -> Result<Profiles, Error> {
        if !credentials_path.exists() {
            let credentials_path = credentials_path.to_owned();
            Err(Error::CredentialsFileNonExistent { credentials_path })
        } else if credentials_path.is_dir() {
            let credentials_path = credentials_path.to_owned();
            Err(Error::CredentialsFileIsDir { credentials_path })
        } else {
            let profiles_contents = Self::credentials_file_read(credentials_path).await?;
            Self::credentials_deserialize(profiles_contents, credentials_path)
        }
    }

    async fn credentials_file_read(credentials_path: &Path) -> Result<Vec<u8>, Error> {
        async_fs::read(credentials_path).await.map_err(|io_error| {
            let credentials_path = credentials_path.to_owned();
            Error::CredentialsFileFailedToRead {
                credentials_path,
                io_error,
            }
        })
    }

    fn credentials_deserialize(
        profiles_contents: Vec<u8>,
        credentials_path: &Path,
    ) -> Result<Profiles, Error> {
        toml::from_slice(&profiles_contents).map_err(|toml_de_error| {
            let credentials_path = credentials_path.to_owned();
            Error::CredentialsFileFailedToDeserialize {
                credentials_path,
                toml_de_error,
            }
        })
    }
}