use crate::client::Client;
use crate::errors::Error;
use crate::KeygenResponseData;
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum IsolationStrategy {
#[default]
Isolated,
Shared,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EnvironmentAttributes {
pub name: String,
pub code: String,
#[serde(rename = "isolationStrategy")]
pub isolation_strategy: IsolationStrategy,
pub created: String,
pub updated: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Environment {
pub id: String,
pub name: String,
pub code: String,
pub isolation_strategy: IsolationStrategy,
pub created: String,
pub updated: String,
pub account_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct EnvironmentResponse {
pub data: KeygenResponseData<EnvironmentAttributes>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct EnvironmentsResponse {
pub data: Vec<KeygenResponseData<EnvironmentAttributes>>,
pub meta: Option<serde_json::Value>,
pub links: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EnvironmentsListResult {
pub environments: Vec<Environment>,
pub meta: Option<serde_json::Value>,
pub links: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateEnvironmentRequest {
pub name: String,
pub code: String,
#[serde(rename = "isolationStrategy")]
pub isolation_strategy: Option<IsolationStrategy>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateEnvironmentRequest {
pub name: Option<String>,
pub code: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ListEnvironmentsOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<u32>,
#[serde(rename = "page[size]", skip_serializing_if = "Option::is_none")]
pub page_size: Option<u32>,
#[serde(rename = "page[number]", skip_serializing_if = "Option::is_none")]
pub page_number: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateEnvironmentTokenRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expiry: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub permissions: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EnvironmentTokenAttributes {
pub token: String,
pub name: Option<String>,
pub expiry: Option<String>,
pub permissions: Option<Vec<String>>,
pub created: String,
pub updated: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EnvironmentToken {
pub id: String,
pub token: String,
pub name: Option<String>,
pub expiry: Option<String>,
pub permissions: Option<Vec<String>>,
pub created: String,
pub updated: String,
pub environment_id: String,
pub account_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct EnvironmentTokenResponse {
pub data: KeygenResponseData<EnvironmentTokenAttributes>,
}
impl Environment {
pub(crate) fn from(data: KeygenResponseData<EnvironmentAttributes>) -> Environment {
Environment {
id: data.id,
name: data.attributes.name,
code: data.attributes.code,
isolation_strategy: data.attributes.isolation_strategy,
created: data.attributes.created,
updated: data.attributes.updated,
account_id: data
.relationships
.account
.as_ref()
.and_then(|a| a.data.as_ref().map(|d| d.id.clone())),
}
}
#[cfg(feature = "token")]
pub async fn create(request: CreateEnvironmentRequest) -> Result<Environment, Error> {
let client = Client::from_global_config()?;
let mut attributes = serde_json::Map::new();
attributes.insert("name".to_string(), json!(request.name));
attributes.insert("code".to_string(), json!(request.code));
if let Some(isolation_strategy) = request.isolation_strategy {
attributes.insert("isolationStrategy".to_string(), json!(isolation_strategy));
}
let body = json!({
"data": {
"type": "environments",
"attributes": attributes
}
});
let response = client
.post("environments", Some(&body), None::<&()>)
.await?;
let environment_response: EnvironmentResponse = serde_json::from_value(response.body)?;
Ok(Environment::from(environment_response.data))
}
#[cfg(feature = "token")]
pub async fn list(
options: Option<ListEnvironmentsOptions>,
) -> Result<EnvironmentsListResult, Error> {
let client = Client::from_global_config()?;
let mut query_params = HashMap::new();
if let Some(options) = options {
if let Some(limit) = options.limit {
query_params.insert("limit".to_string(), limit.to_string());
}
if let Some(page_size) = options.page_size {
query_params.insert("page[size]".to_string(), page_size.to_string());
}
if let Some(page_number) = options.page_number {
query_params.insert("page[number]".to_string(), page_number.to_string());
}
}
let query = if query_params.is_empty() {
None
} else {
Some(query_params)
};
let response = client.get("environments", query.as_ref()).await?;
let environments_response: EnvironmentsResponse = serde_json::from_value(response.body)?;
Ok(EnvironmentsListResult {
environments: environments_response
.data
.into_iter()
.map(Environment::from)
.collect(),
meta: environments_response.meta,
links: environments_response.links,
})
}
#[cfg(feature = "token")]
pub async fn get(id_or_code: &str) -> Result<Environment, Error> {
let client = Client::from_global_config()?;
let endpoint = format!("environments/{id_or_code}");
let response = client.get(&endpoint, None::<&()>).await?;
let environment_response: EnvironmentResponse = serde_json::from_value(response.body)?;
Ok(Environment::from(environment_response.data))
}
#[cfg(feature = "token")]
pub async fn update(&self, request: UpdateEnvironmentRequest) -> Result<Environment, Error> {
let client = Client::from_global_config()?;
let endpoint = format!("environments/{}", self.id);
let mut attributes = serde_json::Map::new();
if let Some(name) = request.name {
attributes.insert("name".to_string(), json!(name));
}
if let Some(code) = request.code {
attributes.insert("code".to_string(), json!(code));
}
let body = json!({
"data": {
"type": "environments",
"attributes": attributes
}
});
let response = client.patch(&endpoint, Some(&body), None::<&()>).await?;
let environment_response: EnvironmentResponse = serde_json::from_value(response.body)?;
Ok(Environment::from(environment_response.data))
}
#[cfg(feature = "token")]
pub async fn delete(&self) -> Result<(), Error> {
let client = Client::from_global_config()?;
let endpoint = format!("environments/{}", self.id);
let _response = client
.delete::<(), serde_json::Value>(&endpoint, None::<&()>)
.await?;
Ok(())
}
#[cfg(feature = "token")]
pub async fn generate_token(
&self,
request: Option<CreateEnvironmentTokenRequest>,
) -> Result<EnvironmentToken, Error> {
let client = Client::from_global_config()?;
let endpoint = format!("environments/{}/tokens", self.id);
let mut attributes = serde_json::Map::new();
if let Some(request) = request {
if let Some(name) = request.name {
attributes.insert("name".to_string(), json!(name));
}
if let Some(expiry) = request.expiry {
attributes.insert("expiry".to_string(), json!(expiry));
}
if let Some(permissions) = request.permissions {
attributes.insert("permissions".to_string(), json!(permissions));
}
}
let body = json!({
"data": {
"type": "tokens",
"attributes": attributes
}
});
let response = client.post(&endpoint, Some(&body), None::<&()>).await?;
let token_response: EnvironmentTokenResponse = serde_json::from_value(response.body)?;
Ok(EnvironmentToken {
id: token_response.data.id,
token: token_response.data.attributes.token,
name: token_response.data.attributes.name,
expiry: token_response.data.attributes.expiry,
permissions: token_response.data.attributes.permissions,
created: token_response.data.attributes.created,
updated: token_response.data.attributes.updated,
environment_id: self.id.clone(),
account_id: self.account_id.clone(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
KeygenRelationship, KeygenRelationshipData, KeygenRelationships, KeygenResponseData,
};
#[test]
fn test_environment_from_response_data() {
let environment_data = KeygenResponseData {
id: "test-environment-id".to_string(),
r#type: "environments".to_string(),
attributes: EnvironmentAttributes {
name: "Test Environment".to_string(),
code: "test-env".to_string(),
isolation_strategy: IsolationStrategy::Isolated,
created: "2023-01-01T00:00:00Z".to_string(),
updated: "2023-01-01T00:00:00Z".to_string(),
},
relationships: KeygenRelationships {
policy: None,
account: Some(KeygenRelationship {
data: Some(KeygenRelationshipData {
r#type: "accounts".to_string(),
id: "test-account-id".to_string(),
}),
links: None,
}),
product: None,
group: None,
owner: None,
users: None,
machines: None,
environment: None,
license: None,
release: None,
other: HashMap::new(),
},
};
let environment = Environment::from(environment_data);
assert_eq!(environment.id, "test-environment-id");
assert_eq!(environment.name, "Test Environment");
assert_eq!(environment.code, "test-env");
assert_eq!(environment.isolation_strategy, IsolationStrategy::Isolated);
assert_eq!(environment.account_id, Some("test-account-id".to_string()));
}
#[test]
fn test_isolation_strategy_default() {
let default_strategy = IsolationStrategy::default();
assert_eq!(default_strategy, IsolationStrategy::Isolated);
}
#[test]
fn test_create_environment_request_serialization() {
let request = CreateEnvironmentRequest {
name: "Production".to_string(),
code: "prod".to_string(),
isolation_strategy: Some(IsolationStrategy::Shared),
};
let json = serde_json::to_string(&request).unwrap();
assert!(json.contains("\"name\":\"Production\""));
assert!(json.contains("\"code\":\"prod\""));
assert!(json.contains("\"isolationStrategy\":\"SHARED\""));
}
#[test]
fn test_create_environment_token_request_optional_fields() {
let request = CreateEnvironmentTokenRequest {
name: Some("Test Token".to_string()),
expiry: None,
permissions: Some(vec!["environment.read".to_string()]),
};
let json = serde_json::to_string(&request).unwrap();
assert!(json.contains("\"name\":\"Test Token\""));
assert!(json.contains("\"permissions\":[\"environment.read\"]"));
assert!(!json.contains("expiry"));
}
}