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


use tap::TapResultOps;

use reqwest;
use reqwest::Method;
use querystring;
use instance::{InstanceItems, empty_instance_items};

use std::error::Error;
use querystring::QueryParams;
use std::collections::HashMap;

/// Google Cloud OAuth2 Token endpoint
const TOKEN_ENDPOINT: &str = "https://www.googleapis.com/oauth2/v4/token";


#[derive(Deserialize, Debug, Clone)]
pub struct GoogleAPISuccessResponse {
    pub kind: String,
    pub id: String,

    #[serde(default = "empty_instance_items")]
    pub items: InstanceItems,
}


#[derive(Deserialize, Debug, Clone)]
pub enum GoogleAPIComputeOperationStatus {
    PROGRESS,
    DONE,
}

#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct GoogleAPISuccessComputeOperationResponse {
    pub kind: String,
    pub id: String,
    pub name: String,
    pub zone: String,
    pub operation_type: String,
    pub status: GoogleAPIComputeOperationStatus,
    pub user: String,
    pub progress: u8,
    pub insert_time: String,
    pub start_time: String,
    pub end_time: String,
}

#[derive(Deserialize, Debug, Clone)]
pub struct GoogleAuthAPISuccessResponse {
    pub access_token: String,
    pub expires_in: u64,
    pub token_type: String,
}

#[derive(Deserialize, Debug, Clone)]
pub struct GoogleAuthAPIErrorResponse {
    pub error: String,
    pub error_description: String,
}

#[derive(Deserialize, Debug, Clone)]
pub struct GoogleAPIErrorResponse {
    pub error: GoogleAPIError,
}
#[derive(Deserialize, Debug, Clone)]
pub struct GoogleAPIError {
    pub code: u64,
    pub message: String,
}

#[derive(Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum GoogleAPIResponse {
    GoogleAPISuccessComputeOperationResponse(GoogleAPISuccessComputeOperationResponse),
    GoogleAPISuccessResponse(GoogleAPISuccessResponse),
    GoogleAuthAPISuccessResponse(GoogleAuthAPISuccessResponse),
    GoogleAuthAPIErrorResponse(GoogleAuthAPIErrorResponse),
    GoogleAPIErrorResponse(GoogleAPIErrorResponse),
}

#[derive(Debug, Clone)]
pub struct Client<'a> {
    pub client_id: &'a str,
    pub client_secret: &'a str,
    pub refresh_token: &'a str,

    /// Google Cloud project name, e.g. `myproject-181100`
    pub project: &'a str,

    /// Google Cloud zone, e.g. `europe-west1-d`
    pub zone: &'a str,
}


impl<'a> Client<'a> {
    /// Ask for a new access_token using the refresh_token
    fn refresh_token(&self) -> Result<GoogleAPIResponse, Box<Error>> {
        let mut params = HashMap::new();

        params.insert("client_id", self.client_id);
        params.insert("client_secret", self.client_secret);
        params.insert("refresh_token", self.refresh_token);
        params.insert("grant_type", "refresh_token");

        Ok(reqwest::Client::new()
            .post(TOKEN_ENDPOINT)
            .form(&params)
            .send()?
            .json()?)
    }

    /// Get a new `access_token`, it will use the `refresh_token` underneath
    /// @todo only ask for a new access_token when it's too old
    fn get_access_token(&self) -> String {
        match self.refresh_token() {
            Ok(GoogleAPIResponse::GoogleAuthAPISuccessResponse(resp)) => {
                trace!("{:#?}", resp);
                resp.access_token
            }
            _ => String::new(),
        }
    }

    fn url(&self, path: &str, query: QueryParams) -> String {
        let access_token = self.get_access_token();

        let query_string = querystring::stringify(
            [
                &query[..],
                &vec![("access_token", access_token.as_str())][..],
            ].concat(),
        );

        let url = format!(
            "https://www.googleapis.com/compute/v1/projects/{}/zones/{}{}?{}",
            self.project,
            self.zone,
            path,
            query_string
        );
        debug!("url={}", url);
        url
    }

    /// Run a Get HTTP query
    pub fn get(&self, path: &str, query: QueryParams) -> Result<GoogleAPIResponse, Box<Error>> {
        let client = reqwest::Client::new();
        Ok(client
            .request(Method::GET, &self.url(path, query))
            .send()
            .tap_ok(|resp| println!("Response: {:#?}", resp))?
            .json()?)
    }

    /// Run a Post HTTP query
    pub fn post(&self, path: &str, query: QueryParams) -> Result<GoogleAPIResponse, Box<Error>> {
        let client = reqwest::Client::new();

        Ok(client
            .request(Method::POST, &self.url(path, query))
            .body("")
            .send()
            .tap_ok(|resp| println!("Response: {:#?}", resp))?
            .json()?)
    }
}