jute/
lib.rs

1//! # Jute
2//!
3//! Jute is a **schema-driven code generator** for Rust, inspired by
4//! Apache ZooKeeper’s *Jute* serialization format.
5//!
6//! This crate provides a high-level API to:
7//!
8//! - Parse `.jute` schema files into an abstract syntax tree (AST)
9//! - Resolve cross-file and cross-module type dependencies
10//! - Validate schemas (unknown types, ambiguities, cycles, etc.)
11//! - Generate idiomatic, type-safe Rust code
12//! - Generate binary serialization and deserialization logic
13//!
14//! The primary entry point to the API is [`JuteGenerator`], which
15//! orchestrates the full pipeline from schema parsing to code generation.
16//!
17//! ## Typical Workflow
18//!
19//! 1. Author one or more `.jute` schema files
20//! 2. Feed them into [`JuteGenerator`]
21//! 3. Generate Rust code into a target directory
22//! 4. Compile and use the generated types directly in your application
23//!
24//! ## Example
25//!
26//! ```no_run
27//! use jute::JuteGenerator;
28//!
29//! JuteGenerator::new()
30//!     .add_src_file("schema/common.jute")
31//!     .add_src_file("schema/model.jute")
32//!     .add_out_dir("generated")
33//!     .generate()
34//!     .expect("Jute code generation failed");
35//! ```
36//!
37
38use crate::{
39    code_generator::{CodeGenerator, rust::writer::RustCodeGenerator},
40    compiler::{ast::Module, build_ast, dependency_resolver::resolve_dependencies},
41};
42use std::{
43    path::{Path, PathBuf},
44    str::FromStr,
45};
46
47pub mod code_generator;
48pub(crate) mod compiler;
49pub mod errors;
50pub use crate::code_generator::rust::JuteSerializable;
51pub use crate::errors::JuteError;
52
53/// High-level Jute code generation driver.
54///
55/// `JuteGenerator` follows a builder-style API and is responsible for:
56/// 1. Collecting source `.jute` files
57/// 2. Parsing them into ASTs
58/// 3. Resolving type and module dependencies
59/// 4. Invoking the language-specific code generator (Rust)
60///
61/// # Example
62///
63/// ```no_run
64/// use jute::JuteGenerator;
65/// let schema_path1 = "./schema/1.jute";
66/// let schema_path2 = "./schema/2.jute";
67/// let out_dir_path= "generated";
68///
69/// JuteGenerator::new()
70///     .add_src_file(schema_path1)
71///     .add_src_file(schema_path2)
72///     .add_out_dir(out_dir_path)
73///     .generate()
74///     .unwrap();
75/// ```
76#[derive(Default)]
77pub struct JuteGenerator {
78    /// Output directory where generated code will be written.
79    ///
80    /// If not provided, code generation defaults to the current(src) directory.
81    pub out_dir: Option<PathBuf>,
82    /// List of input Jute schema files.
83    pub src_files: Vec<PathBuf>,
84}
85impl JuteGenerator {
86    /// Creates a new empty `JuteGenerator`.
87    pub fn new() -> Self {
88        Self {
89            out_dir: None,
90            src_files: Vec::new(),
91        }
92    }
93
94    /// Adds a Jute source file to the generator.
95    ///
96    /// # Panics
97    ///
98    /// Panics if the same file is added more than once.
99    pub fn add_src_file<P: AsRef<Path>>(mut self, file_path: P) -> Self {
100        let file_path = file_path.as_ref().to_path_buf().canonicalize().unwrap();
101        if self.src_files.iter().any(|x| **x == file_path) {
102            panic!("Same file added multiple times");
103        }
104        self.src_files.push(file_path);
105        self
106    }
107    /// Sets the output directory for generated code.
108    ///
109    /// # Panics
110    ///
111    /// Panics if the output directory is already set.
112    pub fn add_out_dir<P: AsRef<Path>>(mut self, out_dir: P) -> Self {
113        if self.out_dir.is_some() {
114            panic!("Output dir alreay assigned");
115        }
116        let out_dir = out_dir.as_ref().to_path_buf();
117        self.out_dir = Some(out_dir);
118        self
119    }
120
121    /// Executes the full code generation pipeline.
122    ///
123    /// This method:
124    /// 1. Parses all provided `.jute` files into AST documents
125    /// 2. Resolves cross-module and cross-file dependencies
126    /// 3. Merges all modules into a single collection
127    /// 4. Generates Rust code into the configured output directory
128    ///
129    /// # Errors
130    ///
131    /// Returns an error if:
132    /// - Parsing fails
133    /// - Dependency resolution fails
134    /// - Code generation fails
135    pub fn generate(self) -> Result<(), JuteError> {
136        // this function will handle all the generation logic
137        // we will parse all the files one by one and push docs in the vector
138        let mut docs = Vec::new();
139        for file in self.src_files {
140            let doc = build_ast(Path::new(&file))?;
141            docs.push(doc);
142        }
143        let dependencies = resolve_dependencies(&docs)?;
144        let merged_modules: Vec<Module> =
145            docs.into_iter().map(|doc| doc.modules).flatten().collect();
146        // now we have a array of docs now we will validate this doc
147        RustCodeGenerator::new(
148            merged_modules,
149            dependencies,
150            self.out_dir.unwrap_or(PathBuf::from_str("").unwrap()), // unwrap will be never called
151        )
152        .generate()?;
153        Ok(())
154    }
155}