tovuk 0.1.114

Use Tovuk scraper APIs from a native CLI.
use super::super::{
    args::CliOptions,
    errors::{Result, agent_error},
    utils::encode_component,
};
use super::{
    common::{command_arg, joined_args, optional_trimmed_value},
    generic::{print_authenticated_mutation, print_paged_authenticated},
};
use reqwest::Method;
use serde::Serialize;
use serde_json::Value;

#[derive(Serialize)]
struct SupportTicketInput {
    subject: String,
    details: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    severity: Option<SupportSeverity>,
    #[serde(skip_serializing_if = "Option::is_none")]
    request_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    scraper_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    failing_command: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    first_log_line: Option<String>,
}

#[derive(Clone, Copy, Serialize)]
#[serde(rename_all = "lowercase")]
enum SupportSeverity {
    Low,
    Normal,
    Urgent,
}

pub(crate) fn support_command(cli: &CliOptions) -> Result<()> {
    match cli.args.first().map_or("list", String::as_str) {
        "list" => print_paged_authenticated(cli, "/v1/support/tickets"),
        "create" => support_create(cli),
        "resolve" => support_resolve(cli),
        _ => Err(agent_error(
            "unknown_command",
            "Unknown support command.",
            "Use `tovuk support list --json`, `tovuk support create \"Subject\" \"Details\" --json`, or `tovuk support resolve <ticket_id> --json`.",
            cli.output.json,
        )),
    }
}

fn support_resolve(cli: &CliOptions) -> Result<()> {
    let ticket_id = command_arg(
        cli,
        "invalid_support_ticket",
        "Support ticket id is required.",
        "Use `tovuk support resolve <ticket_id> --json` with an id from support list.",
    )?;
    print_authenticated_mutation(
        cli,
        Method::POST,
        &format!(
            "/v1/support/tickets/{}/resolve",
            encode_component(&ticket_id)
        ),
        None,
    )
}

fn support_create(cli: &CliOptions) -> Result<()> {
    print_authenticated_mutation(
        cli,
        Method::POST,
        "/v1/support/tickets",
        Some(support_create_body(cli)?),
    )
}

fn support_create_body(cli: &CliOptions) -> Result<Value> {
    let subject = cli.args.get(1).map_or("", String::as_str).trim();
    let details = support_details(cli);
    if subject.is_empty() || details.is_empty() {
        return Err(agent_error(
            "invalid_support_ticket",
            "Support ticket subject and details are required.",
            "Use `tovuk support create \"Short subject\" \"Command output, request id, and first actionable error line\" --json`.",
            cli.output.json,
        ));
    }
    serde_json::to_value(SupportTicketInput {
        subject: subject.to_owned(),
        details,
        severity: support_severity(cli)?,
        request_id: optional_trimmed_value(cli.request_id.as_str()),
        scraper_id: optional_trimmed_value(cli.scraper_id.as_str()),
        failing_command: optional_trimmed_value(cli.failing_command.as_str()),
        first_log_line: optional_trimmed_value(cli.first_log_line.as_str()),
    })
    .map_err(|error| {
        agent_error(
            "invalid_support_ticket",
            format!("Support ticket input could not be encoded: {error}"),
            "Retry with visible support ticket subject and details.",
            cli.output.json,
        )
    })
}

fn support_details(cli: &CliOptions) -> String {
    joined_args(cli, 2)
}

fn support_severity(cli: &CliOptions) -> Result<Option<SupportSeverity>> {
    let Some(value) = optional_trimmed_value(cli.severity.as_str()) else {
        return Ok(None);
    };
    match value.as_str() {
        "low" => Ok(Some(SupportSeverity::Low)),
        "normal" => Ok(Some(SupportSeverity::Normal)),
        "urgent" => Ok(Some(SupportSeverity::Urgent)),
        _ => Err(agent_error(
            "invalid_support_ticket",
            "Support ticket severity must be low, normal, or urgent.",
            "Use `--severity low`, `--severity normal`, or `--severity urgent`.",
            cli.output.json,
        )),
    }
}

#[cfg(test)]
#[path = "support_tests.rs"]
mod tests;