openlatch-provider 0.2.1

Self-service onboarding CLI + runtime daemon for OpenLatch Editors and Providers
//! `providers list / update / delete`.

use crate::api::editor::{delete_provider, list_providers, update_provider};
use crate::cli::commands::shared::{hard_confirm, make_client};
use crate::cli::{GlobalArgs, ProvidersAction};
use crate::error::OlError;
use crate::ui::output::OutputConfig;
use tabled::Table;

pub async fn run(g: &GlobalArgs, action: ProvidersAction) -> Result<(), OlError> {
    match action {
        ProvidersAction::List => list(g).await,
        ProvidersAction::Update { slug } => update(g, &slug).await,
        ProvidersAction::Delete { slug, yes } => delete(g, &slug, yes).await,
    }
}

async fn list(g: &GlobalArgs) -> Result<(), OlError> {
    let out = OutputConfig::resolve(g);
    let client = make_client().await?;
    let providers = list_providers(&client).await?;
    if out.is_machine() {
        out.print_json(&providers);
        return Ok(());
    }
    if providers.is_empty() {
        out.print_info("No providers found.");
        return Ok(());
    }
    let rows: Vec<crate::ui::tables::ProviderRow> = providers
        .into_iter()
        .map(|p| crate::ui::tables::ProviderRow {
            slug: p.slug,
            display_name: p.display_name.unwrap_or_default(),
            region: p.region.unwrap_or_else(|| "global".into()),
            capacity_qps: p
                .total_capacity_qps
                .map(|q| q.to_string())
                .unwrap_or_else(|| "".into()),
        })
        .collect();
    println!("{}", Table::new(rows));
    Ok(())
}

async fn update(g: &GlobalArgs, slug: &str) -> Result<(), OlError> {
    let out = OutputConfig::resolve(g);
    out.print_info(&format!(
        "`providers update` lands fully in P1.T6 (register flow); for now \
        edit your manifest then run `openlatch-provider register`. Targeted: \
        {slug}"
    ));
    Ok(())
}

async fn delete(g: &GlobalArgs, slug: &str, yes_flag: bool) -> Result<(), OlError> {
    let out = OutputConfig::resolve(g);
    let yes = g.yes || yes_flag;
    if g.dry_run {
        out.print_step(&format!(
            "--dry-run: would DELETE /api/v1/editor/providers/{slug}"
        ));
        return Ok(());
    }
    hard_confirm(slug, out.interactive, yes)?;
    let client = make_client().await?;
    match delete_provider(&client, slug).await {
        Ok(()) => {
            out.print_step(&format!("Deleted provider `{slug}`"));
            Ok(())
        }
        Err(e) if e.code.code == "OL-4234" => {
            out.print_step(&format!("Provider `{slug}` already absent — no-op"));
            Ok(())
        }
        Err(e) => Err(e),
    }
}

#[allow(dead_code)]
async fn _todo_update_endpoint(client: &crate::api::client::ApiClient, slug: &str) {
    // Placeholder so the `update_provider` API method gets exercised once
    // the schema for the patch body is finalised in P1.T6.
    let _ = update_provider(client, slug, &serde_json::json!({})).await;
}