use std::collections::BTreeMap;
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use zynk_schema::ApiGraph;
#[derive(Debug, Clone, Copy)]
pub struct GenerationContext<'a> {
pub graph: &'a ApiGraph,
pub options: &'a Value,
}
impl<'a> GenerationContext<'a> {
pub fn new(graph: &'a ApiGraph, options: &'a Value) -> Self {
Self { graph, options }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GeneratedFile {
pub path: PathBuf,
pub contents: String,
}
impl GeneratedFile {
pub fn new(path: impl Into<PathBuf>, contents: impl Into<String>) -> Self {
Self {
path: path.into(),
contents: contents.into(),
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct GenerationResult {
pub files: Vec<GeneratedFile>,
}
impl GenerationResult {
pub fn new(files: Vec<GeneratedFile>) -> Self {
Self { files }
}
}
pub trait Generator {
fn generate(&self, ctx: &GenerationContext) -> GenerationResult;
}
#[derive(Default)]
pub struct GeneratorRegistry {
generators: BTreeMap<String, Box<dyn Generator>>,
}
impl GeneratorRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register(
&mut self,
name: impl Into<String>,
generator: Box<dyn Generator>,
) -> Option<Box<dyn Generator>> {
self.generators.insert(name.into(), generator)
}
pub fn get(&self, name: &str) -> Option<&dyn Generator> {
self.generators.get(name).map(Box::as_ref)
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use serde_json::json;
use zynk_schema::{ApiGraph, Endpoint, EndpointKind, TypeRef};
use super::{GeneratedFile, GenerationContext, GenerationResult, Generator, GeneratorRegistry};
struct TinyGenerator;
impl Generator for TinyGenerator {
fn generate(&self, ctx: &GenerationContext) -> GenerationResult {
let endpoint_count = ctx.graph.endpoints.len();
let suffix = ctx
.options
.get("suffix")
.and_then(serde_json::Value::as_str)
.unwrap_or("txt");
GenerationResult::new(vec![GeneratedFile::new(
PathBuf::from(format!("api.{suffix}")),
format!("endpoints={endpoint_count}"),
)])
}
}
#[test]
fn generator_trait_uses_context_and_returns_generated_files() {
let mut graph = ApiGraph::new();
graph.insert_endpoint(Endpoint::new("ping", EndpointKind::Rpc, TypeRef::void()));
let options = json!({ "suffix": "ts" });
let ctx = GenerationContext::new(&graph, &options);
let result = TinyGenerator.generate(&ctx);
assert_eq!(result.files.len(), 1);
assert_eq!(result.files[0].path, PathBuf::from("api.ts"));
assert_eq!(result.files[0].contents, "endpoints=1");
}
#[test]
fn registry_registers_and_looks_up_generators_by_name() {
let mut registry = GeneratorRegistry::new();
assert!(registry.get("typescript").is_none());
assert!(registry
.register("typescript", Box::new(TinyGenerator))
.is_none());
let graph = ApiGraph::new();
let options = json!({ "suffix": "txt" });
let ctx = GenerationContext::new(&graph, &options);
let generator = registry.get("typescript").expect("registered generator");
let result = generator.generate(&ctx);
assert_eq!(
result.files,
vec![GeneratedFile::new("api.txt", "endpoints=0")]
);
assert!(registry.get("effect").is_none());
}
}