alien_core/resources/
kv.rs1use crate::error::{ErrorData, Result};
2use crate::resource::{ResourceDefinition, ResourceOutputsDefinition, ResourceRef};
3use crate::ResourceType;
4use alien_error::AlienError;
5use bon::Builder;
6use serde::{Deserialize, Serialize};
7use std::any::Any;
8use std::fmt::Debug;
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Builder)]
13#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
14#[serde(rename_all = "camelCase", deny_unknown_fields)]
15#[builder(start_fn = new)]
16pub struct Kv {
17 #[builder(start_fn)]
20 pub id: String,
21}
22
23impl Kv {
24 pub const RESOURCE_TYPE: ResourceType = ResourceType::from_static("kv");
26
27 pub fn id(&self) -> &str {
29 &self.id
30 }
31}
32
33#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
35#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
36#[serde(rename_all = "camelCase")]
37pub struct KvOutputs {
38 pub store_name: String,
40 #[serde(skip_serializing_if = "Option::is_none")]
42 pub identifier: Option<String>,
43 #[serde(skip_serializing_if = "Option::is_none")]
45 pub endpoint: Option<String>,
46}
47
48#[typetag::serde(name = "kv")]
49impl ResourceOutputsDefinition for KvOutputs {
50 fn resource_type() -> ResourceType {
51 Kv::RESOURCE_TYPE.clone()
52 }
53
54 fn as_any(&self) -> &dyn Any {
55 self
56 }
57
58 fn box_clone(&self) -> Box<dyn ResourceOutputsDefinition> {
59 Box::new(self.clone())
60 }
61
62 fn outputs_eq(&self, other: &dyn ResourceOutputsDefinition) -> bool {
63 other.as_any().downcast_ref::<KvOutputs>() == Some(self)
64 }
65}
66
67#[typetag::serde(name = "kv")]
69impl ResourceDefinition for Kv {
70 fn resource_type() -> ResourceType {
71 Self::RESOURCE_TYPE.clone()
72 }
73
74 fn get_resource_type(&self) -> ResourceType {
75 Self::resource_type()
76 }
77
78 fn id(&self) -> &str {
79 &self.id
80 }
81
82 fn get_dependencies(&self) -> Vec<ResourceRef> {
83 Vec::new()
84 }
85
86 fn validate_update(&self, new_config: &dyn ResourceDefinition) -> Result<()> {
87 let new_kv = new_config.as_any().downcast_ref::<Kv>().ok_or_else(|| {
89 AlienError::new(ErrorData::UnexpectedResourceType {
90 resource_id: self.id.clone(),
91 expected: Self::RESOURCE_TYPE,
92 actual: new_config.get_resource_type(),
93 })
94 })?;
95
96 if self.id != new_kv.id {
97 return Err(AlienError::new(ErrorData::InvalidResourceUpdate {
98 resource_id: self.id.clone(),
99 reason: "the 'id' field is immutable".to_string(),
100 }));
101 }
102 Ok(())
103 }
104
105 fn as_any(&self) -> &dyn Any {
106 self
107 }
108
109 fn as_any_mut(&mut self) -> &mut dyn Any {
110 self
111 }
112
113 fn box_clone(&self) -> Box<dyn ResourceDefinition> {
114 Box::new(self.clone())
115 }
116
117 fn resource_eq(&self, other: &dyn ResourceDefinition) -> bool {
118 other.as_any().downcast_ref::<Kv>() == Some(self)
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 #[test]
127 fn test_kv_builder() {
128 let kv = Kv::new("my-kv".to_string()).build();
129 assert_eq!(kv.id, "my-kv");
130 }
131
132 #[test]
133 fn test_kv_resource_type() {
134 assert_eq!(Kv::RESOURCE_TYPE.as_ref(), "kv");
135 }
136
137 #[test]
138 fn test_kv_resource_definition() {
139 let kv = Kv::new("test-kv".to_string()).build();
140 assert_eq!(kv.get_resource_type(), Kv::RESOURCE_TYPE);
141 assert_eq!(kv.id(), "test-kv");
142 assert!(kv.get_dependencies().is_empty());
143 }
144
145 #[test]
146 fn test_kv_validate_update() {
147 let original = Kv::new("test-kv".to_string()).build();
148 let valid_update = Kv::new("test-kv".to_string()).build();
149 let invalid_update = Kv::new("different-kv".to_string()).build();
150
151 assert!(original.validate_update(&valid_update).is_ok());
153
154 assert!(original.validate_update(&invalid_update).is_err());
156 }
157
158 #[test]
159 fn test_kv_outputs_serialization() {
160 let outputs = KvOutputs {
161 store_name: "my-kv-store".to_string(),
162 identifier: Some(
163 "arn:aws:dynamodb:us-east-1:123456789012:table/my-kv-store".to_string(),
164 ),
165 endpoint: Some("https://my-kv-store.table.core.windows.net".to_string()),
166 };
167
168 let json = serde_json::to_string(&outputs).unwrap();
169 let deserialized: KvOutputs = serde_json::from_str(&json).unwrap();
170 assert_eq!(outputs, deserialized);
171 }
172}