pub(crate) trait WorkflowCauseCountResult {
fn into_workflow_result(self) -> Result<usize, String>;
}
impl WorkflowCauseCountResult for usize {
fn into_workflow_result(self) -> Result<usize, String> {
Ok(self)
}
}
impl<E: ToString> WorkflowCauseCountResult for Result<usize, E> {
fn into_workflow_result(self) -> Result<usize, String> {
self.map_err(|err| err.to_string())
}
}
#[derive(Debug, Clone)]
pub enum WorkflowError {
InvalidConfig { reason: String },
SchemaMismatch { reason: String },
MissingDependency { reason: String },
IntegrationFailed { reason: String },
FormulaDsl {
context: &'static str,
source: crate::inference::formula_dsl::FormulaDslError,
},
ColumnNotFound {
name: String,
role: Option<String>,
available: Vec<String>,
similar: Vec<String>,
tsv_hint: bool,
},
}
impl std::fmt::Display for WorkflowError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WorkflowError::InvalidConfig { reason }
| WorkflowError::SchemaMismatch { reason }
| WorkflowError::MissingDependency { reason }
| WorkflowError::IntegrationFailed { reason } => f.write_str(reason),
WorkflowError::FormulaDsl { context, source } => write!(f, "{context}: {source}"),
WorkflowError::ColumnNotFound {
name,
role,
available,
similar,
tsv_hint,
} => {
let label = match role {
Some(r) => format!("{r} column '{name}'"),
None => format!("column '{name}'"),
};
let tsv_suffix = if *tsv_hint {
" — your file appears to be tab-separated; gam expects comma-separated CSV. \
Replace tabs with commas, or pre-convert with `tr '\\t' ',' < file.tsv > file.csv`."
} else {
""
};
if similar.is_empty() {
write!(
f,
"{label} not found in data. Available columns: [{}]{tsv_suffix}",
available.join(", ")
)
} else {
write!(
f,
"{label} not found in data. Did you mean one of [{}]? Full list: [{}]{tsv_suffix}",
similar.join(", "),
available.join(", ")
)
}
}
}
}
}
impl std::error::Error for WorkflowError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
WorkflowError::FormulaDsl { source, .. } => Some(source),
WorkflowError::InvalidConfig { .. }
| WorkflowError::SchemaMismatch { .. }
| WorkflowError::MissingDependency { .. }
| WorkflowError::IntegrationFailed { .. }
| WorkflowError::ColumnNotFound { .. } => None,
}
}
}
impl From<WorkflowError> for String {
fn from(err: WorkflowError) -> String {
err.to_string()
}
}
impl From<String> for WorkflowError {
fn from(reason: String) -> Self {
Self::InvalidConfig { reason }
}
}
impl From<&str> for WorkflowError {
fn from(reason: &str) -> Self {
Self::InvalidConfig {
reason: reason.to_string(),
}
}
}
impl From<crate::inference::formula_dsl::FormulaDslError> for WorkflowError {
fn from(err: crate::inference::formula_dsl::FormulaDslError) -> Self {
Self::FormulaDsl {
context: "workflow formula materialization",
source: err,
}
}
}
impl From<crate::terms::term_builder::TermBuilderError> for WorkflowError {
fn from(err: crate::terms::term_builder::TermBuilderError) -> Self {
use crate::terms::term_builder::TermBuilderError;
match err {
TermBuilderError::ColumnNotFound {
name,
role,
available,
similar,
tsv_hint,
} => Self::ColumnNotFound {
name,
role,
available,
similar,
tsv_hint,
},
TermBuilderError::MissingColumn { reason }
| TermBuilderError::MalformedFormula { reason } => Self::SchemaMismatch { reason },
TermBuilderError::IncompatibleConfig { reason }
| TermBuilderError::InvalidOption { reason }
| TermBuilderError::UnsupportedFeature { reason }
| TermBuilderError::DegenerateData { reason } => Self::InvalidConfig { reason },
}
}
}
impl From<crate::inference::data::DataError> for WorkflowError {
fn from(err: crate::inference::data::DataError) -> Self {
use crate::inference::data::DataError;
match err {
DataError::ColumnNotFound {
name,
role,
available,
similar,
tsv_hint,
} => Self::ColumnNotFound {
name,
role,
available,
similar,
tsv_hint,
},
DataError::SchemaMismatch { reason } => Self::SchemaMismatch { reason },
DataError::ParseError { reason }
| DataError::EncodingFailure { reason }
| DataError::EmptyInput { reason }
| DataError::InvalidValue { reason } => Self::InvalidConfig { reason },
}
}
}