robotrt-cli 0.1.0-beta.2

RobotRT modular robotics runtime and middleware components.
use crate::helpers::{
    first_positional, has_flag, option_value, parse_u64_option, resolve_runtime_endpoint,
};
use crate::gateway::{
    STATUS_OP_ACTION_CANCEL, STATUS_OP_ACTION_SEND, STATUS_SERVICE_NAME, StatusServiceResponse,
    build_op_payload_request, make_udp_service_client, next_request_id, validate_response,
};

const DEFAULT_DAEMON_ENDPOINT: &str = "127.0.0.1:7588";

pub fn action_send(args: &[String]) -> Result<(), String> {
    let action = first_positional(args).ok_or_else(|| String::from("missing action name"))?;
    let goal = option_value(args, "--goal").ok_or_else(|| String::from("missing --goal payload"))?;
    let timeout_ms = parse_u64_option(args, "--timeout-ms", 1000)?;
    let endpoint = resolve_runtime_endpoint(
        args,
        DEFAULT_DAEMON_ENDPOINT,
        "action control requires --endpoint in embedded mode",
    )?;
    let json = has_flag(args, "--json");

    let client = make_udp_service_client(endpoint.clone(), timeout_ms)?;
    let request_id = next_request_id();
    let request = build_op_payload_request(
        STATUS_OP_ACTION_SEND,
        serde_json::json!({
            "action": action,
            "goal": goal,
            "timeout_ms": timeout_ms,
        }),
    );

    let response: StatusServiceResponse = client
        .call_json(STATUS_SERVICE_NAME, request_id, &request)
        .map_err(|err| format!("action send to {endpoint} failed: {err}"))?;
    validate_response(&response, request_id, STATUS_OP_ACTION_SEND)?;
    let result = response
        .op_result
        .ok_or_else(|| format!("action send response from {endpoint} missing op_result"))?;

    if json {
        let payload = serde_json::json!({
            "api_version": "robotrt.action.send.v1",
            "source": {
                "mode": "remote_service",
                "service": STATUS_SERVICE_NAME,
                "endpoint": endpoint,
                "timeout_ms": timeout_ms,
            },
            "result": result,
        });
        println!(
            "{}",
            serde_json::to_string_pretty(&payload)
                .map_err(|err| format!("serialize action send json failed: {err}"))?
        );
        return Ok(());
    }

    println!("RobotRT Action Send");
    println!("endpoint: {endpoint}");
    println!("action: {}", result.get("action").and_then(serde_json::Value::as_str).unwrap_or("-"));
    println!("goal_id: {}", result.get("goal_id").and_then(serde_json::Value::as_str).unwrap_or("-"));
    println!("accepted: {}", result.get("accepted").and_then(serde_json::Value::as_bool).unwrap_or(false));
    println!("state: {}", result.get("state").and_then(serde_json::Value::as_str).unwrap_or("-"));

    Ok(())
}

pub fn action_cancel(args: &[String]) -> Result<(), String> {
    let action = first_positional(args).ok_or_else(|| String::from("missing action name"))?;
    let goal_id = option_value(args, "--goal-id").ok_or_else(|| String::from("missing --goal-id"))?;
    let timeout_ms = parse_u64_option(args, "--timeout-ms", 1000)?;
    let endpoint = resolve_runtime_endpoint(
        args,
        DEFAULT_DAEMON_ENDPOINT,
        "action control requires --endpoint in embedded mode",
    )?;
    let json = has_flag(args, "--json");

    let client = make_udp_service_client(endpoint.clone(), timeout_ms)?;
    let request_id = next_request_id();
    let request = build_op_payload_request(
        STATUS_OP_ACTION_CANCEL,
        serde_json::json!({
            "action": action,
            "goal_id": goal_id,
        }),
    );

    let response: StatusServiceResponse = client
        .call_json(STATUS_SERVICE_NAME, request_id, &request)
        .map_err(|err| format!("action cancel to {endpoint} failed: {err}"))?;
    validate_response(&response, request_id, STATUS_OP_ACTION_CANCEL)?;
    let result = response
        .op_result
        .ok_or_else(|| format!("action cancel response from {endpoint} missing op_result"))?;

    if json {
        let payload = serde_json::json!({
            "api_version": "robotrt.action.cancel.v1",
            "source": {
                "mode": "remote_service",
                "service": STATUS_SERVICE_NAME,
                "endpoint": endpoint,
                "timeout_ms": timeout_ms,
            },
            "result": result,
        });
        println!(
            "{}",
            serde_json::to_string_pretty(&payload)
                .map_err(|err| format!("serialize action cancel json failed: {err}"))?
        );
        return Ok(());
    }

    println!("RobotRT Action Cancel");
    println!("endpoint: {endpoint}");
    println!("action: {}", result.get("action").and_then(serde_json::Value::as_str).unwrap_or("-"));
    println!("goal_id: {}", result.get("goal_id").and_then(serde_json::Value::as_str).unwrap_or("-"));
    println!("canceled: {}", result.get("canceled").and_then(serde_json::Value::as_bool).unwrap_or(false));
    println!("state: {}", result.get("state").and_then(serde_json::Value::as_str).unwrap_or("-"));
    println!("error: {}", result.get("error").and_then(serde_json::Value::as_str).unwrap_or("-"));

    Ok(())
}