made-client 0.7.8

Reusable operator client for MADE's public gRPC API.
Documentation
use std::path::Path;

use made_proto::v1::{GenerateCeremonyReportRequest, GenerateCeremonyReportResponse};
use tokio::io::AsyncWriteExt;
use uuid::Uuid;

use crate::{MadeClient, MadeClientError};

impl MadeClient {
    pub async fn generate_report(
        &self,
        ceremony_ids: Vec<String>,
        title: impl Into<String>,
    ) -> Result<GenerateCeremonyReportResponse, MadeClientError> {
        self.rpc()
            .generate_ceremony_report(Self::request(
                &self.context(),
                "/underpass.made.v1.MadeService/GenerateCeremonyReport",
                GenerateCeremonyReportRequest {
                    ceremony_ids,
                    title: title.into(),
                },
            ))
            .await
            .map(tonic::Response::into_inner)
            .map_err(MadeClientError::from_status)
    }

    pub async fn export_report(
        &self,
        ceremony_ids: Vec<String>,
        title: impl Into<String>,
        destination: &Path,
    ) -> Result<GenerateCeremonyReportResponse, MadeClientError> {
        let report = self.generate_report(ceremony_ids, title).await?;
        let parent = destination.parent().unwrap_or_else(|| Path::new("."));
        tokio::fs::create_dir_all(parent)
            .await
            .map_err(|error| MadeClientError::io(parent, error))?;
        let name = destination
            .file_name()
            .and_then(|name| name.to_str())
            .unwrap_or("report.md");
        let temporary = parent.join(format!(".{name}.{}.part", Uuid::new_v4()));
        let result = async {
            let mut file = tokio::fs::File::create(&temporary)
                .await
                .map_err(|error| MadeClientError::io(&temporary, error))?;
            file.write_all(report.report_markdown.as_bytes())
                .await
                .map_err(|error| MadeClientError::io(&temporary, error))?;
            file.sync_all()
                .await
                .map_err(|error| MadeClientError::io(&temporary, error))?;
            drop(file);
            tokio::fs::rename(&temporary, destination)
                .await
                .map_err(|error| MadeClientError::io(destination, error))
        }
        .await;
        if result.is_err() {
            let _ = tokio::fs::remove_file(&temporary).await;
        }
        result.map(|()| report)
    }
}