1use crate::error::Result;
2use serde::{Deserialize, Serialize};
3use std::any::Any;
4use std::borrow::Cow;
5use std::fmt::Debug;
6#[cfg(feature = "openapi")]
7use utoipa::openapi::schema::AdditionalProperties;
8#[cfg(feature = "openapi")]
9use utoipa::openapi::{ObjectBuilder, Ref, RefOr, Schema, Type};
10#[cfg(feature = "openapi")]
11use utoipa::{PartialSchema, ToSchema};
12
13#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
15#[serde(transparent)]
16pub struct ResourceType(pub Cow<'static, str>);
17
18impl ResourceType {
19 pub const fn from_static(s: &'static str) -> Self {
21 Self(Cow::Borrowed(s))
22 }
23}
24
25impl From<String> for ResourceType {
26 fn from(s: String) -> Self {
27 Self(Cow::Owned(s))
28 }
29}
30
31impl From<&str> for ResourceType {
32 fn from(s: &str) -> Self {
33 Self(Cow::Owned(s.to_string()))
34 }
35}
36
37impl std::fmt::Display for ResourceType {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 write!(f, "{}", self.0)
40 }
41}
42
43impl From<ResourceType> for String {
44 fn from(val: ResourceType) -> Self {
45 val.0.into_owned()
46 }
47}
48
49impl AsRef<str> for ResourceType {
50 fn as_ref(&self) -> &str {
51 &self.0
52 }
53}
54
55#[cfg(feature = "openapi")]
56impl PartialSchema for ResourceType {
57 fn schema() -> RefOr<Schema> {
58 RefOr::T(Schema::Object(
59 ObjectBuilder::new()
60 .schema_type(Type::String)
61 .description(Some("Resource type identifier that determines the specific kind of resource. This field is used for polymorphic deserialization and resource-specific behavior."))
62 .examples([
63 "function",
64 "storage",
65 "queue",
66 "redis",
67 "postgres"
68 ])
69 .build()
70 ))
71 }
72}
73
74#[cfg(feature = "openapi")]
75impl ToSchema for ResourceType {
76 fn name() -> std::borrow::Cow<'static, str> {
77 std::borrow::Cow::Borrowed("ResourceType")
78 }
79}
80
81#[typetag::serde(tag = "type")]
85pub trait ResourceDefinition: Debug + Send + Sync + 'static {
86 fn resource_type() -> ResourceType
88 where
89 Self: Sized;
90
91 fn get_resource_type(&self) -> ResourceType;
93
94 fn id(&self) -> &str;
96
97 fn get_dependencies(&self) -> Vec<ResourceRef>;
99
100 fn get_permissions(&self) -> Option<&str> {
108 None
109 }
110
111 fn validate_update(&self, new_config: &dyn ResourceDefinition) -> Result<()>;
113
114 fn as_any(&self) -> &dyn Any;
116
117 fn as_any_mut(&mut self) -> &mut dyn Any;
119
120 fn box_clone(&self) -> Box<dyn ResourceDefinition>;
122
123 fn resource_eq(&self, other: &dyn ResourceDefinition) -> bool;
125}
126
127impl Clone for Box<dyn ResourceDefinition> {
129 fn clone(&self) -> Self {
130 self.box_clone()
131 }
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct Resource {
136 #[serde(flatten)]
137 inner: Box<dyn ResourceDefinition>,
138}
139
140impl Resource {
141 pub fn new<T: ResourceDefinition>(resource: T) -> Self {
143 Self {
144 inner: Box::new(resource),
145 }
146 }
147
148 pub fn from_boxed(boxed_resource: Box<dyn ResourceDefinition>) -> Self {
150 Self {
151 inner: boxed_resource,
152 }
153 }
154
155 pub fn resource_type(&self) -> ResourceType {
157 self.inner.get_resource_type()
158 }
159
160 pub fn id(&self) -> &str {
162 self.inner.id()
163 }
164
165 pub fn get_dependencies(&self) -> Vec<ResourceRef> {
167 self.inner.get_dependencies()
168 }
169
170 pub fn get_permissions(&self) -> Option<&str> {
172 self.inner.get_permissions()
173 }
174
175 pub fn validate_update(&self, new_config: &Resource) -> Result<()> {
177 self.inner.validate_update(new_config.inner.as_ref())
178 }
179
180 pub fn as_resource_definition(&self) -> &dyn ResourceDefinition {
182 self.inner.as_ref()
183 }
184
185 pub fn downcast_ref<T: ResourceDefinition + 'static>(&self) -> Option<&T> {
187 self.inner.as_any().downcast_ref::<T>()
188 }
189
190 pub fn downcast_mut<T: ResourceDefinition + 'static>(&mut self) -> Option<&mut T> {
192 self.inner.as_any_mut().downcast_mut::<T>()
193 }
194}
195
196impl PartialEq for Resource {
197 fn eq(&self, other: &Self) -> bool {
198 self.inner.resource_eq(other.inner.as_ref())
199 }
200}
201
202impl Eq for Resource {}
203
204#[cfg(feature = "openapi")]
226impl PartialSchema for Resource {
227 fn schema() -> RefOr<Schema> {
228 RefOr::T(Schema::Object(
229 ObjectBuilder::new()
230 .schema_type(Type::Object)
231 .property("type", Ref::from_schema_name("ResourceType"))
232 .property("id",
233 ObjectBuilder::new()
234 .schema_type(Type::String)
235 .description(Some("The unique identifier for this specific resource instance. Must contain only alphanumeric characters, hyphens, and underscores ([A-Za-z0-9-_]). Maximum 64 characters."))
236 .build()
237 )
238 .required("type")
239 .required("id")
240 .additional_properties(Some(AdditionalProperties::FreeForm(true)))
241 .description(Some("Resource that can hold any resource type in the Alien system. All resources share common 'type' and 'id' fields with additional type-specific properties."))
242 .build()
243 ))
244 }
245}
246
247#[cfg(feature = "openapi")]
248impl ToSchema for Resource {
249 fn name() -> std::borrow::Cow<'static, str> {
250 std::borrow::Cow::Borrowed("BaseResource")
251 }
252}
253
254#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
257#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
258#[serde(rename_all = "camelCase")]
259pub struct ResourceRef {
260 #[serde(rename = "type")]
261 pub resource_type: ResourceType,
262 pub id: String,
263}
264
265impl ResourceRef {
266 pub fn new(resource_type: ResourceType, id: impl Into<String>) -> Self {
268 Self {
269 resource_type,
270 id: id.into(),
271 }
272 }
273
274 pub fn resource_type(&self) -> &ResourceType {
276 &self.resource_type
277 }
278
279 pub fn id(&self) -> &str {
281 &self.id
282 }
283}
284
285impl<T: ResourceDefinition> From<&T> for ResourceRef {
286 fn from(resource: &T) -> Self {
287 Self::new(T::resource_type(), resource.id())
288 }
289}
290
291impl From<&Resource> for ResourceRef {
292 fn from(resource: &Resource) -> Self {
293 Self::new(resource.resource_type(), resource.id())
294 }
295}
296
297#[typetag::serde(tag = "type")]
301pub trait ResourceOutputsDefinition: Debug + Send + Sync + 'static {
302 fn resource_type() -> ResourceType
304 where
305 Self: Sized;
306
307 fn as_any(&self) -> &dyn Any;
309
310 fn box_clone(&self) -> Box<dyn ResourceOutputsDefinition>;
312
313 fn outputs_eq(&self, other: &dyn ResourceOutputsDefinition) -> bool;
315}
316
317impl Clone for Box<dyn ResourceOutputsDefinition> {
319 fn clone(&self) -> Self {
320 self.box_clone()
321 }
322}
323
324#[derive(Debug, Clone, Serialize, Deserialize)]
327pub struct ResourceOutputs {
328 #[serde(flatten)]
329 inner: Box<dyn ResourceOutputsDefinition>,
330}
331
332impl ResourceOutputs {
333 pub fn new<T: ResourceOutputsDefinition>(outputs: T) -> Self {
335 Self {
336 inner: Box::new(outputs),
337 }
338 }
339
340 pub fn as_resource_outputs(&self) -> &dyn ResourceOutputsDefinition {
342 self.inner.as_ref()
343 }
344
345 pub fn downcast_ref<T: ResourceOutputsDefinition + 'static>(&self) -> Option<&T> {
347 self.inner.as_any().downcast_ref::<T>()
348 }
349}
350
351impl PartialEq for ResourceOutputs {
352 fn eq(&self, other: &Self) -> bool {
353 self.inner.outputs_eq(other.inner.as_ref())
354 }
355}
356
357impl Eq for ResourceOutputs {}
358
359#[cfg(feature = "openapi")]
378impl PartialSchema for ResourceOutputs {
379 fn schema() -> RefOr<Schema> {
380 RefOr::T(Schema::Object(
381 ObjectBuilder::new()
382 .schema_type(Type::Object)
383 .property("type", Ref::from_schema_name("ResourceType"))
384 .required("type")
385 .additional_properties(Some(AdditionalProperties::FreeForm(true)))
386 .description(Some("Resource outputs that can hold output data for any resource type in the Alien system. All resource outputs share a common 'type' field with additional type-specific output properties."))
387 .build()
388 ))
389 }
390}
391
392#[cfg(feature = "openapi")]
393impl ToSchema for ResourceOutputs {
394 fn name() -> std::borrow::Cow<'static, str> {
395 std::borrow::Cow::Borrowed("BaseResourceOutputs")
396 }
397}
398
399#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
401#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
402#[serde(rename_all = "kebab-case")]
403pub enum ResourceStatus {
404 Pending, Provisioning, ProvisionFailed,
407 Running, Updating,
409 UpdateFailed,
410 Deleting, DeleteFailed,
412 Deleted, RefreshFailed, }
415
416impl ResourceStatus {
417 pub fn is_terminal(&self) -> bool {
418 match self {
419 ResourceStatus::Deleted => true,
420 ResourceStatus::ProvisionFailed => true,
421 ResourceStatus::UpdateFailed => true,
422 ResourceStatus::DeleteFailed => true,
423 ResourceStatus::RefreshFailed => true,
424 _ => false, }
426 }
427}
428
429#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Hash, Deserialize)]
431#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
432#[serde(rename_all = "kebab-case")]
433pub enum ResourceLifecycle {
434 Frozen,
438
439 Live,
444
445 LiveOnSetup,
450}
451
452impl ResourceLifecycle {
453 pub fn is_live(&self) -> bool {
455 match self {
456 ResourceLifecycle::Frozen => false,
457 ResourceLifecycle::Live | ResourceLifecycle::LiveOnSetup => true,
458 }
459 }
460
461 pub fn initial_setup(&self) -> bool {
464 match self {
465 ResourceLifecycle::Frozen | ResourceLifecycle::LiveOnSetup => true,
466 ResourceLifecycle::Live => false,
467 }
468 }
469}