use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[cfg_attr(feature = "server", derive(utoipa::ToSchema))]
pub struct TriggerSpec {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(flatten)]
pub activation: TriggerActivation,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[cfg_attr(feature = "server", derive(utoipa::ToSchema))]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum TriggerActivation {
Schedule {
cron: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
tz: Option<String>,
},
ObservationsReady {},
}
#[cfg(test)]
mod tests {
use super::{TriggerActivation, TriggerSpec};
use crate::envelope::{CardKind, Spec};
#[test]
fn observations_ready_decodes_with_optional_description() {
let spec: TriggerSpec =
serde_json::from_value(serde_json::json!({ "kind": "observations_ready" }))
.expect("the bare activation decodes");
assert!(matches!(
spec.activation,
TriggerActivation::ObservationsReady { .. }
));
assert_eq!(spec.description, None);
let described: TriggerSpec = serde_json::from_value(serde_json::json!({
"kind": "observations_ready",
"description": "after each committed observation",
}))
.expect("description decodes beside the activation");
assert_eq!(
described.description.as_deref(),
Some("after each committed observation")
);
}
#[test]
fn observations_ready_rejects_unknown_fields() {
let error = serde_json::from_value::<TriggerSpec>(serde_json::json!({
"kind": "observations_ready",
"api_token": "sk-live-not-a-real-token",
}))
.expect_err("an unknown sibling key is refused");
assert!(error.to_string().contains("api_token"), "{error}");
}
#[test]
fn schedule_rejects_unknown_fields() {
let error = serde_json::from_value::<TriggerSpec>(serde_json::json!({
"kind": "schedule",
"cron": "0 * * * *",
"threshhold": 0.2,
}))
.expect_err("a misspelled sibling key is refused");
assert!(error.to_string().contains("threshhold"), "{error}");
}
#[test]
fn trigger_card_spec_rejects_unknown_fields() {
let error = Spec::from_kind_and_value(
&CardKind::Trigger,
serde_json::json!({
"kind": "observations_ready",
"api_token": "sk-live-not-a-real-token",
}),
)
.expect_err("a Trigger Card spec refuses the same unknown key");
assert!(error.to_string().contains("api_token"), "{error}");
}
}