alien_core/resources/
ai.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)]
18#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
19#[serde(rename_all = "camelCase", deny_unknown_fields)]
20#[builder(start_fn = new)]
21pub struct Ai {
22 #[builder(start_fn)]
25 pub id: String,
26}
27
28impl Ai {
29 pub const RESOURCE_TYPE: ResourceType = ResourceType::from_static("ai");
31
32 pub fn id(&self) -> &str {
34 &self.id
35 }
36}
37
38#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
40#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
41#[serde(rename_all = "camelCase")]
42pub struct AiOutputs {
43 pub provider: String,
45 #[serde(skip_serializing_if = "Option::is_none")]
47 pub endpoint: Option<String>,
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub account: Option<String>,
51}
52
53impl ResourceOutputsDefinition for AiOutputs {
54 fn get_resource_type(&self) -> ResourceType {
55 Ai::RESOURCE_TYPE.clone()
56 }
57
58 fn as_any(&self) -> &dyn Any {
59 self
60 }
61
62 fn box_clone(&self) -> Box<dyn ResourceOutputsDefinition> {
63 Box::new(self.clone())
64 }
65
66 fn outputs_eq(&self, other: &dyn ResourceOutputsDefinition) -> bool {
67 other.as_any().downcast_ref::<AiOutputs>() == Some(self)
68 }
69
70 fn to_json_value(&self) -> serde_json::Result<serde_json::Value> {
71 serde_json::to_value(self)
72 }
73}
74
75impl ResourceDefinition for Ai {
76 fn get_resource_type(&self) -> ResourceType {
77 Self::RESOURCE_TYPE
78 }
79
80 fn id(&self) -> &str {
81 &self.id
82 }
83
84 fn get_dependencies(&self) -> Vec<ResourceRef> {
85 Vec::new()
86 }
87
88 fn validate_update(&self, new_config: &dyn ResourceDefinition) -> Result<()> {
89 let new_ai = new_config.as_any().downcast_ref::<Ai>().ok_or_else(|| {
90 AlienError::new(ErrorData::UnexpectedResourceType {
91 resource_id: self.id.clone(),
92 expected: Self::RESOURCE_TYPE,
93 actual: new_config.get_resource_type(),
94 })
95 })?;
96
97 if self.id != new_ai.id {
98 return Err(AlienError::new(ErrorData::InvalidResourceUpdate {
99 resource_id: self.id.clone(),
100 reason: "the 'id' field is immutable".to_string(),
101 }));
102 }
103 Ok(())
104 }
105
106 fn as_any(&self) -> &dyn Any {
107 self
108 }
109
110 fn as_any_mut(&mut self) -> &mut dyn Any {
111 self
112 }
113
114 fn box_clone(&self) -> Box<dyn ResourceDefinition> {
115 Box::new(self.clone())
116 }
117
118 fn resource_eq(&self, other: &dyn ResourceDefinition) -> bool {
119 other.as_any().downcast_ref::<Ai>() == Some(self)
120 }
121
122 fn to_json_value(&self) -> serde_json::Result<serde_json::Value> {
123 serde_json::to_value(self)
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn test_ai_builder() {
133 let ai = Ai::new("llm".to_string()).build();
134 assert_eq!(ai.id, "llm");
135 }
136
137 #[test]
138 fn test_ai_resource_type() {
139 assert_eq!(Ai::RESOURCE_TYPE.as_ref(), "ai");
140 }
141
142 #[test]
143 fn test_ai_resource_definition() {
144 let ai = Ai::new("test-ai".to_string()).build();
145 assert_eq!(ai.get_resource_type(), Ai::RESOURCE_TYPE);
146 assert_eq!(ResourceDefinition::id(&ai), "test-ai");
147 assert!(ai.get_dependencies().is_empty());
148 }
149
150 #[test]
151 fn test_ai_validate_update() {
152 let original = Ai::new("test-ai".to_string()).build();
153 let valid_update = Ai::new("test-ai".to_string()).build();
154 let invalid_update = Ai::new("different-ai".to_string()).build();
155
156 assert!(original.validate_update(&valid_update).is_ok());
157 assert!(original.validate_update(&invalid_update).is_err());
158 }
159
160 #[test]
161 fn test_ai_outputs_serialization() {
162 let outputs = AiOutputs {
163 provider: "bedrock".to_string(),
164 endpoint: Some("https://bedrock-runtime.us-east-1.amazonaws.com".to_string()),
165 account: None,
166 };
167
168 let json = serde_json::to_string(&outputs).unwrap();
169 let deserialized: AiOutputs = serde_json::from_str(&json).unwrap();
170 assert_eq!(outputs, deserialized);
171 }
172}