use std::borrow::Cow; use color_eyre::eyre::{self, Context}; use serde::Deserialize; use serde_json::value::RawValue; use serde_repr::Deserialize_repr; use crate::action::{Action, ActionType, BuildStepId, ResultFields, StartFields}; #[derive(Debug, Deserialize)] #[serde(rename_all = "lowercase")] pub enum RawActionType { Msg, Start, Result, Stop, } #[derive(Debug, Deserialize_repr)] #[repr(u8)] pub enum RawEnumType { FileLinked = 100, BuildLogLine = 101, UntrustedPath = 102, CorruptedPath = 103, SetPhase = 104, Progress = 105, SetExpected = 106, PostBuildLogLine = 107, FetchStatus = 108, } #[derive(Debug, Deserialize)] #[serde(tag = "action", rename_all = "lowercase")] pub struct RawAction<'a> { #[serde(rename = "action")] pub action_type: RawActionType, pub level: Option, pub msg: Option>, pub id: Option, pub parent: Option, pub text: Option>, #[serde(borrow)] pub fields: Option<&'a RawValue>, #[serde(rename = "type")] pub any_type: Option, } impl<'a> TryFrom> for Action<'a> { type Error = color_eyre::Report; fn try_from(val: RawAction<'a>) -> Result { let missing = |field: &'static str| eyre::eyre!("missing field `{field}`"); let action = match val.action_type { RawActionType::Msg => Action::Msg { level: val.level.ok_or_else(|| missing("level"))?, msg: val.msg.ok_or_else(|| missing("msg"))?, }, RawActionType::Start => { let start_type: ActionType = serde_json::from_value(val.any_type.ok_or_else(|| missing("type"))?.into()) .context("invalid type")?; let start_type = match start_type { ActionType::Unknown => StartFields::Unknown, ActionType::CopyPath => { let raw_fields = val.fields.ok_or_else(|| missing("fields"))?.get(); let (path, origin, destination) = serde_json::from_str(raw_fields).context("invalid fields")?; StartFields::CopyPath { path, origin, destination, } } ActionType::FileTransfer => { let raw_fields = val.fields.ok_or_else(|| missing("fields"))?.get(); let [target] = serde_json::from_str(raw_fields).context("invalid fields")?; StartFields::FileTransfer { target } } ActionType::Realise => StartFields::Realise, ActionType::CopyPaths => StartFields::CopyPaths, ActionType::Builds => StartFields::Builds, ActionType::Build => { let raw_fields = val.fields.ok_or_else(|| missing("fields"))?.get(); let (target, source, val1, val2) = serde_json::from_str(raw_fields).context("invalid fields")?; StartFields::Build { target, source, val1, val2, } } ActionType::OptimiseStore => StartFields::OptimiseStore, ActionType::VerifyPaths => StartFields::VerifyPaths, ActionType::Substitute => { let raw_fields = val.fields.ok_or_else(|| missing("fields"))?.get(); let (source, target) = serde_json::from_str(raw_fields).context("invalid fields")?; StartFields::Substitute { source, target } } ActionType::QueryPathInfo => { let raw_fields = val.fields.ok_or_else(|| missing("fields"))?.get(); let (path, source) = serde_json::from_str(raw_fields).context("invalid fields")?; StartFields::QueryPathInfo { path, source } } ActionType::PostBuildHook => StartFields::PostBuildHook, ActionType::BuildWaiting => StartFields::BuildWaiting, ActionType::FetchTree => StartFields::FetchTree, }; Action::Start { start_type, id: val.id.ok_or_else(|| missing("id"))?, level: val.level.ok_or_else(|| missing("level"))?, parent: val.parent.ok_or_else(|| missing("parent"))?, text: val.text.ok_or_else(|| missing("text"))?, } } RawActionType::Result => { let raw_fields = val.fields.ok_or_else(|| missing("fields"))?.get(); let fields = match val.any_type.ok_or_else(|| missing("type"))? { 100 => { let (val1, val2) = serde_json::from_str(raw_fields).context("invalid fields")?; ResultFields::FileLinked { val1, val2 } } 101 => { let [line] = serde_json::from_str(raw_fields).context("invalid fields")?; ResultFields::BuildLogLine(line) } 102 => { let [path] = serde_json::from_str(raw_fields).context("invalid fields")?; ResultFields::UntrustedPath(path) } 103 => { let [path] = serde_json::from_str(raw_fields).context("invalid fields")?; ResultFields::CorruptedPath(path) } 104 => { let [phase] = serde_json::from_str(raw_fields).context("invalid fields")?; ResultFields::SetPhase(phase) } 105 => { let (done, expected, running, failed) = serde_json::from_str(raw_fields).context("invalid fields")?; ResultFields::Progress { done, expected, running, failed, } } 106 => { let (action, expected) = serde_json::from_str(raw_fields).unwrap(); ResultFields::SetExpected { action, expected } } 107 => { let [line] = serde_json::from_str(raw_fields).context("invalid fields")?; ResultFields::PostBuildLogLine(line) } 108 => { let [status] = serde_json::from_str(raw_fields).context("invalid fields")?; ResultFields::FetchStatus(status) } v => eyre::bail!("Unknown result type `{v}`"), }; Action::Result { id: val.id.ok_or_else(|| missing("id"))?, fields, } } RawActionType::Stop => Action::Stop { id: val.id.ok_or_else(|| missing("id"))?, }, }; Ok(action) } }