v0.10.0
This commit is contained in:
+75
-1
@@ -51,6 +51,62 @@ fn flow_condition_kind(kind: &str) -> bool {
|
||||
fn flow_logic_kind(kind: &str) -> bool { matches!(kind, "logic_and" | "logic_or" | "logic_not") }
|
||||
fn flow_action_kind(kind: &str) -> bool { matches!(kind, "zone_thermostat" | "device_action" | "group_action") }
|
||||
|
||||
fn shared_input_comparison_kind(kind: &str) -> bool {
|
||||
matches!(kind,
|
||||
"outdoor_temperature" | "device_temperature" | "zone_temperature" |
|
||||
"ha_state" | "ha_numeric" | "ha_attribute" | "house_mode" |
|
||||
"device_state" | "zone_state" | "group_state"
|
||||
)
|
||||
}
|
||||
|
||||
fn validate_shared_input_source(kind: &str, config: &Value, state: &AppState) -> Result<(), AppError> {
|
||||
match kind {
|
||||
"outdoor_temperature" | "house_mode" | "night_mode" => {}
|
||||
"device_temperature" => {
|
||||
let id = flow_string(config, "device_id").ok_or_else(|| AppError::BadRequest("shared device temperature input needs device".into()))?;
|
||||
if state.db.get_device(&id)?.is_none() { return Err(AppError::BadRequest("shared Flow input references a missing device".into())); }
|
||||
}
|
||||
"zone_temperature" => {
|
||||
let id = flow_string(config, "zone_id").ok_or_else(|| AppError::BadRequest("shared zone temperature input needs zone".into()))?;
|
||||
if state.db.get_zone(&id)?.is_none() { return Err(AppError::BadRequest("shared Flow input references a missing zone".into())); }
|
||||
}
|
||||
"ha_state" | "ha_numeric" | "ha_available" => {
|
||||
if flow_string(config, "entity_id").is_none() { return Err(AppError::BadRequest("shared Home Assistant input needs entity_id".into())); }
|
||||
}
|
||||
"ha_attribute" => {
|
||||
if flow_string(config, "entity_id").is_none() || flow_string(config, "attribute").is_none() {
|
||||
return Err(AppError::BadRequest("shared Home Assistant attribute input needs entity_id and attribute".into()));
|
||||
}
|
||||
}
|
||||
"device_state" => {
|
||||
let id = flow_string(config, "device_id").ok_or_else(|| AppError::BadRequest("shared device state input needs device".into()))?;
|
||||
if state.db.get_device(&id)?.is_none() { return Err(AppError::BadRequest("shared Flow input references a missing device".into())); }
|
||||
let field = flow_string(config, "field").ok_or_else(|| AppError::BadRequest("shared device state input needs a field".into()))?;
|
||||
if !matches!(field.as_str(), "enabled" | "online" | "power" | "mode" | "fan_speed" | "swing_vertical" | "swing_horizontal" | "quiet" | "turbo" | "light" | "air" | "xfan" | "health" | "sleep") {
|
||||
return Err(AppError::BadRequest("unsupported shared device state field".into()));
|
||||
}
|
||||
}
|
||||
"zone_state" => {
|
||||
let id = flow_string(config, "zone_id").ok_or_else(|| AppError::BadRequest("shared zone state input needs zone".into()))?;
|
||||
if state.db.get_zone(&id)?.is_none() { return Err(AppError::BadRequest("shared Flow input references a missing zone".into())); }
|
||||
let field = flow_string(config, "field").ok_or_else(|| AppError::BadRequest("shared zone state input needs a field".into()))?;
|
||||
if !matches!(field.as_str(), "enabled" | "mode" | "active_preset" | "demand" | "control_owner" | "device_manual_override" | "local_thermostat_power") {
|
||||
return Err(AppError::BadRequest("unsupported shared zone state field".into()));
|
||||
}
|
||||
}
|
||||
"group_state" => {
|
||||
let id = flow_string(config, "group_id").ok_or_else(|| AppError::BadRequest("shared group state input needs group".into()))?;
|
||||
if state.db.get_group(&id)?.is_none() { return Err(AppError::BadRequest("shared Flow input references a missing group".into())); }
|
||||
if flow_string(config, "field").as_deref() != Some("power_enabled") { return Err(AppError::BadRequest("unsupported shared group state field".into())); }
|
||||
}
|
||||
"constant" => {
|
||||
if config.get("value").and_then(Value::as_bool).is_none() { return Err(AppError::BadRequest("shared constant input needs a boolean value".into())); }
|
||||
}
|
||||
_ => return Err(AppError::BadRequest(format!("unsupported shared Flow input kind: {kind}"))),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_flow_graph(input: &FlowInput) -> Result<(), AppError> {
|
||||
if input.name.trim().is_empty() { return Err(AppError::BadRequest("flow name is required".into())); }
|
||||
if input.nodes.is_empty() { return Err(AppError::BadRequest("flow needs at least one block".into())); }
|
||||
@@ -221,7 +277,25 @@ fn validate_condition(condition: &crate::models::FlowCondition, state: &AppState
|
||||
if condition.config.get("value").and_then(Value::as_bool).is_none() { return Err(AppError::BadRequest("constant block needs a boolean value".into())); }
|
||||
}
|
||||
"shared_input" => {
|
||||
if flow_string(&condition.config, "input_id").is_none() { return Err(AppError::BadRequest("shared Flow input block needs input_id".into())); }
|
||||
let input_id = flow_string(&condition.config, "input_id").ok_or_else(|| AppError::BadRequest("shared Flow input block needs input_id".into()))?;
|
||||
let settings = state.db.load_runtime_settings()?.ok_or_else(|| AppError::BadRequest("runtime settings are unavailable".into()))?;
|
||||
let shared = settings.home_assistant.flow_inputs.iter().find(|item| item.id == input_id)
|
||||
.ok_or_else(|| AppError::BadRequest("shared Flow input block references a missing input".into()))?;
|
||||
validate_shared_input_source(&shared.kind, &shared.config, state)?;
|
||||
if shared_input_comparison_kind(&shared.kind) {
|
||||
let operator = flow_string(&condition.config, "operator")
|
||||
.ok_or_else(|| AppError::BadRequest("shared Flow value needs an operator in the Flow block".into()))?;
|
||||
let mut config = shared.config.clone();
|
||||
let map = config.as_object_mut().ok_or_else(|| AppError::BadRequest("shared Flow input config must be an object".into()))?;
|
||||
map.insert("operator".into(), Value::String(operator));
|
||||
map.insert("value".into(), condition.config.get("value").cloned().unwrap_or(Value::Null));
|
||||
let resolved = crate::models::FlowCondition {
|
||||
id: condition.id.clone(), kind: shared.kind.clone(), config, inputs: Vec::new(),
|
||||
};
|
||||
validate_condition(&resolved, state)?;
|
||||
} else if flow_string(&condition.config, "operator").is_some() || condition.config.get("value").is_some() {
|
||||
return Err(AppError::BadRequest("this shared Flow input is already boolean and does not accept a comparison".into()));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
+10
-7
@@ -120,13 +120,16 @@ fn validate_flow_shared_inputs(settings: &mut RuntimeSettings, state: &AppState)
|
||||
"night_mode" | "constant") {
|
||||
return Err(AppError::BadRequest(format!("unsupported shared Flow input kind: {}", item.kind)));
|
||||
}
|
||||
let condition = crate::models::FlowCondition {
|
||||
id: item.id.clone(),
|
||||
kind: item.kind.clone(),
|
||||
config: item.config.clone(),
|
||||
inputs: Vec::new(),
|
||||
};
|
||||
validate_condition(&condition, state)?;
|
||||
if !item.config.is_object() {
|
||||
return Err(AppError::BadRequest("shared Flow input config must be an object".into()));
|
||||
}
|
||||
if item.config.get("operator").is_some() {
|
||||
return Err(AppError::BadRequest("shared Flow inputs are value sources; operator belongs to the Flow block".into()));
|
||||
}
|
||||
if shared_input_comparison_kind(&item.kind) && item.config.get("value").is_some() {
|
||||
return Err(AppError::BadRequest("shared Flow inputs are value sources; comparison value belongs to the Flow block".into()));
|
||||
}
|
||||
validate_shared_input_source(&item.kind, &item.config, state)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user