use crate::errors::OrionError;
pub(crate) async fn ensure_workflow_is_active(
workflows: &dyn crate::storage::repositories::workflows::WorkflowRepository,
draft: &crate::storage::models::Channel,
) -> Result<(), OrionError> {
let Some(workflow_id) = draft
.workflow_id
.as_deref()
.filter(|w| !w.trim().is_empty())
else {
return Err(OrionError::validation(format!(
"Cannot activate channel '{}': it names no workflow_id, so it would be \
quarantined at load and never serve. Set workflow_id first.",
draft.name
)));
};
let has_active = workflows
.list_active()
.await?
.iter()
.any(|w| w.workflow_id == workflow_id);
if !has_active {
let detail = match workflows.get_by_id(workflow_id).await {
Ok(_) => "has no active version",
Err(OrionError::NotFound(_)) => "does not exist",
Err(e) => return Err(e),
};
return Err(OrionError::validation(format!(
"Cannot activate channel '{}': workflow '{workflow_id}' {detail} — \
activate the workflow first",
draft.name
)));
}
Ok(())
}
pub(crate) fn ensure_cron_is_enabled(
config: &crate::config::AppConfig,
draft: &crate::storage::models::Channel,
) -> Result<(), OrionError> {
let is_cron = draft.protocol == crate::storage::models::ChannelProtocol::Cron.as_str();
if !is_cron || config.cron.enabled {
return Ok(());
}
Err(OrionError::validation(format!(
"Cannot activate channel '{}': it is a cron channel and this instance has cron.enabled = false, so its schedule would never fire. Set cron.enabled (or ORION_CRON__ENABLED=true) and restart, or leave the channel as a draft.",
draft.name
)))
}
pub(crate) async fn ensure_name_is_unclaimed(
channels: &dyn crate::storage::repositories::channels::ChannelRepository,
draft: &crate::storage::models::Channel,
) -> Result<(), OrionError> {
if let Some(holder) = channels
.active_name_holder(&draft.name, &draft.channel_id)
.await?
{
return Err(OrionError::Conflict(format!(
"Cannot activate channel '{}': active channel id '{holder}' already uses \
that name, and the data plane addresses channels by name. Rename one \
of the two first.",
draft.name
)));
}
Ok(())
}
pub(crate) async fn ensure_route_is_unclaimed(
channels: &dyn crate::storage::repositories::channels::ChannelRepository,
data_mounts: &[String],
draft: &crate::storage::models::Channel,
) -> Result<(), OrionError> {
use crate::channel::routing::declared_route;
let declared = declared_route(draft);
if declared.is_empty() {
return Ok(());
}
let active = channels.list_active().await?;
for (route, methods) in &declared {
for mount in data_mounts {
let served = if mount == "/" {
route.clone()
} else {
format!("{mount}{route}")
};
if let Some(platform) = crate::server::routes::shadowed_platform_route(&served) {
return Err(OrionError::validation(format!(
"Cannot activate channel '{}': with server.data_mounts entry '{mount}' \
its route would be served at {served}, which collides with the \
platform route '{platform}'. The platform route wins, so the channel \
would never run. Change the route_pattern or the mount.",
draft.name,
)));
}
}
for other in &active {
if other.channel_id == draft.channel_id {
continue; }
for (other_route, other_methods) in declared_route(other) {
if &other_route == route
&& other.priority == draft.priority
&& crate::channel::routing::methods_overlap(methods, &other_methods)
{
return Err(OrionError::validation(format!(
"Cannot activate channel '{}': active channel '{}' (id {}) already \
claims {} {route} at priority {}. Requests to that path would run \
one of the two arbitrarily. Change the route_pattern, narrow the \
methods, or give one a higher priority.",
draft.name,
other.name,
other.channel_id,
if methods.is_empty() {
"every method on".to_string()
} else {
methods.join("/")
},
draft.priority,
)));
}
}
}
}
Ok(())
}