use std::path::PathBuf;
use peace_cmd_model::CmdExecutionError;
use peace_core::{FlowId, ItemId, Profile};
use peace_params::{ParamsResolveError, ParamsSpecs};
use peace_resource_rt::paths::ParamsSpecsFile;
pub use self::{apply_cmd_error::ApplyCmdError, state_downcast_error::StateDowncastError};
mod apply_cmd_error;
mod state_downcast_error;
cfg_if::cfg_if! {
if #[cfg(not(target_arch = "wasm32"))] {
pub use self::native_error::NativeError;
mod native_error;
} else {
pub use self::web_error::WebError;
mod web_error;
}
}
#[cfg_attr(feature = "error_reporting", derive(miette::Diagnostic))]
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Failed to apply changes.")]
#[cfg_attr(
feature = "error_reporting",
diagnostic(code(peace_rt_model::apply_error))
)]
ApplyCmdError(
#[cfg_attr(feature = "error_reporting", diagnostic_source)]
#[source]
#[from]
ApplyCmdError,
),
#[error(
"Error in `CmdExecution` or `CmdBlock` logic, usually due to incorrect `Resource` insertion or removal."
)]
#[cfg_attr(
feature = "error_reporting",
diagnostic(help("Make sure that the value is populated by a predecessor."))
)]
CmdExecution(
#[cfg_attr(feature = "error_reporting", diagnostic_source)]
#[source]
#[from]
CmdExecutionError,
),
#[error("Failed to serialize error.")]
#[cfg_attr(
feature = "error_reporting",
diagnostic(code(peace_rt_model::error_serialize))
)]
ErrorSerialize(#[source] serde_yaml::Error),
#[error("Failed to resolve values for a `Params` object from `resources`.")]
#[cfg_attr(
feature = "error_reporting",
diagnostic(
code(peace_rt_model::params_resolve_error),
help("Make sure that the value is populated by a predecessor.")
)
)]
ParamsResolveError(
#[cfg_attr(feature = "error_reporting", diagnostic_source)]
#[source]
#[from]
ParamsResolveError,
),
#[error("A `Params::Spec` was not present for item: {item_id}")]
#[cfg_attr(
feature = "error_reporting",
diagnostic(
code(peace_rt_model::params_spec_not_found),
help(
"If you are an end user, please ask for help from the providers of your automation tool.\n\
\n\
If you are developing a tool with the Peace framework,\n\
please open an issue in the Peace repository:\n\
\n\
https://github.com/azriel91/peace/"
)
)
)]
ParamsSpecNotFound {
item_id: ItemId,
},
#[error("Item params specs do not match with the items in the flow.")]
#[cfg_attr(
feature = "error_reporting",
diagnostic(
code(peace_rt_model::params_specs_mismatch),
help("{}", params_specs_mismatch_display(
item_ids_with_no_params_specs,
params_specs_provided_mismatches,
params_specs_stored_mismatches.as_ref().as_ref(),
params_specs_not_usable,
))
)
)]
ParamsSpecsMismatch {
item_ids_with_no_params_specs: Vec<ItemId>,
params_specs_provided_mismatches: ParamsSpecs,
params_specs_stored_mismatches: Box<Option<ParamsSpecs>>,
params_specs_not_usable: Vec<ItemId>,
},
#[error("Params specifications not defined for `{profile_a}` or `{profile_b}`.")]
#[cfg_attr(
feature = "error_reporting",
diagnostic(
code(peace_rt_model::params_specs_not_defined_for_diff),
help(
"Make sure at least one of the flows has `.with_items_params(..)`\n\
defined for every item in the flow."
)
)
)]
ParamsSpecsNotDefinedForDiff {
profile_a: Profile,
profile_b: Profile,
},
#[error("Failed to serialize a presentable type.")]
#[cfg_attr(
feature = "error_reporting",
diagnostic(code(peace_rt_model::presentable_serialize))
)]
PresentableSerialize(#[source] serde_yaml::Error),
#[error("Failed to serialize progress update.")]
#[cfg_attr(
feature = "error_reporting",
diagnostic(code(peace_rt_model::progress_update_serialize))
)]
ProgressUpdateSerialize(#[source] serde_yaml::Error),
#[error("Failed to serialize progress update.")]
#[cfg_attr(
feature = "error_reporting",
diagnostic(code(peace_rt_model::progress_update_serialize_json))
)]
ProgressUpdateSerializeJson(#[source] serde_json::Error),
#[error("Failed to deserialize states for flow: `{flow_id}`.")]
#[cfg_attr(
feature = "error_reporting",
diagnostic(
code(peace_rt_model::states_deserialize),
help(
"Make sure that all commands using the `{flow_id}` flow, also use the same item graph.\n\
This is because all Items are used to deserialize state.\n\
\n\
If the item graph is different, it may make sense to use a different flow ID."
)
)
)]
StatesDeserialize {
flow_id: FlowId,
#[cfg(feature = "error_reporting")]
#[source_code]
states_file_source: miette::NamedSource<String>,
#[cfg(feature = "error_reporting")]
#[label("{}", error_message)]
error_span: Option<miette::SourceOffset>,
#[cfg(feature = "error_reporting")]
error_message: String,
#[cfg(feature = "error_reporting")]
#[label]
context_span: Option<miette::SourceOffset>,
#[source]
error: serde_yaml::Error,
},
#[error("Failed to serialize states.")]
#[cfg_attr(
feature = "error_reporting",
diagnostic(code(peace_rt_model::states_serialize))
)]
StatesSerialize(#[source] serde_yaml::Error),
#[error("Failed to deserialize params specs for `{profile}/{flow_id}`.")]
#[cfg_attr(
feature = "error_reporting",
diagnostic(
code(peace_rt_model::params_specs_deserialize),
help(
"Make sure that all commands using the `{flow_id}` flow, also use the same item graph.\n\
This is because all Items are used to deserialize state.\n\
\n\
If the item graph is different, it may make sense to use a different flow ID."
)
)
)]
ParamsSpecsDeserialize {
profile: Profile,
flow_id: FlowId,
#[cfg(feature = "error_reporting")]
#[source_code]
params_specs_file_source: miette::NamedSource<String>,
#[cfg(feature = "error_reporting")]
#[label("{}", error_message)]
error_span: Option<miette::SourceOffset>,
#[cfg(feature = "error_reporting")]
error_message: String,
#[cfg(feature = "error_reporting")]
#[label]
context_span: Option<miette::SourceOffset>,
#[source]
error: serde_yaml::Error,
},
#[error("Failed to serialize params specs.")]
#[cfg_attr(
feature = "error_reporting",
diagnostic(code(peace_rt_model::params_specs_serialize))
)]
ParamsSpecsSerialize(#[source] serde_yaml::Error),
#[error("Params specs file does not exist for `{profile}/{flow_id}`.")]
#[cfg_attr(
feature = "error_reporting",
diagnostic(
code(peace_rt_model::params_specs_file_not_exists),
help(
"Ensure that a `SingleProfileSingleFlow` command context has previously been built."
)
)
)]
ParamsSpecsFileNotExists {
profile: Profile,
flow_id: FlowId,
params_specs_file: ParamsSpecsFile,
},
#[error("Current states have not been discovered.")]
#[cfg_attr(
feature = "error_reporting",
diagnostic(
code(peace_rt_model::states_current_discover_required),
help("Ensure that `StatesDiscoverCmd::current` has been called.")
)
)]
StatesCurrentDiscoverRequired,
#[error("Goal states have not been written to disk.")]
#[cfg_attr(
feature = "error_reporting",
diagnostic(
code(peace_rt_model::states_goal_discover_required),
help("Ensure that `StatesDiscoverCmd::goal` has been called.")
)
)]
StatesGoalDiscoverRequired,
#[error("Failed to serialize state diffs.")]
#[cfg_attr(
feature = "error_reporting",
diagnostic(code(peace_rt_model::state_diffs_serialize))
)]
StateDiffsSerialize(#[source] serde_yaml::Error),
#[error("Failed to serialize error as JSON.")]
#[cfg_attr(
feature = "error_reporting",
diagnostic(code(peace_rt_model::error_serialize_json))
)]
ErrorSerializeJson(#[source] serde_json::Error),
#[error("Failed to serialize states as JSON.")]
#[cfg_attr(
feature = "error_reporting",
diagnostic(code(peace_rt_model::states_current_serialize_json))
)]
StatesSerializeJson(#[source] serde_json::Error),
#[error("Failed to serialize state diffs as JSON.")]
#[cfg_attr(
feature = "error_reporting",
diagnostic(code(peace_rt_model::state_diffs_serialize_json))
)]
StateDiffsSerializeJson(#[source] serde_json::Error),
#[error("Failed to serialize workspace init params.")]
#[cfg_attr(
feature = "error_reporting",
diagnostic(code(peace_rt_model::workspace_init_params_serialize))
)]
WorkspaceParamsSerialize(#[source] serde_yaml::Error),
#[error("Failed to deserialize workspace init params.")]
#[cfg_attr(
feature = "error_reporting",
diagnostic(code(peace_rt_model::workspace_init_params_deserialize))
)]
WorkspaceParamsDeserialize(#[source] serde_yaml::Error),
#[error("Workspace params does not exist, so cannot look up `Profile`.")]
#[cfg_attr(
feature = "error_reporting",
diagnostic(code(peace_rt_model::workspace_params_none_for_profile))
)]
WorkspaceParamsNoneForProfile,
#[error("Workspace param for `Profile` does not exist.")]
#[cfg_attr(
feature = "error_reporting",
diagnostic(code(peace_rt_model::workspace_params_profile_none))
)]
WorkspaceParamsProfileNone,
#[error("Profile `{profile}` not in scope, make sure it exists in `.peace/*/{profile}`.")]
#[cfg_attr(
feature = "error_reporting",
diagnostic(
code(peace_rt_model::profile_not_in_scope),
help(
"Make sure the profile is spelt correctly.\n\
Available profiles are: [{profiles_in_scope}]",
profiles_in_scope = profiles_in_scope
.iter()
.map(|profile| format!("{profile}"))
.collect::<Vec<_>>()
.join(",")
)
)
)]
ProfileNotInScope {
profile: Profile,
profiles_in_scope: Vec<Profile>,
},
#[error("Profile `{profile}`'s states have not been discovered.")]
#[cfg_attr(
feature = "error_reporting",
diagnostic(
code(peace_rt_model::profile_states_current_not_discovered),
help("Switch to the profile and run the states discover command.")
)
)]
ProfileStatesCurrentNotDiscovered {
profile: Profile,
},
#[error("Failed to serialize profile init params.")]
#[cfg_attr(
feature = "error_reporting",
diagnostic(code(peace_rt_model::profile_init_params_serialize))
)]
ProfileParamsSerialize(#[source] serde_yaml::Error),
#[error("Failed to deserialize profile init params.")]
#[cfg_attr(
feature = "error_reporting",
diagnostic(code(peace_rt_model::profile_init_params_deserialize))
)]
ProfileParamsDeserialize(#[source] serde_yaml::Error),
#[error("Failed to serialize flow init params.")]
#[cfg_attr(
feature = "error_reporting",
diagnostic(code(peace_rt_model::flow_init_params_serialize))
)]
FlowParamsSerialize(#[source] serde_yaml::Error),
#[error("Failed to deserialize flow init params.")]
#[cfg_attr(
feature = "error_reporting",
diagnostic(code(peace_rt_model::flow_init_params_deserialize))
)]
FlowParamsDeserialize(#[source] serde_yaml::Error),
#[error("Item does not exist in storage: `{}`.", path.display())]
#[cfg_attr(
feature = "error_reporting",
diagnostic(code(peace_rt_model::item_not_exists))
)]
ItemNotExists {
path: PathBuf,
},
#[error("Error downcasting a `BoxDtDisplay` into an item's concrete state type.")]
StateDowncastError(
#[cfg_attr(feature = "error_reporting", diagnostic_source)]
#[source]
#[from]
StateDowncastError,
),
#[error("Native application error occurred.")]
#[cfg(not(target_arch = "wasm32"))]
Native(
#[cfg_attr(feature = "error_reporting", diagnostic_source)]
#[source]
#[from]
NativeError,
),
#[error("Web application error occurred.")]
#[cfg(target_arch = "wasm32")]
Web(
#[cfg_attr(feature = "error_reporting", diagnostic_source)]
#[source]
#[from]
WebError,
),
}
#[cfg(feature = "error_reporting")]
fn params_specs_mismatch_display(
item_ids_with_no_params: &[ItemId],
params_specs_provided_mismatches: &ParamsSpecs,
params_specs_stored_mismatches: Option<&ParamsSpecs>,
params_specs_not_usable: &[ItemId],
) -> String {
let mut items = Vec::<String>::new();
if !item_ids_with_no_params.is_empty() {
items.push(format!(
"The following items do not have parameters provided:\n\
\n\
{}\n",
item_ids_with_no_params
.iter()
.map(|item_id| format!("* {item_id}"))
.collect::<Vec<String>>()
.join("\n")
));
}
if !params_specs_provided_mismatches.is_empty() {
let params_specs_provided_mismatches_list = params_specs_provided_mismatches
.keys()
.map(|item_id| format!("* {item_id}"))
.collect::<Vec<String>>()
.join("\n");
items.push(format!(
"The following provided params specs do not correspond to any items in the flow:\n\
\n\
{params_specs_provided_mismatches_list}\n",
))
}
if let Some(params_specs_stored_mismatches) = params_specs_stored_mismatches {
if !params_specs_stored_mismatches.is_empty() {
let params_specs_stored_mismatches_list = params_specs_stored_mismatches
.keys()
.map(|item_id| format!("* {item_id}"))
.collect::<Vec<String>>()
.join("\n");
items.push(format!(
"The following stored params specs do not correspond to any items in the flow:\n\
\n\
{params_specs_stored_mismatches_list}\n",
));
}
}
if !params_specs_not_usable.is_empty() {
items.push(format!(
"The following items either have not had a params spec provided previously,\n\
or had contained a mapping function, which cannot be loaded from disk.\n\
\n\
So the params spec needs to be provided to the command context for:\n\
\n\
{}\n",
params_specs_not_usable
.iter()
.map(|item_id| format!("* {item_id}"))
.collect::<Vec<String>>()
.join("\n")
));
}
items.join("\n")
}