use super::*;
pub struct NewEnvironment<'a> {
pub name: &'a str,
pub copy_from: Option<&'a str>,
pub empty: bool,
pub deploy: bool,
pub ttl: Option<&'a str>,
}
impl ApiClient {
pub async fn environments(&self, project_id: &str) -> Result<Answer<EnvironmentsPage>> {
self.get_answer(&format!("/api/infra/projects/{project_id}/environments"))
.await
}
pub async fn create_environment(
&self,
project_id: &str,
draft: &NewEnvironment<'_>,
) -> Result<Answer<CreatedEnvironment>> {
self.post_answer(
&format!("/api/infra/projects/{project_id}/environments"),
&environment_body(draft),
)
.await
}
pub async fn rename_environment(
&self,
project_id: &str,
environment: &str,
name: &str,
) -> Result<Answer<EnvironmentPage>> {
self.answer(
Method::PATCH,
&format!(
"/api/infra/projects/{project_id}/environments/{}",
utf8_percent_encode(environment, NON_ALPHANUMERIC)
),
Some(&serde_json::json!({ "name": name })),
)
.await
}
pub async fn delete_environment(
&self,
project_id: &str,
environment: &str,
confirmation: &str,
) -> Result<()> {
self.delete(&format!(
"/api/infra/projects/{project_id}/environments/{}?confirm={}",
utf8_percent_encode(environment, NON_ALPHANUMERIC),
utf8_percent_encode(confirmation, NON_ALPHANUMERIC)
))
.await
}
pub async fn set_environment_expiry(
&self,
project_id: &str,
environment: &str,
ttl: Option<&str>,
) -> Result<Answer<EnvironmentPage>> {
self.answer(
Method::PATCH,
&format!(
"/api/infra/projects/{project_id}/environments/{}",
utf8_percent_encode(environment, NON_ALPHANUMERIC)
),
Some(&serde_json::json!({ "ttl": ttl })),
)
.await
}
pub async fn compare_environments(
&self,
project_id: &str,
from: &str,
to: &str,
) -> Result<Answer<EnvironmentComparison>> {
self.get_answer(&format!(
"/api/infra/projects/{project_id}/environments/compare?from={}&to={}",
utf8_percent_encode(from, NON_ALPHANUMERIC),
utf8_percent_encode(to, NON_ALPHANUMERIC)
))
.await
}
pub async fn promotion_plan(
&self,
project_id: &str,
from: &str,
to: &str,
services: &[String],
) -> Result<Answer<PromotionPlan>> {
self.get_answer(&format!(
"{}/promote?to={}{}",
environment_path(project_id, from),
utf8_percent_encode(to, NON_ALPHANUMERIC),
services_query(services)
))
.await
}
pub async fn promote_environment(
&self,
project_id: &str,
from: &str,
to: &str,
services: &[String],
) -> Result<Answer<PromotionPlan>> {
self.post_answer(
&self.released(&format!("{}/promote", environment_path(project_id, from))),
&serde_json::json!({ "to": to, "services": services }),
)
.await
}
pub async fn data_copy_plan(
&self,
project_id: &str,
from: &str,
to: &str,
services: &[String],
) -> Result<Answer<DataCopyPlan>> {
self.get_answer(&format!(
"{}/copy-data?to={}{}",
environment_path(project_id, from),
utf8_percent_encode(to, NON_ALPHANUMERIC),
services_query(services)
))
.await
}
pub async fn copy_environment_data(
&self,
project_id: &str,
from: &str,
to: &str,
services: &[String],
confirmation: &str,
) -> Result<Answer<DataCopyPlan>> {
let path = format!("{}/copy-data", environment_path(project_id, from));
let response = self
.unbounded_request(
Method::POST,
&path,
Some(
&serde_json::json!({ "to": to, "services": services, "confirm": confirmation }),
),
)
.send()
.await?;
let text = Self::check(response).await?;
let raw = serde_json::from_str(&text)
.with_context(|| format!("could not read the answer from {path}"))?;
decoded(raw, &path)
}
pub async fn copy_service_to_environment(
&self,
project_id: &str,
environment: &str,
from: &str,
deploy: bool,
) -> Result<Answer<CopiedServicePage>> {
let mut body = serde_json::Map::new();
body.insert("from".into(), from.into());
if !deploy {
body.insert("deploy".into(), false.into());
}
self.post_answer(
&format!(
"/api/infra/projects/{project_id}/environments/{}/services",
utf8_percent_encode(environment, NON_ALPHANUMERIC)
),
&serde_json::Value::Object(body),
)
.await
}
}
fn environment_path(project_id: &str, environment: &str) -> String {
format!(
"/api/infra/projects/{project_id}/environments/{}",
utf8_percent_encode(environment, NON_ALPHANUMERIC)
)
}
fn services_query(services: &[String]) -> String {
if services.is_empty() {
return String::new();
}
let joined = services.join(",");
format!(
"&services={}",
utf8_percent_encode(&joined, NON_ALPHANUMERIC)
)
}
fn environment_body(draft: &NewEnvironment<'_>) -> serde_json::Value {
let mut body = serde_json::Map::new();
body.insert("name".into(), draft.name.into());
if draft.empty {
body.insert("copyFrom".into(), serde_json::Value::Null);
} else if let Some(source) = draft.copy_from {
body.insert("copyFrom".into(), source.into());
}
if !draft.deploy {
body.insert("deploy".into(), false.into());
}
if let Some(ttl) = draft.ttl {
body.insert("ttl".into(), ttl.into());
}
serde_json::Value::Object(body)
}
#[cfg(test)]
mod tests {
use super::*;
fn named(name: &str) -> NewEnvironment<'_> {
NewEnvironment {
name,
copy_from: None,
empty: false,
deploy: true,
ttl: None,
}
}
#[test]
fn it_should_leave_the_source_to_the_server_when_none_is_named() {
assert_eq!(
environment_body(&named("staging")),
serde_json::json!({ "name": "staging" })
);
}
#[test]
fn it_should_ask_for_an_empty_environment_with_a_null_source() {
let draft = NewEnvironment {
empty: true,
..named("sandbox")
};
assert_eq!(
environment_body(&draft),
serde_json::json!({ "name": "sandbox", "copyFrom": null })
);
}
#[test]
fn it_should_ask_for_an_environment_that_expires() {
let draft = NewEnvironment {
ttl: Some("7d"),
..named("demo")
};
assert_eq!(
environment_body(&draft),
serde_json::json!({ "name": "demo", "ttl": "7d" })
);
}
#[test]
fn it_should_ask_for_no_services_in_particular_with_no_query() {
assert_eq!(services_query(&[]), "");
}
#[test]
fn it_should_name_the_services_asked_for_in_one_query_parameter() {
assert_eq!(
services_query(&["api".to_owned(), "web".to_owned()]),
"&services=api%2Cweb"
);
}
#[test]
fn it_should_say_only_no_about_deploying() {
let draft = NewEnvironment {
deploy: false,
copy_from: Some("qa"),
..named("staging")
};
assert_eq!(
environment_body(&draft),
serde_json::json!({ "name": "staging", "copyFrom": "qa", "deploy": false })
);
}
}