use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::types::output::OutputValue;
use crate::types::state::{Module, StateRepresentation};
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct PlanRepresentation {
pub format_version: String,
pub terraform_version: String,
pub planned_values: PlannedValues,
#[serde(default)]
pub resource_changes: Vec<ResourceChange>,
#[serde(default)]
pub output_changes: HashMap<String, OutputChange>,
pub prior_state: Option<StateRepresentation>,
#[serde(default)]
pub timestamp: Option<String>,
#[serde(default)]
pub applyable: bool,
#[serde(default)]
pub complete: bool,
#[serde(default)]
pub errored: bool,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct PlannedValues {
#[serde(default)]
pub outputs: HashMap<String, OutputValue>,
pub root_module: Module,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ResourceChange {
pub address: String,
pub mode: String,
#[serde(rename = "type")]
pub resource_type: String,
pub name: String,
pub provider_name: String,
pub change: Change,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Change {
pub actions: Vec<String>,
#[serde(default)]
pub before: Value,
#[serde(default)]
pub after: Value,
#[serde(default)]
pub after_unknown: Value,
#[serde(default)]
pub before_sensitive: Value,
#[serde(default)]
pub after_sensitive: Value,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct OutputChange {
pub actions: Vec<String>,
#[serde(default)]
pub before: Value,
#[serde(default)]
pub after: Value,
#[serde(default)]
pub after_unknown: Value,
#[serde(default)]
pub before_sensitive: Value,
#[serde(default)]
pub after_sensitive: Value,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn deserialize_plan() {
let json = r#"{
"format_version": "1.2",
"terraform_version": "1.14.6",
"planned_values": {
"outputs": {
"id": {
"sensitive": false,
"type": "string",
"value": "123456"
}
},
"root_module": {
"resources": [
{
"address": "null_resource.example",
"mode": "managed",
"type": "null_resource",
"name": "example",
"provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0,
"values": {"id": "123456"},
"sensitive_values": {}
}
]
}
},
"resource_changes": [
{
"address": "null_resource.example",
"mode": "managed",
"type": "null_resource",
"name": "example",
"provider_name": "registry.terraform.io/hashicorp/null",
"change": {
"actions": ["no-op"],
"before": {"id": "123456"},
"after": {"id": "123456"},
"after_unknown": {},
"before_sensitive": {},
"after_sensitive": {}
}
}
],
"output_changes": {
"id": {
"actions": ["no-op"],
"before": "123456",
"after": "123456",
"after_unknown": false,
"before_sensitive": false,
"after_sensitive": false
}
},
"applyable": false,
"complete": true,
"errored": false
}"#;
let plan: PlanRepresentation = serde_json::from_str(json).unwrap();
assert_eq!(plan.format_version, "1.2");
assert_eq!(plan.resource_changes.len(), 1);
assert_eq!(plan.resource_changes[0].change.actions, vec!["no-op"]);
assert_eq!(plan.output_changes.len(), 1);
assert!(!plan.applyable);
assert!(plan.complete);
}
#[test]
fn deserialize_plan_with_create() {
let json = r#"{
"format_version": "1.2",
"terraform_version": "1.14.6",
"planned_values": {
"root_module": { "resources": [] }
},
"resource_changes": [
{
"address": "null_resource.new",
"mode": "managed",
"type": "null_resource",
"name": "new",
"provider_name": "registry.terraform.io/hashicorp/null",
"change": {
"actions": ["create"],
"before": null,
"after": {"triggers": null},
"after_unknown": {"id": true},
"before_sensitive": false,
"after_sensitive": false
}
}
],
"applyable": true,
"complete": true,
"errored": false
}"#;
let plan: PlanRepresentation = serde_json::from_str(json).unwrap();
assert_eq!(plan.resource_changes[0].change.actions, vec!["create"]);
assert!(plan.applyable);
}
}