protoc-gen-ts-temporal 0.0.1

protoc plugin that emits a typed TypeScript Temporal client from temporal.v1.* annotated protos
Documentation
//! Plugin options parsed from `CodeGeneratorRequest.parameter`.
//!
//! Protoc passes a comma-separated `k=v` string via `--ts-temporal_opt=...`.
//! Unknown keys are an error so users see typos surfaced immediately rather
//! than silently falling back to defaults.

use anyhow::{Result, bail};

#[derive(Debug, Clone)]
pub struct Config {
    /// Suffix appended to the proto basename when computing the sibling
    /// import path for generated message types. Defaults to `_pb`, matching
    /// `@bufbuild/protobuf-es`. The generated TS will import from
    /// `./<basename><pb_suffix>.ts`.
    pub pb_suffix: String,
}

impl Default for Config {
    fn default() -> Self {
        Self {
            pb_suffix: "_pb".to_string(),
        }
    }
}

impl Config {
    /// Parse from the protoc parameter string, e.g. `pb_suffix=_pb`.
    pub fn parse(raw: &str) -> Result<Self> {
        let mut cfg = Self::default();
        if raw.is_empty() {
            return Ok(cfg);
        }
        for entry in raw.split(',') {
            let entry = entry.trim();
            if entry.is_empty() {
                continue;
            }
            let (key, value) = entry.split_once('=').unwrap_or((entry, ""));
            match key.trim() {
                "pb_suffix" => cfg.pb_suffix = value.trim().to_string(),
                other => bail!("unknown plugin option {other:?}. Recognised keys: pb_suffix"),
            }
        }
        Ok(cfg)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn defaults_when_empty() {
        let cfg = Config::parse("").unwrap();
        assert_eq!(cfg.pb_suffix, "_pb");
    }

    #[test]
    fn parses_known_keys() {
        let cfg = Config::parse("pb_suffix=_proto").unwrap();
        assert_eq!(cfg.pb_suffix, "_proto");
    }

    #[test]
    fn rejects_unknown_keys() {
        let err = Config::parse("frobnicate=yes").unwrap_err();
        assert!(format!("{err:#}").contains("unknown plugin option"));
    }

    #[test]
    fn tolerates_whitespace_and_trailing_commas() {
        let cfg = Config::parse(" pb_suffix = _pb , ").unwrap();
        assert_eq!(cfg.pb_suffix, "_pb");
    }
}