use std::collections::HashMap;
use anyhow::{bail, Context};
use bytesize::ByteSize;
use super::{CronJobSpecV1, StringWebcIdent};
#[derive(
serde::Serialize, serde::Deserialize, schemars::JsonSchema, Clone, Debug, PartialEq, Eq,
)]
pub struct AppConfigV1 {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub app_id: Option<String>,
pub package: StringWebcIdent,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub env: HashMap<String, String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cli_args: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub capabilities: Option<AppConfigCapabilityMapV1>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scheduled_tasks: Option<Vec<AppScheduledTask>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub debug: Option<bool>,
}
#[derive(
serde::Serialize, serde::Deserialize, schemars::JsonSchema, Clone, Debug, PartialEq, Eq,
)]
pub struct AppScheduledTask {
pub name: String,
#[serde(flatten)]
pub spec: CronJobSpecV1,
}
impl AppConfigV1 {
pub const KIND: &'static str = "wasmer.io/App.v0";
pub const CANONICAL_FILE_NAME: &'static str = "app.yaml";
pub fn to_yaml_value(self) -> Result<serde_yaml::Value, serde_yaml::Error> {
let obj = match serde_yaml::to_value(self)? {
serde_yaml::Value::Mapping(m) => m,
_ => unreachable!(),
};
let mut m = serde_yaml::Mapping::new();
m.insert("kind".into(), Self::KIND.into());
for (k, v) in obj.into_iter() {
m.insert(k, v);
}
Ok(m.into())
}
pub fn to_yaml(self) -> Result<String, serde_yaml::Error> {
serde_yaml::to_string(&self.to_yaml_value()?)
}
pub fn parse_yaml(value: &str) -> Result<Self, anyhow::Error> {
let raw = serde_yaml::from_str::<serde_yaml::Value>(value).context("invalid yaml")?;
let kind = raw
.get("kind")
.context("invalid app config: no 'kind' field found")?
.as_str()
.context("invalid app config: 'kind' field is not a string")?;
match kind {
Self::KIND => {}
other => {
bail!(
"invalid app config: unspported kind '{}', expected {}",
other,
Self::KIND
);
}
}
let data = serde_yaml::from_value(raw).context("could not deserialize app config")?;
Ok(data)
}
}
#[derive(
serde::Serialize, serde::Deserialize, schemars::JsonSchema, Clone, Debug, PartialEq, Eq,
)]
pub struct AppConfigCapabilityMapV1 {
#[serde(skip_serializing_if = "Option::is_none")]
pub memory: Option<AppConfigCapabilityMemoryV1>,
}
#[derive(
serde::Serialize, serde::Deserialize, schemars::JsonSchema, Clone, Debug, PartialEq, Eq,
)]
pub struct AppConfigCapabilityMemoryV1 {
#[schemars(with = "Option<String>")]
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<ByteSize>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_app_config_v1_deser() {
let config = r#"
kind: wasmer.io/App.v0
name: test
package: ns/name@0.1.0
debug: true
env:
e1: v1
E2: V2
cli_args:
- arg1
- arg2
scheduled_tasks:
- name: backup
schedule: 1day
max_retries: 3
timeout: 10m
invoke:
fetch:
url: /api/do-backup
headers:
h1: v1
success_status_codes: [200, 201]
"#;
let parsed = AppConfigV1::parse_yaml(config).unwrap();
assert_eq!(
parsed,
AppConfigV1 {
name: "test".to_string(),
app_id: None,
package: "ns/name@0.1.0".parse().unwrap(),
env: [
("e1".to_string(), "v1".to_string()),
("E2".to_string(), "V2".to_string())
]
.into_iter()
.collect(),
cli_args: Some(vec!["arg1".to_string(), "arg2".to_string()]),
capabilities: None,
scheduled_tasks: Some(vec![AppScheduledTask {
name: "backup".to_string(),
spec: CronJobSpecV1 {
schedule: "1day".to_string(),
max_schedule_drift: None,
job: crate::schema::JobDefinition {
max_retries: Some(3),
timeout: Some(std::time::Duration::from_secs(10 * 60).into()),
invoke: crate::schema::JobInvoke::Fetch(
crate::schema::JobInvokeFetch {
url: "/api/do-backup".parse().unwrap(),
headers: Some(
[("h1".to_string(), "v1".to_string())]
.into_iter()
.collect()
),
success_status_codes: Some(vec![200, 201]),
method: None,
}
)
},
}
}]),
debug: Some(true),
}
);
}
}