Skip to main content

smbcloud_network/
network.rs

1use {
2    log::{debug, error},
3    reqwest::{RequestBuilder, Response, StatusCode},
4    serde::de::DeserializeOwned,
5    smbcloud_model::{
6        account::SmbAuthorization,
7        error_codes::{ErrorCode, ErrorResponse},
8        login::AccountStatus,
9    },
10};
11
12//use std::time::Duration;
13#[cfg(debug_assertions)]
14const LOG_RESPONSE_BODY: bool = true; // You know what to do here.
15#[cfg(not(debug_assertions))]
16const LOG_RESPONSE_BODY: bool = false;
17
18/// Check if there is an active internet connection.
19///
20/// Native clients can afford a preflight connectivity check. Browser/wasm
21/// clients should skip it and rely on the real request, otherwise the check
22/// itself becomes a separate cross-origin failure surface.
23#[cfg(not(target_arch = "wasm32"))]
24pub async fn check_internet_connection() -> bool {
25    debug!("Checking internet connection");
26    let client = reqwest::Client::builder()
27        //.timeout(Duration::from_secs(5)) Does not work on wasm32
28        .build();
29
30    if let Err(e) = client {
31        error!("Failed to create client for connectivity check: {:?}", e);
32        return false;
33    }
34
35    if let Ok(client) = client {
36        match client.get("https://dns.google").send().await {
37            Ok(response) => {
38                debug!(
39                    "Internet connection check successful: {}",
40                    response.status()
41                );
42                response.status().is_success()
43            }
44            Err(e) => {
45                error!("Internet connection check failed: {:?}", e);
46                false
47            }
48        }
49    } else {
50        false
51    }
52}
53
54#[cfg(target_arch = "wasm32")]
55pub async fn check_internet_connection() -> bool {
56    true
57}
58
59pub async fn parse_error_response<T: DeserializeOwned>(
60    response: Response,
61) -> Result<T, ErrorResponse> {
62    let error_response_body = match response.text().await {
63        Ok(body) => body,
64        Err(e) => {
65            error!("Failed to get response body: {:?}", e);
66            return Err(ErrorResponse::Error {
67                error_code: ErrorCode::NetworkError,
68                message: ErrorCode::NetworkError.message(None).to_string(),
69            });
70        }
71    };
72
73    if LOG_RESPONSE_BODY {
74        debug!(
75            "Parse Error >>>> {:?}",
76            serde_json::to_string_pretty(&error_response_body)
77        );
78    }
79
80    let error_response = match serde_json::from_str(&error_response_body) {
81        Ok(error_response) => error_response,
82        Err(e) => {
83            error!("Failed to parse error response: {:?}", e);
84            return Err(ErrorResponse::Error {
85                error_code: ErrorCode::ParseError,
86                message: ErrorCode::ParseError.message(None).to_string(),
87            });
88        }
89    };
90    // The parsing itself is succeed.
91    // Why is this an ErrorResponse.
92    Err(error_response)
93}
94
95pub async fn request_login(builder: RequestBuilder) -> Result<AccountStatus, ErrorResponse> {
96    let response = builder.send().await;
97    let response = match response {
98        Ok(response) => response,
99        Err(e) => {
100            error!("request_login: Failed to get response: {:?}", e);
101            return Err(ErrorResponse::Error {
102                error_code: ErrorCode::NetworkError,
103                message: ErrorCode::NetworkError.message(None).to_string(),
104            });
105        }
106    };
107
108    if LOG_RESPONSE_BODY {
109        debug!("request_login: Parse >>>> {:?}", response.status());
110    }
111
112    match (response.status(), response.headers().get("Authorization")) {
113        (StatusCode::OK, Some(token)) => {
114            // Login successful, let's get the access token for real.
115            let access_token = match token.to_str() {
116                Ok(token) => token.to_string(),
117                Err(_) => {
118                    return Err(ErrorResponse::Error {
119                        error_code: ErrorCode::NetworkError,
120                        message: ErrorCode::NetworkError.message(None).to_string(),
121                    });
122                }
123            };
124            Ok(AccountStatus::Ready { access_token })
125        }
126        (StatusCode::OK, None) => {
127            // Silent login from oauth. Need improvement.
128            let error_response = match parse_error_response::<ErrorResponse>(response).await {
129                Ok(error) => error,
130                Err(_) => return Ok(AccountStatus::NotFound),
131            };
132            match error_response {
133                ErrorResponse::Error {
134                    error_code,
135                    message: _,
136                } => match error_code {
137                    ErrorCode::EmailNotVerified => Ok(AccountStatus::Incomplete {
138                        status: smbcloud_model::account::ErrorCode::EmailUnverified,
139                    }),
140                    ErrorCode::PasswordNotSet => Ok(AccountStatus::Incomplete {
141                        status: smbcloud_model::account::ErrorCode::PasswordNotSet,
142                    }),
143                    ErrorCode::Unknown => Ok(AccountStatus::Ready {
144                        access_token: "tokenization".to_string(),
145                    }),
146                    _ => Ok(AccountStatus::NotFound),
147                },
148            }
149        }
150        (StatusCode::NOT_FOUND, _) => {
151            // Account not found
152            Ok(AccountStatus::NotFound)
153        }
154        (StatusCode::UNPROCESSABLE_ENTITY, _) => {
155            let body_text = match response.text().await {
156                Ok(text) => text,
157                Err(_) => {
158                    return Err(ErrorResponse::Error {
159                        error_code: ErrorCode::NetworkError,
160                        message: ErrorCode::NetworkError.message(None).to_string(),
161                    });
162                }
163            };
164
165            let result: SmbAuthorization = match serde_json::from_str(&body_text) {
166                Ok(res) => res,
167                Err(_) => {
168                    // Try generic JSON for a useful message.
169                    let message = serde_json::from_str::<serde_json::Value>(&body_text)
170                        .ok()
171                        .and_then(|v| {
172                            v.get("message")
173                                .and_then(serde_json::Value::as_str)
174                                .map(ToOwned::to_owned)
175                        })
176                        .unwrap_or_else(|| ErrorCode::NetworkError.message(None).to_string());
177                    return Err(ErrorResponse::Error {
178                        error_code: ErrorCode::NetworkError,
179                        message,
180                    });
181                }
182            };
183
184            let error_code = match result.error_code {
185                Some(code) => code,
186                None => {
187                    // No error_code but we have a message — return it as Incomplete with a
188                    // generic unverified status so the client can display the message.
189                    return Err(ErrorResponse::Error {
190                        error_code: ErrorCode::NetworkError,
191                        message: result.message,
192                    });
193                }
194            };
195            Ok(AccountStatus::Incomplete { status: error_code })
196        }
197        (StatusCode::UNAUTHORIZED, _) => {
198            let body_text = match response.text().await {
199                Ok(text) => text,
200                Err(_) => {
201                    return Err(ErrorResponse::Error {
202                        error_code: ErrorCode::NetworkError,
203                        message: ErrorCode::NetworkError.message(None).to_string(),
204                    });
205                }
206            };
207
208            // Try parsing as SmbAuthorization first.
209            if let Ok(result) = serde_json::from_str::<SmbAuthorization>(&body_text) {
210                let error_code = match result.error_code {
211                    Some(code) => code,
212                    None => {
213                        // Response parsed but no error_code — surface the server's message.
214                        return Err(ErrorResponse::Error {
215                            error_code: ErrorCode::Unauthorized,
216                            message: result.message,
217                        });
218                    }
219                };
220                return Err(ErrorResponse::Error {
221                    error_code: ErrorCode::Unauthorized,
222                    message: error_code.to_string(),
223                });
224            }
225
226            // Fallback: try to extract message or error from a generic JSON object.
227            if let Ok(generic) = serde_json::from_str::<serde_json::Value>(&body_text) {
228                let extracted = generic
229                    .get("message")
230                    .or_else(|| generic.get("error"))
231                    .and_then(serde_json::Value::as_str)
232                    .unwrap_or("Sign in failed.");
233                return Err(ErrorResponse::Error {
234                    error_code: ErrorCode::Unauthorized,
235                    message: extracted.to_string(),
236                });
237            }
238
239            Err(ErrorResponse::Error {
240                error_code: ErrorCode::Unauthorized,
241                message: "Sign in failed.".to_string(),
242            })
243        }
244        (status, _) => parse_error_response(response)
245            .await
246            .map_err(|_| ErrorResponse::Error {
247                error_code: ErrorCode::NetworkError,
248                message: format!("Unexpected login response status: {}", status),
249            }),
250    }
251}
252
253pub async fn request<R: DeserializeOwned>(builder: RequestBuilder) -> Result<R, ErrorResponse> {
254    // Check internet connection before making the request
255    if !check_internet_connection().await {
256        error!("No internet connection available");
257        return Err(ErrorResponse::Error {
258            error_code: ErrorCode::NetworkError,
259            message: "No internet connection. Please check your network settings and try again."
260                .to_string(),
261        });
262    }
263
264    let response = builder.send().await;
265    let response = match response {
266        Ok(response) => response,
267        Err(e) => {
268            error!("Failed to get response: {:?}", e);
269            return Err(ErrorResponse::Error {
270                error_code: ErrorCode::NetworkError,
271                message: ErrorCode::NetworkError.message(None).to_string(),
272            });
273        }
274    };
275    let response = match response.status() {
276        reqwest::StatusCode::OK | reqwest::StatusCode::CREATED => response,
277        status => {
278            error!("Failed to get response: {:?}", status);
279            // This should handle parsing the error response.
280            return parse_error_response(response).await;
281        }
282    };
283
284    let response_body = match response.text().await {
285        Ok(body) => body,
286        Err(e) => {
287            error!("Failed to get response body: {:?}", e);
288            return Err(ErrorResponse::Error {
289                error_code: ErrorCode::NetworkError,
290                message: ErrorCode::NetworkError.message(None).to_string(),
291            });
292        }
293    };
294
295    if LOG_RESPONSE_BODY {
296        debug!(
297            "Parse >>>> {:?}",
298            serde_json::to_string_pretty(&response_body)
299        );
300    }
301
302    let response = match serde_json::from_str::<R>(&response_body) {
303        Ok(response) => response,
304        Err(e) => {
305            error!("Failed to parse response: {:?}", e);
306            return Err(ErrorResponse::Error {
307                error_code: ErrorCode::ParseError,
308                message: e.to_string(),
309            });
310        }
311    };
312
313    Ok(response)
314}