use serde_json::{Map, Value};
use super::{routine_path, to_body};
pub(super) fn set_routine_enabled(routine: &str, enabled: bool, json: bool) -> i32 {
let body = serde_json::json!({ "enabled": enabled }).to_string();
match crate::cli::http_request_json("PATCH", &routine_path(routine), Some(&body)) {
Ok((status, resp)) if (200..300).contains(&status) => {
report_enabled(routine, enabled, &resp, json);
0
}
Ok((status, resp)) => report_http_error(status, &resp),
Err(_) => not_running(),
}
}
pub(super) fn move_routine(routine: &str, folder: Option<String>, slug: Option<String>) -> i32 {
let (id, current_slug) = match resolve_routine(routine) {
Ok(found) => found,
Err(code) => return code,
};
let Some(slug) = slug.or(current_slug) else {
return 1;
};
let mut map = Map::new();
map.insert("slug".to_string(), Value::String(slug));
if let Some(folder) = folder {
map.insert("folder".to_string(), Value::String(folder));
}
match crate::cli::http_request_json(
"POST",
&format!("{}/move", routine_path(&id)),
Some(&to_body(map)),
) {
Ok((status, resp)) if (200..300).contains(&status) => {
print_move_result(&resp);
0
}
Ok((status, resp)) => report_http_error(status, &resp),
Err(_) => not_running(),
}
}
fn resolve_routine(routine: &str) -> Result<(String, Option<String>), i32> {
if let Ok((status, resp)) = crate::cli::http_request_json("GET", &routine_path(routine), None) {
if (200..300).contains(&status) {
return Ok(parse_routine_identity(&resp).unwrap_or_else(|| (routine.to_string(), None)));
}
} else {
return Err(not_running());
}
let resp = match crate::cli::http_request_json("GET", "/api/v1/routines", None) {
Ok((status, resp)) if (200..300).contains(&status) => resp,
Ok((status, resp)) => {
return Err(report_http_error(status, &resp));
}
Err(_) => return Err(not_running()),
};
let Ok(value) = serde_json::from_str::<Value>(&resp) else {
eprintln!("error: could not parse routines list from server");
return Err(1);
};
value
.as_array()
.and_then(|items| {
items.iter().find_map(|item| {
let id = item.get("id")?.as_str()?;
let slug = item.get("slug").and_then(Value::as_str);
let rel_path = item.get("rel_path").and_then(Value::as_str);
(routine == id || Some(routine) == slug || Some(routine) == rel_path)
.then(|| (id.to_string(), slug.map(ToString::to_string)))
})
})
.ok_or_else(|| {
eprintln!("error: no routine with id, slug, or path {routine}");
1
})
}
fn parse_routine_identity(resp: &str) -> Option<(String, Option<String>)> {
let value = serde_json::from_str::<Value>(resp).ok()?;
let id = value.get("id")?.as_str()?.to_string();
let slug = value
.get("slug")
.and_then(Value::as_str)
.map(ToString::to_string);
Some((id, slug))
}
fn report_enabled(routine: &str, requested: bool, resp: &str, json: bool) {
let parsed = serde_json::from_str::<Value>(resp).ok();
let id = parsed
.as_ref()
.and_then(|value| value.get("id"))
.and_then(Value::as_str)
.unwrap_or(routine);
let state = parsed
.as_ref()
.and_then(|value| value.get("enabled"))
.and_then(Value::as_bool)
.unwrap_or(requested);
if json {
println!("{}", serde_json::json!({ "routine": id, "enabled": state }));
} else {
let word = if state { "enabled" } else { "disabled" };
println!("routine {id} is now {word}");
}
}
fn print_move_result(resp: &str) {
let parsed = serde_json::from_str::<Value>(resp).ok();
let Some(value) = parsed.as_ref() else {
println!("{resp}");
return;
};
let id = value.get("id").and_then(Value::as_str).unwrap_or("routine");
let rel_path = value
.get("rel_path")
.and_then(Value::as_str)
.unwrap_or("unknown");
println!("moved routine {id} to {rel_path}");
}
fn report_http_error(status: u16, resp: &str) -> i32 {
eprintln!("error: server returned HTTP {status}");
if !resp.is_empty() {
eprintln!("{resp}");
}
1
}
fn not_running() -> i32 {
eprintln!("moadim is not running");
crate::cli::EXIT_NOT_RUNNING
}
#[cfg(test)]
#[path = "commands_routine_actions_tests.rs"]
mod commands_routine_actions_tests;