openlatch-provider 0.2.1

Self-service onboarding CLI + runtime daemon for OpenLatch Editors and Providers
//! `bindings` endpoints — list/get/update/delete + regenerate-secret +
//! delete-secret + metrics.

use serde::{Deserialize, Serialize};

use crate::api::client::ApiClient;
use crate::api::editor::{EditorBindingRow, ListResponse};
use crate::error::OlError;

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct BindingDetail {
    pub id: String,
    pub tool: String,
    pub provider: String,
    #[serde(default)]
    pub state: Option<String>,
    /// Public HTTPS ingress for this binding. Joined server-side from the
    /// binding's provider — the field lives on `Provider` in the manifest,
    /// but the platform projects it onto `BindingDetail` so callers don't
    /// have to fetch the provider separately.
    #[serde(default)]
    pub endpoint_url: Option<String>,
    #[serde(default)]
    pub declared_latency_p95_ms: Option<u64>,
    #[serde(default)]
    pub capacity_qps: Option<u64>,
    #[serde(default)]
    pub priority: Option<u32>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct BindingMetrics {
    pub binding_id: String,
    #[serde(default)]
    pub score: Option<f64>,
    #[serde(default)]
    pub active_penalty: Option<f64>,
    #[serde(default)]
    pub latency_ms: Option<LatencyStats>,
    #[serde(default)]
    pub success_rate_24h: Option<f64>,
    #[serde(default)]
    pub thumbs_down_7d: Option<f64>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct LatencyStats {
    #[serde(default)]
    pub p50: Option<u64>,
    #[serde(default)]
    pub p95: Option<u64>,
    #[serde(default)]
    pub p99: Option<u64>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct BindingSecretReveal {
    /// Plaintext `whsec_live_…` — shown to the user exactly once.
    pub secret: String,
    pub binding_id: String,
}

pub async fn list(client: &ApiClient) -> Result<Vec<EditorBindingRow>, OlError> {
    let resp: ListResponse<EditorBindingRow> = client.get("/api/v1/editor/bindings").await?;
    Ok(resp.data)
}

pub async fn get(client: &ApiClient, id: &str) -> Result<BindingDetail, OlError> {
    client.get(&format!("/api/v1/editor/bindings/{id}")).await
}

pub async fn delete(client: &ApiClient, id: &str) -> Result<(), OlError> {
    let _: serde_json::Value = client
        .delete(&format!("/api/v1/editor/bindings/{id}"))
        .await?;
    Ok(())
}

pub async fn regenerate_secret(
    client: &ApiClient,
    id: &str,
) -> Result<BindingSecretReveal, OlError> {
    #[derive(Serialize)]
    struct Empty {}
    client
        .post(
            &format!("/api/v1/editor/bindings/{id}/regenerate-secret"),
            &Empty {},
        )
        .await
}

pub async fn delete_secret(client: &ApiClient, id: &str) -> Result<(), OlError> {
    let _: serde_json::Value = client
        .delete(&format!("/api/v1/editor/bindings/{id}/secret"))
        .await?;
    Ok(())
}

pub async fn metrics(client: &ApiClient, id: &str) -> Result<BindingMetrics, OlError> {
    client
        .get(&format!("/api/v1/editor/bindings/{id}/metrics"))
        .await
}