use std::time::Duration;
use serde::{Deserialize, Serialize};
use crate::PackageError;
pub const CURRENT_FORMAT_VERSION: u32 = 1;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ManifestVersion(pub String);
impl ManifestVersion {
#[must_use]
pub fn new(version: impl Into<String>) -> Self {
Self(version.into())
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct DeclaredActivity {
#[serde(rename = "activity_type")]
pub activity_type: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Manifest {
#[serde(rename = "entry_module")]
pub entry_module: String,
#[serde(rename = "entry_function")]
pub entry_function: String,
#[serde(rename = "input_schema")]
pub input_schema: serde_json::Value,
#[serde(rename = "output_schema")]
pub output_schema: serde_json::Value,
#[serde(rename = "timeout")]
pub timeout: Duration,
#[serde(rename = "activities")]
pub activities: Vec<DeclaredActivity>,
#[serde(rename = "version")]
pub version: ManifestVersion,
#[serde(rename = "format_version")]
pub format_version: u32,
}
impl Manifest {
pub fn check_format_version(&self) -> Result<(), PackageError> {
if self.format_version == CURRENT_FORMAT_VERSION {
Ok(())
} else {
Err(PackageError::UnknownFormatVersion {
found: self.format_version,
})
}
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use serde_json::json;
use super::{CURRENT_FORMAT_VERSION, DeclaredActivity, Manifest, ManifestVersion};
use crate::PackageError;
fn sample_manifest() -> Manifest {
Manifest {
entry_module: "workflow/order".to_owned(),
entry_function: "run".to_owned(),
input_schema: json!({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["order_id"],
"properties": {
"order_id": { "type": "string" },
"retry": { "type": "boolean" }
}
}),
output_schema: json!({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["status"],
"properties": {
"status": { "enum": ["accepted", "rejected"] },
"total": { "type": "number" }
}
}),
timeout: Duration::new(30, 250_000_000),
activities: vec![
DeclaredActivity {
activity_type: "charge_card".to_owned(),
},
DeclaredActivity {
activity_type: "send_receipt".to_owned(),
},
],
version: ManifestVersion::new(
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
),
format_version: CURRENT_FORMAT_VERSION,
}
}
#[test]
fn manifest_round_trips_losslessly_through_json() -> Result<(), serde_json::Error> {
let manifest = sample_manifest();
let json = serde_json::to_string(&manifest)?;
let decoded: Manifest = serde_json::from_str(&json)?;
assert_eq!(decoded, manifest);
Ok(())
}
#[test]
fn manifest_with_schemas_and_declared_activities_round_trips() -> Result<(), serde_json::Error>
{
let manifest = sample_manifest();
let json = serde_json::to_string(&manifest)?;
let decoded: Manifest = serde_json::from_str(&json)?;
assert_eq!(
decoded.input_schema["properties"]["order_id"]["type"],
"string"
);
assert_eq!(
decoded.output_schema["properties"]["status"]["enum"][0],
"accepted"
);
assert_eq!(decoded.activities.len(), 2);
assert_eq!(decoded, manifest);
Ok(())
}
#[test]
fn supported_format_version_passes() -> Result<(), PackageError> {
sample_manifest().check_format_version()
}
#[test]
fn unsupported_format_version_returns_typed_error() {
let mut manifest = sample_manifest();
manifest.format_version = CURRENT_FORMAT_VERSION + 1;
let result = manifest.check_format_version();
assert!(matches!(
result,
Err(PackageError::UnknownFormatVersion { found }) if found == CURRENT_FORMAT_VERSION + 1
));
}
#[test]
fn manifest_json_keys_are_stable() -> Result<(), serde_json::Error> {
let manifest = sample_manifest();
let json = serde_json::to_value(&manifest)?;
assert!(json.get("entry_module").is_some());
assert!(json.get("entry_function").is_some());
assert!(json.get("input_schema").is_some());
assert!(json.get("output_schema").is_some());
assert!(json.get("timeout").is_some());
assert!(json.get("activities").is_some());
assert!(json.get("version").is_some());
assert!(json.get("format_version").is_some());
assert_eq!(json["activities"][0]["activity_type"], "charge_card");
Ok(())
}
}