specta-go 0.0.2

Export your Rust types to Go
Documentation
use std::{borrow::Cow, path::Path};

use specta::{ResolvedTypes, Types};

use crate::{
    primitives::{self, GoContext},
    Error,
};

/// Controls whether the exporter applies Specta's serde rewrites first.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SerdeMode {
    Serialize,
    Deserialize,
    Both,
}

/// Allows configuring the format of the final file.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum Layout {
    /// Flatten all types into a single file. (Idiomatic for Go packages)
    #[default]
    FlatFile,
    /// Produce a dedicated file for each type (Not recommended for Go)
    Files,
}

/// Go language exporter.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Go {
    pub header: Cow<'static, str>,
    pub layout: Layout,
    package_name: String,
    pub serde: Option<SerdeMode>,
}

impl Default for Go {
    fn default() -> Self {
        Self {
            header: Cow::Borrowed(""),
            layout: Layout::FlatFile,
            package_name: "bindings".into(),
            serde: Some(SerdeMode::Both),
        }
    }
}

impl Go {
    pub fn new() -> Self {
        Default::default()
    }

    pub fn package_name(mut self, name: impl Into<String>) -> Self {
        self.package_name = name.into();
        self
    }

    pub fn header(mut self, header: impl Into<Cow<'static, str>>) -> Self {
        self.header = header.into();
        self
    }

    pub fn with_serde(mut self, mode: SerdeMode) -> Self {
        self.serde = Some(mode);
        self
    }

    pub fn export(&self, types: &Types) -> Result<String, Error> {
        let mut ctx = GoContext::default();
        let mut body = String::new();

        let resolved_types = if self.serde.is_some() {
            specta_serde::apply(types.clone())?
        } else {
            ResolvedTypes::from_resolved_types(types.clone())
        };
        let types = resolved_types.as_types();

        for ndt in types.into_sorted_iter() {
            let type_def = primitives::export(self, types, ndt, &mut ctx)?;
            body.push_str(&type_def);
            body.push('\n');
        }

        let mut out = String::new();
        if !self.header.is_empty() {
            out.push_str(&self.header);
            out.push('\n');
        }

        out.push_str("package ");
        out.push_str(&self.package_name);
        out.push_str("\n\n");

        if !ctx.imports.is_empty() {
            out.push_str("import (\n");
            let mut sorted: Vec<_> = ctx.imports.iter().collect();
            sorted.sort();
            for imp in sorted {
                out.push_str(&format!("\t\"{}\"\n", imp));
            }
            out.push_str(")\n\n");
        }

        out.push_str(&body);
        Ok(out)
    }

    pub fn export_to(&self, path: impl AsRef<Path>, types: &Types) -> Result<(), Error> {
        if self.layout == Layout::Files {
            return Err(Error::UnableToExport(Layout::Files));
        }

        let content = self.export(types)?;
        if let Some(parent) = path.as_ref().parent() {
            std::fs::create_dir_all(parent)?;
        }
        std::fs::write(path, content)?;
        Ok(())
    }
}