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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
/* VERSION.rs
 *   by Lut99
 *
 * Created:
 *   08 May 2022, 13:31:16
 * Last edited:
 *   23 May 2022, 20:50:07
 * Auto updated?
 *   Yes
 *
 * Description:
 *   Implements version queriers for the Brane framework.
**/

use std::str::FromStr;

use log::debug;
use reqwest::{Response, StatusCode};
use specifications::arch::Arch;
use specifications::version::Version;

use crate::errors::VersionError;
use crate::instance::InstanceInfo;


/***** HELPER STRUCTS *****/
/// Struct that is used in querying the local CLI.
#[derive(Debug)]
struct LocalVersion {
    /// The architecture as reported by `uname -m`
    arch:    Arch,
    /// The version as reported by the env
    version: Version,
}

impl LocalVersion {
    /// Constructor for the RemoteVersion.
    ///
    /// Queries the CARGO_PKG_VERSION environment variable for the version.
    ///
    /// # Returns
    /// A new LocalVersion instance on success, or else a VersionError.
    fn new() -> Result<Self, VersionError> {
        // Parse the env
        let version = match Version::from_str(env!("CARGO_PKG_VERSION")) {
            Ok(version) => version,
            Err(err) => {
                return Err(VersionError::VersionParseError { raw: env!("CARGO_PKG_VERSION").to_string(), err });
            },
        };

        // Done, return the struct
        Ok(Self { arch: Arch::HOST, version })
    }
}



/// Struct that is used in querying the remote CLI.
#[derive(Debug)]
struct RemoteVersion {
    /// The architecture as reported by the remote
    _arch:   Arch,
    /// The version as downloaded from the remote
    version: Version,
}

impl RemoteVersion {
    /// Constructor for the RemoteVersion.
    ///
    /// Queries the remote host as stored in the Brane registry login file (get_config_dir()/registry.yml) for its version number.
    ///
    /// # Returns
    /// A new RemoteVersion instance on success, or else a VersionError.
    async fn new() -> Result<Self, VersionError> {
        debug!("Retrieving remote version number");

        // Try to get the registry file path
        debug!(" > Reading registy.yml...");
        let config: InstanceInfo = match InstanceInfo::from_active_path() {
            Ok(config) => config,
            Err(err) => {
                return Err(VersionError::InstanceInfoError { err });
            },
        };

        // Pass to the other constructor
        Self::from_instance_info(config).await
    }

    /// Constructor for the RemoteVersion, which creates it from a given IdentityFile.
    ///
    /// # Arguments
    /// - `info`: The InstanceInfo file to use to find the remote registry's properties.
    ///
    /// # Returns
    /// A new RemoteVersion instance on success, or else a VersionError.
    async fn from_instance_info(info: InstanceInfo) -> Result<Self, VersionError> {
        // Use reqwest for the API call
        debug!(" > Querying...");
        let mut url: String = info.api.to_string();
        url.push_str("/version");
        let response: Response = match reqwest::get(&url).await {
            Ok(version) => version,
            Err(err) => {
                return Err(VersionError::RequestError { url, err });
            },
        };
        if response.status() != StatusCode::OK {
            return Err(VersionError::RequestFailure { url, status: response.status() });
        }
        let version_body: String = match response.text().await {
            Ok(body) => body,
            Err(err) => {
                return Err(VersionError::RequestBodyError { url, err });
            },
        };

        // Try to parse the version
        debug!(" > Parsing remote version...");
        let version = match Version::from_str(&version_body) {
            Ok(version) => version,
            Err(err) => {
                return Err(VersionError::VersionParseError { raw: version_body, err });
            },
        };

        // Done!
        debug!("Remote version number: {}", &version);
        Ok(Self { _arch: Arch::X86_64, version })
    }
}





/***** HANDLERS *****/
/// Returns the local architecture (without any extra text).
pub fn handle_local_arch() -> Result<(), VersionError> {
    // Get the local version and print it
    println!("{}", LocalVersion::new()?.arch);

    // Done
    Ok(())
}

/// Returns the local version (without any extra text).
pub fn handle_local_version() -> Result<(), VersionError> {
    // Get the local version and print it
    println!("{}", LocalVersion::new()?.version);

    // Done
    Ok(())
}



/// Returns the local architecture (without any extra text).
pub async fn handle_remote_arch() -> Result<(), VersionError> {
    // Get the remote version and print it
    println!("<TBD>");

    // Done
    Ok(())
}

/// Returns the local version (without any extra text).
pub async fn handle_remote_version() -> Result<(), VersionError> {
    // Get the remote version and print it
    println!("{}", RemoteVersion::new().await?.version);

    // Done
    Ok(())
}



/// Returns both the local and possible remote version numbers with some pretty formatting.
pub async fn handle() -> Result<(), VersionError> {
    // Get the local version first and immediately print
    let local = LocalVersion::new()?;
    println!();
    println!("Brane CLI client");
    println!(" - Version      : v{}", local.version);
    println!(" - Architecture : {}", local.arch);
    println!();

    // If the registry file exists, then also do the remote
    let active_instance_exists: bool = match InstanceInfo::active_instance_exists() {
        Ok(exists) => exists,
        Err(err) => {
            return Err(VersionError::InstanceInfoExistsError { err });
        },
    };
    if active_instance_exists {
        // Get the registry file from it
        let config = match InstanceInfo::from_active_path() {
            Ok(config) => config,
            Err(err) => {
                return Err(VersionError::InstanceInfoError { err });
            },
        };

        // Print the URL
        println!("Remote Brane instance at '{}'", &config.api);

        // Get the version
        let remote = RemoteVersion::from_instance_info(config).await?;
        println!(" - Version      : v{}", remote.version);
        println!(" - Architecture : <TBD>");
        println!();
    }

    // Done
    Ok(())
}