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
// Copyright Materialize, Inc. and contributors. All rights reserved.
//
// Use of this software is governed by the Business Source License
// included in the LICENSE file.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0.
//! This module implements Materialize cloud API sync endpoint to
//! list all the availables cloud providers and regions, represented
//! by [`CloudProvider`].
//! As utilities, the module implements the enum [`CloudProviderRegion`].
//! It is useful to validate and compare external input (e.g. user input)
//! with the results from the cloud API represented by [`CloudProvider`].
//!
//! Example:
//! ```ignore
//! // List all the available providers
//! let cloud_providers = client.list_cloud_regions().await?;
//!
//! // Search the cloud provider
//! let user_input = "aws/us-east-1";
//! let cloud_provider_region_selected: CloudProviderRegion = CloudProviderRegion::from_str(user_input)?;
//! let cloud_provider: CloudProvider = cloud_providers
//! .iter()
//! .find(|cp| cp.as_cloud_provider_region().unwrap() == cloud_provider_region_selected)
//! .unwrap()
//! .to_owned();
//! ```
use std::fmt::Display;
use std::str::FromStr;
use reqwest::Method;
use serde::{Deserialize, Serialize};
use url::Url;
use crate::client::Client;
use crate::error::Error;
/// Holds all the information related to a cloud provider and a particular region.
#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct CloudProvider {
/// Contains the concatenation between cloud provider name and region:
///
/// E.g.: `aws/us-east-1` or `aws/eu-west-1`
pub id: String,
/// Contains the region name:
///
/// E.g.: `us-east-1` or `eu-west-1`
pub name: String,
/// Contains the complete Region API url.
///
/// E..g: `https://api.eu-west-1.aws.cloud.materialize.com`
pub url: Url,
/// Contains the cloud provider name.
///
/// E.g.: `aws` or `gcp`
pub cloud_provider: String,
}
impl CloudProvider {
/// Converts the CloudProvider object to a CloudProviderRegion.
pub fn as_cloud_provider_region(&self) -> Result<CloudProviderRegion, Error> {
match self.id.as_str() {
"aws/us-east-1" => Ok(CloudProviderRegion::AwsUsEast1),
"aws/eu-west-1" => Ok(CloudProviderRegion::AwsEuWest1),
"aws/us-west-2" => Ok(CloudProviderRegion::AwsUsWest2),
_ => Err(Error::CloudProviderRegionParseError),
}
}
}
/// Represents a cloud provider and a region.
/// Useful to transform a user input selecting a
/// cloud provider region to an enum and vice-versa.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub enum CloudProviderRegion {
/// Represents `aws/us-east-1` cloud provider and region
#[serde(rename = "aws/us-east-1")]
AwsUsEast1,
/// Represents `aws/eu-west-1` cloud provider and region
#[serde(rename = "aws/eu-west-1")]
AwsEuWest1,
/// Represents `aws/us-west-2` cloud provider and region
#[serde(rename = "aws/us-west-2")]
AwsUsWest2,
}
impl CloudProviderRegion {
/// Converts a CloudProvider object to a CloudProviderRegion.
pub fn from_cloud_provider(cloud_provider: CloudProvider) -> Result<Self, Error> {
match cloud_provider.id.as_str() {
"aws/us-east-1" => Ok(CloudProviderRegion::AwsUsEast1),
"aws/eu-west-1" => Ok(CloudProviderRegion::AwsEuWest1),
"aws/us-west-2" => Ok(CloudProviderRegion::AwsUsWest2),
_ => Err(Error::CloudProviderRegionParseError),
}
}
}
impl Display for CloudProviderRegion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CloudProviderRegion::AwsUsEast1 => write!(f, "aws/us-east-1"),
CloudProviderRegion::AwsEuWest1 => write!(f, "aws/eu-west-1"),
CloudProviderRegion::AwsUsWest2 => write!(f, "aws/us-west-2"),
}
}
}
impl FromStr for CloudProviderRegion {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"aws/us-east-1" => Ok(CloudProviderRegion::AwsUsEast1),
"aws/eu-west-1" => Ok(CloudProviderRegion::AwsEuWest1),
"aws/us-west-2" => Ok(CloudProviderRegion::AwsUsWest2),
_ => Err(Error::CloudProviderRegionParseError),
}
}
}
impl Client {
/// List all the available cloud regions.
///
/// E.g.: [us-east-1, eu-west-1]
pub async fn list_cloud_regions(&self) -> Result<Vec<CloudProvider>, Error> {
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct CloudProviderResponse {
data: Vec<CloudProvider>,
#[serde(skip_serializing_if = "Option::is_none")]
next_cursor: Option<String>,
}
let mut cloud_providers: Vec<CloudProvider> = vec![];
let mut cursor: String = String::new();
loop {
let req = self
.build_global_request(Method::GET, ["api", "cloud-regions"], None)
.await?;
let req = req.query(&[("limit", "50"), ("cursor", &cursor)]);
let response: CloudProviderResponse = self.send_request(req).await?;
cloud_providers.extend(response.data);
if let Some(next_cursor) = response.next_cursor {
cursor = next_cursor;
} else {
break;
}
}
Ok(cloud_providers)
}
}