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
81pub trait ResourceDefinition: Debug + Send + Sync + 'static {
85 fn get_resource_type(&self) -> ResourceType;
87
88 fn id(&self) -> &str;
90
91 fn get_dependencies(&self) -> Vec<ResourceRef>;
93
94 fn get_permissions(&self) -> Option<&str> {
102 None
103 }
104
105 fn validate_update(&self, new_config: &dyn ResourceDefinition) -> Result<()>;
107
108 fn as_any(&self) -> &dyn Any;
110
111 fn as_any_mut(&mut self) -> &mut dyn Any;
113
114 fn box_clone(&self) -> Box<dyn ResourceDefinition>;
116
117 fn resource_eq(&self, other: &dyn ResourceDefinition) -> bool;
119
120 fn to_json_value(&self) -> serde_json::Result<serde_json::Value>;
122}
123
124impl Clone for Box<dyn ResourceDefinition> {
126 fn clone(&self) -> Self {
127 self.box_clone()
128 }
129}
130
131#[derive(Debug, Clone)]
132pub struct Resource {
133 inner: Box<dyn ResourceDefinition>,
134}
135
136impl Serialize for Resource {
137 fn serialize<S: serde::Serializer>(
138 &self,
139 serializer: S,
140 ) -> std::result::Result<S::Ok, S::Error> {
141 let mut v = self
142 .inner
143 .to_json_value()
144 .map_err(serde::ser::Error::custom)?;
145 v.as_object_mut()
146 .ok_or_else(|| serde::ser::Error::custom("resource must serialize as object"))?
147 .insert(
148 "type".into(),
149 serde_json::Value::String(self.inner.get_resource_type().0.into_owned()),
150 );
151 v.serialize(serializer)
152 }
153}
154
155impl<'de> Deserialize<'de> for Resource {
156 fn deserialize<D: serde::Deserializer<'de>>(
157 deserializer: D,
158 ) -> std::result::Result<Self, D::Error> {
159 let mut value = serde_json::Value::deserialize(deserializer)?;
160 let type_tag = value
161 .get("type")
162 .and_then(|v| v.as_str())
163 .ok_or_else(|| serde::de::Error::missing_field("type"))?
164 .to_string();
165
166 if let Some(obj) = value.as_object_mut() {
169 obj.remove("type");
170 }
171
172 let inner: Box<dyn ResourceDefinition> = match type_tag.as_str() {
173 "vault" => Box::new(
174 serde_json::from_value::<crate::resources::Vault>(value)
175 .map_err(serde::de::Error::custom)?,
176 ),
177 "function" => Box::new(
178 serde_json::from_value::<crate::resources::Function>(value)
179 .map_err(serde::de::Error::custom)?,
180 ),
181 "container" => Box::new(
182 serde_json::from_value::<crate::resources::Container>(value)
183 .map_err(serde::de::Error::custom)?,
184 ),
185 "container-cluster" => Box::new(
186 serde_json::from_value::<crate::resources::ContainerCluster>(value)
187 .map_err(serde::de::Error::custom)?,
188 ),
189 "storage" => Box::new(
190 serde_json::from_value::<crate::resources::Storage>(value)
191 .map_err(serde::de::Error::custom)?,
192 ),
193 "queue" => Box::new(
194 serde_json::from_value::<crate::resources::Queue>(value)
195 .map_err(serde::de::Error::custom)?,
196 ),
197 "kv" => Box::new(
198 serde_json::from_value::<crate::resources::Kv>(value)
199 .map_err(serde::de::Error::custom)?,
200 ),
201 "network" => Box::new(
202 serde_json::from_value::<crate::resources::Network>(value)
203 .map_err(serde::de::Error::custom)?,
204 ),
205 "build" => Box::new(
206 serde_json::from_value::<crate::resources::Build>(value)
207 .map_err(serde::de::Error::custom)?,
208 ),
209 "service-account" => Box::new(
210 serde_json::from_value::<crate::resources::ServiceAccount>(value)
211 .map_err(serde::de::Error::custom)?,
212 ),
213 "artifact-registry" => Box::new(
214 serde_json::from_value::<crate::resources::ArtifactRegistry>(value)
215 .map_err(serde::de::Error::custom)?,
216 ),
217 "service_activation" => Box::new(
218 serde_json::from_value::<crate::resources::ServiceActivation>(value)
219 .map_err(serde::de::Error::custom)?,
220 ),
221 "remote-stack-management" => Box::new(
222 serde_json::from_value::<crate::resources::RemoteStackManagement>(value)
223 .map_err(serde::de::Error::custom)?,
224 ),
225 "azure_resource_group" => Box::new(
226 serde_json::from_value::<crate::resources::AzureResourceGroup>(value)
227 .map_err(serde::de::Error::custom)?,
228 ),
229 "azure_storage_account" => Box::new(
230 serde_json::from_value::<crate::resources::AzureStorageAccount>(value)
231 .map_err(serde::de::Error::custom)?,
232 ),
233 "azure_container_apps_environment" => Box::new(
234 serde_json::from_value::<crate::resources::AzureContainerAppsEnvironment>(value)
235 .map_err(serde::de::Error::custom)?,
236 ),
237 "azure_service_bus_namespace" => Box::new(
238 serde_json::from_value::<crate::resources::AzureServiceBusNamespace>(value)
239 .map_err(serde::de::Error::custom)?,
240 ),
241 other => {
242 return Err(serde::de::Error::unknown_variant(
243 other,
244 &[
245 "vault",
246 "function",
247 "container",
248 "container-cluster",
249 "storage",
250 "queue",
251 "kv",
252 "network",
253 "build",
254 "service-account",
255 "artifact-registry",
256 "service_activation",
257 "remote-stack-management",
258 "azure_resource_group",
259 "azure_storage_account",
260 "azure_container_apps_environment",
261 "azure_service_bus_namespace",
262 ],
263 ))
264 }
265 };
266
267 Ok(Resource { inner })
268 }
269}
270
271impl Resource {
272 pub fn new<T: ResourceDefinition>(resource: T) -> Self {
274 Self {
275 inner: Box::new(resource),
276 }
277 }
278
279 pub fn from_boxed(boxed_resource: Box<dyn ResourceDefinition>) -> Self {
281 Self {
282 inner: boxed_resource,
283 }
284 }
285
286 pub fn resource_type(&self) -> ResourceType {
288 self.inner.get_resource_type()
289 }
290
291 pub fn id(&self) -> &str {
293 self.inner.id()
294 }
295
296 pub fn get_dependencies(&self) -> Vec<ResourceRef> {
298 self.inner.get_dependencies()
299 }
300
301 pub fn get_permissions(&self) -> Option<&str> {
303 self.inner.get_permissions()
304 }
305
306 pub fn validate_update(&self, new_config: &Resource) -> Result<()> {
308 self.inner.validate_update(new_config.inner.as_ref())
309 }
310
311 pub fn as_resource_definition(&self) -> &dyn ResourceDefinition {
313 self.inner.as_ref()
314 }
315
316 pub fn downcast_ref<T: ResourceDefinition + 'static>(&self) -> Option<&T> {
318 self.inner.as_any().downcast_ref::<T>()
319 }
320
321 pub fn downcast_mut<T: ResourceDefinition + 'static>(&mut self) -> Option<&mut T> {
323 self.inner.as_any_mut().downcast_mut::<T>()
324 }
325}
326
327impl PartialEq for Resource {
328 fn eq(&self, other: &Self) -> bool {
329 self.inner.resource_eq(other.inner.as_ref())
330 }
331}
332
333impl Eq for Resource {}
334
335#[cfg(feature = "openapi")]
357impl PartialSchema for Resource {
358 fn schema() -> RefOr<Schema> {
359 RefOr::T(Schema::Object(
360 ObjectBuilder::new()
361 .schema_type(Type::Object)
362 .property("type", Ref::from_schema_name("ResourceType"))
363 .property("id",
364 ObjectBuilder::new()
365 .schema_type(Type::String)
366 .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."))
367 .build()
368 )
369 .required("type")
370 .required("id")
371 .additional_properties(Some(AdditionalProperties::FreeForm(true)))
372 .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."))
373 .build()
374 ))
375 }
376}
377
378#[cfg(feature = "openapi")]
379impl ToSchema for Resource {
380 fn name() -> std::borrow::Cow<'static, str> {
381 std::borrow::Cow::Borrowed("BaseResource")
382 }
383}
384
385#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
388#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
389#[serde(rename_all = "camelCase")]
390pub struct ResourceRef {
391 #[serde(rename = "type")]
392 pub resource_type: ResourceType,
393 pub id: String,
394}
395
396impl ResourceRef {
397 pub fn new(resource_type: ResourceType, id: impl Into<String>) -> Self {
399 Self {
400 resource_type,
401 id: id.into(),
402 }
403 }
404
405 pub fn resource_type(&self) -> &ResourceType {
407 &self.resource_type
408 }
409
410 pub fn id(&self) -> &str {
412 &self.id
413 }
414}
415
416impl<T: ResourceDefinition> From<&T> for ResourceRef {
417 fn from(resource: &T) -> Self {
418 Self::new(resource.get_resource_type(), resource.id())
419 }
420}
421
422impl From<&Resource> for ResourceRef {
423 fn from(resource: &Resource) -> Self {
424 Self::new(resource.resource_type(), resource.id())
425 }
426}
427
428pub trait ResourceOutputsDefinition: Debug + Send + Sync + 'static {
432 fn get_resource_type(&self) -> ResourceType;
434
435 fn as_any(&self) -> &dyn Any;
437
438 fn box_clone(&self) -> Box<dyn ResourceOutputsDefinition>;
440
441 fn outputs_eq(&self, other: &dyn ResourceOutputsDefinition) -> bool;
443
444 fn to_json_value(&self) -> serde_json::Result<serde_json::Value>;
446}
447
448impl Clone for Box<dyn ResourceOutputsDefinition> {
450 fn clone(&self) -> Self {
451 self.box_clone()
452 }
453}
454
455#[derive(Debug, Clone)]
458pub struct ResourceOutputs {
459 inner: Box<dyn ResourceOutputsDefinition>,
460}
461
462impl Serialize for ResourceOutputs {
463 fn serialize<S: serde::Serializer>(
464 &self,
465 serializer: S,
466 ) -> std::result::Result<S::Ok, S::Error> {
467 let mut v = self
468 .inner
469 .to_json_value()
470 .map_err(serde::ser::Error::custom)?;
471 v.as_object_mut()
472 .ok_or_else(|| serde::ser::Error::custom("resource outputs must serialize as object"))?
473 .insert(
474 "type".into(),
475 serde_json::Value::String(self.inner.get_resource_type().0.into_owned()),
476 );
477 v.serialize(serializer)
478 }
479}
480
481impl<'de> Deserialize<'de> for ResourceOutputs {
482 fn deserialize<D: serde::Deserializer<'de>>(
483 deserializer: D,
484 ) -> std::result::Result<Self, D::Error> {
485 let mut value = serde_json::Value::deserialize(deserializer)?;
486 let type_tag = value
487 .get("type")
488 .and_then(|v| v.as_str())
489 .ok_or_else(|| serde::de::Error::missing_field("type"))?
490 .to_string();
491
492 if let Some(obj) = value.as_object_mut() {
495 obj.remove("type");
496 }
497
498 let inner: Box<dyn ResourceOutputsDefinition> = match type_tag.as_str() {
499 "vault" => Box::new(
500 serde_json::from_value::<crate::resources::VaultOutputs>(value)
501 .map_err(serde::de::Error::custom)?,
502 ),
503 "function" => Box::new(
504 serde_json::from_value::<crate::resources::FunctionOutputs>(value)
505 .map_err(serde::de::Error::custom)?,
506 ),
507 "container" => Box::new(
508 serde_json::from_value::<crate::resources::ContainerOutputs>(value)
509 .map_err(serde::de::Error::custom)?,
510 ),
511 "container-cluster" => Box::new(
512 serde_json::from_value::<crate::resources::ContainerClusterOutputs>(value)
513 .map_err(serde::de::Error::custom)?,
514 ),
515 "storage" => Box::new(
516 serde_json::from_value::<crate::resources::StorageOutputs>(value)
517 .map_err(serde::de::Error::custom)?,
518 ),
519 "queue" => Box::new(
520 serde_json::from_value::<crate::resources::QueueOutputs>(value)
521 .map_err(serde::de::Error::custom)?,
522 ),
523 "kv" => Box::new(
524 serde_json::from_value::<crate::resources::KvOutputs>(value)
525 .map_err(serde::de::Error::custom)?,
526 ),
527 "network" => Box::new(
528 serde_json::from_value::<crate::resources::NetworkOutputs>(value)
529 .map_err(serde::de::Error::custom)?,
530 ),
531 "build" => Box::new(
532 serde_json::from_value::<crate::resources::BuildOutputs>(value)
533 .map_err(serde::de::Error::custom)?,
534 ),
535 "service-account" => Box::new(
536 serde_json::from_value::<crate::resources::ServiceAccountOutputs>(value)
537 .map_err(serde::de::Error::custom)?,
538 ),
539 "artifact-registry" => Box::new(
540 serde_json::from_value::<crate::resources::ArtifactRegistryOutputs>(value)
541 .map_err(serde::de::Error::custom)?,
542 ),
543 "service_activation" => Box::new(
544 serde_json::from_value::<crate::resources::ServiceActivationOutputs>(value)
545 .map_err(serde::de::Error::custom)?,
546 ),
547 "remote-stack-management" => Box::new(
548 serde_json::from_value::<crate::resources::RemoteStackManagementOutputs>(value)
549 .map_err(serde::de::Error::custom)?,
550 ),
551 "azure_resource_group" => Box::new(
552 serde_json::from_value::<crate::resources::AzureResourceGroupOutputs>(value)
553 .map_err(serde::de::Error::custom)?,
554 ),
555 "azure_storage_account" => Box::new(
556 serde_json::from_value::<crate::resources::AzureStorageAccountOutputs>(value)
557 .map_err(serde::de::Error::custom)?,
558 ),
559 "azure_container_apps_environment" => Box::new(
560 serde_json::from_value::<crate::resources::AzureContainerAppsEnvironmentOutputs>(
561 value,
562 )
563 .map_err(serde::de::Error::custom)?,
564 ),
565 "azure_service_bus_namespace" => Box::new(
566 serde_json::from_value::<crate::resources::AzureServiceBusNamespaceOutputs>(value)
567 .map_err(serde::de::Error::custom)?,
568 ),
569 other => {
570 return Err(serde::de::Error::unknown_variant(
571 other,
572 &[
573 "vault",
574 "function",
575 "container",
576 "container-cluster",
577 "storage",
578 "queue",
579 "kv",
580 "network",
581 "build",
582 "service-account",
583 "artifact-registry",
584 "service_activation",
585 "remote-stack-management",
586 "azure_resource_group",
587 "azure_storage_account",
588 "azure_container_apps_environment",
589 "azure_service_bus_namespace",
590 ],
591 ))
592 }
593 };
594
595 Ok(ResourceOutputs { inner })
596 }
597}
598
599impl ResourceOutputs {
600 pub fn new<T: ResourceOutputsDefinition>(outputs: T) -> Self {
602 Self {
603 inner: Box::new(outputs),
604 }
605 }
606
607 pub fn as_resource_outputs(&self) -> &dyn ResourceOutputsDefinition {
609 self.inner.as_ref()
610 }
611
612 pub fn downcast_ref<T: ResourceOutputsDefinition + 'static>(&self) -> Option<&T> {
614 self.inner.as_any().downcast_ref::<T>()
615 }
616}
617
618impl PartialEq for ResourceOutputs {
619 fn eq(&self, other: &Self) -> bool {
620 self.inner.outputs_eq(other.inner.as_ref())
621 }
622}
623
624impl Eq for ResourceOutputs {}
625
626#[cfg(feature = "openapi")]
645impl PartialSchema for ResourceOutputs {
646 fn schema() -> RefOr<Schema> {
647 RefOr::T(Schema::Object(
648 ObjectBuilder::new()
649 .schema_type(Type::Object)
650 .property("type", Ref::from_schema_name("ResourceType"))
651 .required("type")
652 .additional_properties(Some(AdditionalProperties::FreeForm(true)))
653 .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."))
654 .build()
655 ))
656 }
657}
658
659#[cfg(feature = "openapi")]
660impl ToSchema for ResourceOutputs {
661 fn name() -> std::borrow::Cow<'static, str> {
662 std::borrow::Cow::Borrowed("BaseResourceOutputs")
663 }
664}
665
666#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
668#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
669#[serde(rename_all = "kebab-case")]
670pub enum ResourceStatus {
671 Pending, Provisioning, ProvisionFailed,
674 Running, Updating,
676 UpdateFailed,
677 Deleting, DeleteFailed,
679 Deleted, RefreshFailed, }
682
683impl ResourceStatus {
684 pub fn is_terminal(&self) -> bool {
685 match self {
686 ResourceStatus::Deleted => true,
687 ResourceStatus::ProvisionFailed => true,
688 ResourceStatus::UpdateFailed => true,
689 ResourceStatus::DeleteFailed => true,
690 ResourceStatus::RefreshFailed => true,
691 _ => false, }
693 }
694}
695
696#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Hash, Deserialize)]
698#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
699#[serde(rename_all = "kebab-case")]
700pub enum ResourceLifecycle {
701 Frozen,
705
706 Live,
710}