use aion_core::Payload;
use aion_package::ContentHash;
use super::WorkflowCatalog;
use crate::error::EngineError;
#[derive(Clone, Debug, thiserror::Error)]
pub enum SignalRefusalReason {
#[error("the package declares no signal `{signal_name}`; it declares {declared}")]
Undeclared {
signal_name: String,
declared: String,
},
#[error("the payload is not decodable JSON: {reason}")]
UndecodablePayload {
reason: String,
},
#[error("{violations}")]
PayloadMismatch {
violations: String,
},
}
impl WorkflowCatalog {
pub fn declared_signal_refusal(
&self,
workflow_type: &str,
version: &ContentHash,
signal_name: &str,
payload: &Payload,
) -> Result<Option<SignalRefusalReason>, EngineError> {
let snapshot = self.current()?;
let Some(entry) = snapshot
.by_version
.get(&(workflow_type.to_owned(), version.clone()))
else {
tracing::debug!(
workflow_type,
%version,
signal_name,
"signal target's exact package identity is not retained; no declaration to admit against"
);
return Ok(None);
};
let Ok(contract) = entry.workflow.contract() else {
tracing::debug!(
workflow_type,
%version,
signal_name,
"signal target's package identity predates the .v4 contract commitment; no declaration to admit against"
);
return Ok(None);
};
if contract.signals.is_empty() {
return Ok(None);
}
let Some(declared) = contract.declared_signal(signal_name) else {
return Ok(Some(SignalRefusalReason::Undeclared {
signal_name: signal_name.to_owned(),
declared: contract.declared_signal_names().join(", "),
}));
};
if aion_package::declares_nothing(&declared.input_schema) {
return Ok(None);
}
let value = match payload.to_json() {
Ok(value) => value,
Err(error) => {
return Ok(Some(SignalRefusalReason::UndecodablePayload {
reason: error.to_string(),
}));
}
};
match aion_package::admit_value(&declared.input_schema, &value) {
Ok(()) => Ok(None),
Err(aion_package::AdmissionError::UnusableSchema { reason }) => {
tracing::warn!(
workflow_type,
%version,
signal_name,
%reason,
"package declares a signal schema that is not valid JSON Schema; the payload could not be admitted against it and was allowed through"
);
Ok(None)
}
Err(aion_package::AdmissionError::Mismatch { violations }) => {
Ok(Some(SignalRefusalReason::PayloadMismatch { violations }))
}
}
}
}