use std::collections::HashMap;
use anyhow::Context;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use super::{entity::EntityDescriptorConst, AnyEntity, Entity, WorkloadV2};
pub const APP_ID_PREFIX: &str = "da_";
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct AppMeta {
pub app_id: String,
pub app_version_id: String,
}
impl AppMeta {
pub const ANNOTATION_BACKEND_APP_ID: &str = "wasmer.io/app_id";
pub const ANNOTATION_BACKEND_APP_VERSION_ID: &str = "wasmer.io/app_version_id";
pub fn try_from_annotations(
map: &HashMap<String, serde_json::Value>,
) -> Result<Self, anyhow::Error> {
let app_id = map
.get(Self::ANNOTATION_BACKEND_APP_ID)
.context("missing annotation for app id")?
.as_str()
.context("app id annotation is not a string")?
.to_string();
let app_version_id = map
.get(Self::ANNOTATION_BACKEND_APP_VERSION_ID)
.context("missing annotation for version id")?
.as_str()
.context("version id annotation is not a string")?
.to_string();
Ok(Self {
app_id,
app_version_id,
})
}
pub fn try_from_entity<T>(entity: &Entity<T>) -> Result<Self, anyhow::Error> {
Self::try_from_annotations(&entity.meta.annotations)
}
pub fn new(app_id: String, app_version_id: String) -> Self {
Self {
app_id,
app_version_id,
}
}
pub fn to_annotations_map(self) -> HashMap<String, serde_json::Value> {
let mut map = HashMap::new();
map.insert(
Self::ANNOTATION_BACKEND_APP_ID.to_string(),
serde_json::Value::String(self.app_id),
);
map.insert(
Self::ANNOTATION_BACKEND_APP_VERSION_ID.to_string(),
serde_json::Value::String(self.app_version_id),
);
map
}
}
#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Clone, Debug)]
pub struct AppV1Spec {
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub aliases: Vec<String>,
pub workload: WorkloadV2,
}
#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Clone, Debug)]
pub struct AppStateV1 {}
impl EntityDescriptorConst for AppV1Spec {
const NAMESPACE: &'static str = "wasmer.io";
const NAME: &'static str = "App";
const VERSION: &'static str = "1-alpha1";
const KIND: &'static str = "wasmer.io/App.v1";
type Spec = AppV1Spec;
type State = AppStateV1;
}
pub type AppV1 = super::Entity<AppV1Spec, AnyEntity>;
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use crate::schema::{EntityMeta, EnvVarV1};
use super::*;
#[test]
fn test_deser_app_v1_sparse() {
let inp = r#"
kind: wasmer.io/App.v1
meta:
name: my-app
spec:
workload:
source: theduke/amaze
"#;
let a1 = serde_yaml::from_str::<AppV1>(inp).unwrap();
assert_eq!(
a1,
AppV1 {
meta: EntityMeta::new("my-app"),
spec: AppV1Spec {
aliases: Vec::new(),
workload: crate::schema::WorkloadV2 {
source: "theduke/amaze".parse().unwrap(),
capabilities: Default::default(),
},
},
children: None,
},
);
}
#[test]
fn test_deser_app_v1_full() {
let inp = r#"
kind: wasmer.io/App.v1
meta:
name: my-app
description: hello
labels:
"my/label": "value"
annotations:
"my/annotation": {nested: [1, 2, 3]}
spec:
aliases:
- a
- b
workload:
source: "theduke/my-app"
"#;
let a1 = serde_yaml::from_str::<AppV1>(inp).unwrap();
let expected = AppV1 {
meta: EntityMeta {
uid: None,
name: "my-app".to_string(),
description: Some("hello".to_string()),
labels: vec![("my/label".to_string(), "value".to_string())]
.into_iter()
.collect(),
annotations: vec![(
"my/annotation".to_string(),
serde_json::json!({
"nested": [1, 2, 3],
}),
)]
.into_iter()
.collect(),
parent: None,
},
spec: AppV1Spec {
aliases: vec!["a".to_string(), "b".to_string()],
workload: WorkloadV2 {
source: "theduke/my-app".parse().unwrap(),
capabilities: Default::default(),
},
},
children: None,
};
assert_eq!(a1, expected,);
}
#[test]
fn test_deser_app_v1_with_cli_and_env_cap() {
let raw = r#"
kind: wasmer.io/App.v1
meta:
description: ''
name: christoph/pyenv-dump
spec:
workload:
capabilities:
wasi:
cli_args:
- /src/main.py
env_vars:
- name: PORT
value: '80'
source: wasmer-tests/python-env-dump@0.3.6
"#;
let a1 = serde_yaml::from_str::<AppV1>(raw).unwrap();
let expected = AppV1 {
meta: EntityMeta {
uid: None,
name: "christoph/pyenv-dump".to_string(),
description: Some("".to_string()),
labels: Default::default(),
annotations: Default::default(),
parent: None,
},
spec: AppV1Spec {
aliases: Default::default(),
workload: WorkloadV2 {
source: "wasmer-tests/python-env-dump@0.3.6".parse().unwrap(),
capabilities: crate::schema::CapabilityMapV1 {
wasi: Some(crate::schema::CapabilityWasiV1 {
cli_args: Some(vec!["/src/main.py".to_string()]),
env_vars: Some(vec![EnvVarV1 {
name: "PORT".to_string(),
source: crate::schema::EnvVarSourceV1::Value("80".to_string()),
}]),
}),
..Default::default()
},
},
},
children: None,
};
assert_eq!(a1, expected,);
}
}