use anyhow::anyhow;
use indexmap::IndexMap;
use serde::{Serialize, Deserialize};
pub type MistResult = anyhow::Result<MistOutput>;
pub fn serialize_result(result: MistResult) -> Result<String, serde_yaml::Error> {
serde_yaml::to_string(&MistResultLayout::from(result))
}
pub fn deserialize_result(result_str: &str) -> MistResult {
serde_yaml::from_str::<MistResultLayout>(result_str)?.into()
}
#[derive(Clone, PartialEq, Debug)]
pub struct MistOutput {
message: Option<String>,
files: IndexMap<String, String>,
}
impl MistOutput {
pub fn new() -> Self {
Self {
message: None,
files: IndexMap::new(),
}
}
pub fn set_message(&mut self, message: String) {
self.message = Some(message);
}
pub fn with_message(mut self, message: String) -> Self {
self.set_message(message);
self
}
pub fn set_file(&mut self, filename: String, content: String) {
self.files.insert(filename, content);
}
pub fn with_file(mut self, filename: String, content: String) -> Self {
self.set_file(filename, content);
self
}
pub fn get_message(&self) -> &Option<String> {
&self.message
}
pub fn get_files(&self) -> &IndexMap<String, String> {
&self.files
}
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct MistResultLayout {
api_version: String,
kind: String,
data: MistResultLayoutData,
}
#[derive(Serialize, Deserialize)]
struct MistResultLayoutData {
result: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
message: Option<String>,
#[serde(default, skip_serializing_if = "IndexMap::is_empty")]
files: IndexMap<String, String>,
}
impl From<MistResult> for MistResultLayout {
fn from(result: MistResult) -> Self {
Self {
api_version: "mistletoe.dev/v1alpha1".to_string(),
kind: "MistResult".to_string(),
data: match result {
Ok(output) => MistResultLayoutData {
result: "Ok".to_string(),
message: output.message,
files: output.files,
},
Err(e) => MistResultLayoutData {
result: "Err".to_string(),
message: Some(e.to_string()),
files: IndexMap::new(),
},
}
}
}
}
impl Into<MistResult> for MistResultLayout {
fn into(self) -> MistResult {
match self.data.result.as_str() {
"Ok" => MistResult::Ok(MistOutput {
message: self.data.message,
files: self.data.files,
}),
"Err" => MistResult::Err(match self.data.message {
Some(message) => anyhow!(message),
None => anyhow!("package failed without a message"),
}),
s => MistResult::Err(anyhow!("package result format error: `data.result` must either be \"Ok\" or \"Err\", found {}", s)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use indoc::indoc;
#[test]
fn test_mistresult_ok() {
let expected_yaml = indoc!{"
apiVersion: mistletoe.dev/v1alpha1
kind: MistResult
data:
result: Ok
message: 'warning: nothing went wrong'
files:
namespace.yaml: |
apiVersion: v1
kind: Namespace
metadata:
name: my-namespace"};
let mistoutput = MistOutput::new()
.with_message("warning: nothing went wrong".to_string())
.with_file("namespace.yaml".to_string(), indoc!("
apiVersion: v1
kind: Namespace
metadata:
name: my-namespace")
.to_string());
let yaml = serialize_result(Ok(mistoutput.clone())).unwrap();
assert_eq!(expected_yaml, yaml);
let mistresult_parsed = deserialize_result(&yaml).unwrap();
assert_eq!(mistoutput, mistresult_parsed);
}
#[test]
fn test_mistresult_err() {
let expected_yaml: &str = indoc!{"
apiVersion: mistletoe.dev/v1alpha1
kind: MistResult
data:
result: Err
message: 'error: something went wrong'"};
let err_string = "error: something went wrong";
let yaml = serialize_result(Err(anyhow!(err_string.to_string()))).unwrap();
assert_eq!(expected_yaml, yaml);
let mistresult_parsed = deserialize_result(&yaml);
assert_eq!(err_string, mistresult_parsed.err().unwrap().to_string());
}
}