fundaia 0.53.0

Command line for the Fundaia deployment platform: projects, services, variables, deployments, logs and metrics
use anyhow::Result;
use owo_colors::OwoColorize;

use crate::api::client::{ApiClient, LogFilter};
use crate::api::models::LogLine;
use crate::output::{self, Format};
use crate::render::logs::{render_line, LineFormat};
use crate::render::theme;

pub struct LogQuery<'a> {
    pub follow: bool,
    pub tail: usize,
    pub stream_filter: Option<&'a str>,
    pub stage: Option<&'a str>,
    pub number: Option<u32>,
    pub timestamps: bool,
    pub trace: Option<&'a str>,
}

pub async fn run(
    client: &ApiClient,
    project_reference: &str,
    service_reference: &str,
    query: LogQuery<'_>,
    output_format: Format,
) -> Result<()> {
    let format = LineFormat::with_stream()
        .showing_time(query.timestamps)
        .showing_traces(query.trace.is_none());
    let trace = query.trace.map(str::to_lowercase);

    let (_, service) = client.locate(project_reference, service_reference).await?;
    let deployments = client.deployments(&service.id).await?;

    let chosen = match query.number {
        Some(wanted) => deployments.iter().find(|one| one.number == wanted),
        None => deployments.first(),
    };

    let Some(deployment) = chosen else {
        if output_format.is_json() {
            return Ok(());
        }
        match query.number {
            Some(wanted) => println!("`{}` no tiene ningún despliegue #{wanted}.", service.name),
            None => println!("`{}` no se ha desplegado todavía.", service.name),
        }
        return Ok(());
    };

    let matches = |candidate: &str| query.stream_filter.is_none_or(|wanted| candidate == wanted);

    if !query.follow {
        let lines = client
            .read_log(
                &deployment.id,
                0,
                LogFilter {
                    stage: query.stage,
                    trace: trace.as_deref(),
                    stream: query.stream_filter,
                    tail: Some(query.tail),
                },
            )
            .await?;
        let start = lines.len().saturating_sub(query.tail);

        let mut out = output::Lines::lock();
        for line in lines
            .iter()
            .skip(start)
            .filter(|line| matches(&line.stream))
        {
            if output_format.is_json() {
                out.json(line);
            } else {
                out.line(render_line(line, format));
            }
        }
        out.finish();
        return Ok(());
    }

    if !output_format.is_json() {
        println!(
            "  {} {} #{}{} · Ctrl-C para salir",
            theme::ARROW.style(theme::accent()),
            service.name.style(theme::strong()),
            deployment.number,
            query
                .stage
                .map_or_else(String::new, |step| format!(" · {step}")),
        );
    }

    let in_trace = |line: &LogLine| {
        trace
            .as_deref()
            .is_none_or(|wanted| line.trace_id.as_deref() == Some(wanted))
    };

    client
        .stream_log(&deployment.id, 0, query.stage, |line| {
            if !matches(&line.stream) || !in_trace(&line) {
                return;
            }
            if output_format.is_json() {
                output::emit(&line);
            } else {
                println!("{}", render_line(&line, format));
            }
        })
        .await
}