wasmer_deploy_schema/schema/
app_version.rs

1use anyhow::Context;
2use schemars::JsonSchema;
3use serde::{Deserialize, Serialize};
4
5use super::{entity::EntityDescriptorConst, AnyEntity, WorkloadV2};
6
7/// Describes a backend application.
8///
9/// Will usually be converted from [`super::AppConfigV1`].
10#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Clone, Debug)]
11pub struct AppVersionV1Spec {
12    pub app_id: String,
13    pub app_version_id: String,
14
15    /// A list of alias names for the app.
16    /// Aliases can be used to access the app through app domains.
17    #[serde(default)]
18    #[serde(skip_serializing_if = "Vec::is_empty")]
19    pub aliases: Vec<String>,
20
21    /// The workload to execute.
22    pub workload: WorkloadV2,
23}
24
25#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Clone, Debug)]
26pub struct AppVersionV1State {}
27
28impl EntityDescriptorConst for AppVersionV1Spec {
29    const NAMESPACE: &'static str = "wasmer.io";
30    const NAME: &'static str = "AppVersion";
31    const VERSION: &'static str = "1";
32    const KIND: &'static str = "wasmer.io/AppVersion.v1";
33
34    type Spec = AppVersionV1Spec;
35    type State = AppVersionV1State;
36}
37
38pub type AppVersionV1 = super::Entity<AppVersionV1Spec, AnyEntity>;
39
40/// Parse either a deprecated AppV1 or a new AppVersionV1 from yaml.
41pub fn parse_upcast_app_version_yaml(yaml: &str) -> Result<AppVersionV1, anyhow::Error> {
42    let value: serde_yaml::Value =
43        serde_yaml::from_str(yaml).context("could not parse app version: invalid yaml")?;
44
45    let kind = value.get("kind").and_then(|x| x.as_str());
46
47    if kind == Some(AppVersionV1Spec::KIND) {
48        serde_yaml::from_value::<AppVersionV1>(value)
49            .context("could not parse app version: invalid app v1 schema")
50    } else if kind == Some(super::AppV1Spec::KIND) {
51        let mut app: super::AppV1 = serde_yaml::from_value(value)
52            .context("could not parse app version: invalid app v1 schema")?;
53
54        let annotations = super::AppMeta::try_from_annotations(&app.meta.annotations)?;
55        app.meta
56            .annotations
57            .remove(super::AppMeta::ANNOTATION_BACKEND_APP_ID);
58        app.meta
59            .annotations
60            .remove(super::AppMeta::ANNOTATION_BACKEND_APP_VERSION_ID);
61
62        Ok(AppVersionV1 {
63            meta: app.meta,
64            spec: AppVersionV1Spec {
65                app_id: annotations.app_id,
66                app_version_id: annotations.app_version_id,
67                aliases: app.spec.aliases,
68                workload: app.spec.workload,
69            },
70            children: None,
71        })
72    } else if let Some(other) = kind {
73        anyhow::bail!("could not parse app version: unknown kind: {other}");
74    } else {
75        anyhow::bail!("could not parse app version: no kind field in yaml");
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use pretty_assertions::assert_eq;
82
83    use crate::schema::EntityMeta;
84
85    use super::*;
86
87    /// Tests serilization and deserialization of the [`AppV1`] struct.
88    #[test]
89    fn test_deser_app_v1_sparse() {
90        let inp = r#"
91kind: wasmer.io/App.v1
92meta:
93  name: my-app
94spec:
95  app_id: da_123
96  app_version_id: dav_123
97  workload:
98    source: theduke/amaze
99"#;
100
101        let a1 = serde_yaml::from_str::<AppVersionV1>(inp).unwrap();
102
103        assert_eq!(
104            a1,
105            AppVersionV1 {
106                meta: EntityMeta::new("my-app"),
107                spec: AppVersionV1Spec {
108                    app_id: "da_123".to_string(),
109                    app_version_id: "dav_123".to_string(),
110                    aliases: Vec::new(),
111                    workload: crate::schema::WorkloadV2 {
112                        source: "theduke/amaze".parse().unwrap(),
113                        capabilities: Default::default(),
114                    },
115                },
116                children: None,
117            },
118        );
119    }
120
121    #[test]
122    fn test_deser_app_v1_full() {
123        let inp = r#"
124kind: wasmer.io/App.v1
125meta:
126  name: my-app
127  description: hello
128  labels:
129    "my/label": "value"
130  annotations:
131    "my/annotation": {nested: [1, 2, 3]}
132spec:
133  app_id: da_123
134  app_version_id: dav_123
135  aliases:
136    - a
137    - b
138  workload:
139    source: "theduke/my-app"
140"#;
141
142        let a1 = serde_yaml::from_str::<AppVersionV1>(inp).unwrap();
143
144        let expected = AppVersionV1 {
145            meta: EntityMeta {
146                name: "my-app".to_string(),
147                description: Some("hello".to_string()),
148                labels: vec![("my/label".to_string(), "value".to_string())]
149                    .into_iter()
150                    .collect(),
151                annotations: vec![(
152                    "my/annotation".to_string(),
153                    serde_json::json!({
154                        "nested": [1, 2, 3],
155                    }),
156                )]
157                .into_iter()
158                .collect(),
159                parent: None,
160            },
161            spec: AppVersionV1Spec {
162                app_id: "da_123".to_string(),
163                app_version_id: "dav_123".to_string(),
164                aliases: vec!["a".to_string(), "b".to_string()],
165                workload: WorkloadV2 {
166                    source: "theduke/my-app".parse().unwrap(),
167                    capabilities: Default::default(),
168                },
169            },
170            children: None,
171        };
172
173        assert_eq!(a1, expected);
174    }
175}