ironsbe-codegen 0.3.0

Code generation from SBE XML schemas for IronSBE
Documentation
//! Main code generator orchestration.

use ironsbe_schema::ir::SchemaIr;

use crate::rust::{EnumGenerator, MessageGenerator, TypeGenerator};

/// Main code generator for SBE schemas.
pub struct Generator<'a> {
    ir: &'a SchemaIr,
}

impl<'a> Generator<'a> {
    /// Creates a new generator for the given schema IR.
    #[must_use]
    pub fn new(ir: &'a SchemaIr) -> Self {
        Self { ir }
    }

    /// Generates the complete Rust code for the schema.
    #[must_use]
    pub fn generate(&self) -> String {
        let mut output = String::with_capacity(64 * 1024);

        // File header
        self.generate_header(&mut output);

        // Constants
        self.generate_constants(&mut output);

        // Types (enums, sets, composites)
        let type_gen = TypeGenerator::new(self.ir);
        output.push_str(&type_gen.generate());

        // Enums
        let enum_gen = EnumGenerator::new(self.ir);
        output.push_str(&enum_gen.generate());

        // Messages
        let msg_gen = MessageGenerator::new(self.ir);
        output.push_str(&msg_gen.generate());

        output
    }

    /// Generates the file header with imports.
    fn generate_header(&self, output: &mut String) {
        output.push_str("// Generated by IronSBE codegen - DO NOT EDIT\n");
        output.push_str(&format!(
            "// Schema: {} v{}\n",
            self.ir.package, self.ir.schema_version
        ));
        output.push('\n');
        output.push_str("use ironsbe_core::{\n");
        output.push_str("    buffer::{ReadBuffer, WriteBuffer},\n");
        output.push_str("    header::{MessageHeader, GroupHeader, VarDataHeader},\n");
        output.push_str("    decoder::{SbeDecoder, DecodeError},\n");
        output.push_str("    encoder::SbeEncoder,\n");
        output.push_str("};\n");
        output.push('\n');
    }

    /// Generates schema constants.
    fn generate_constants(&self, output: &mut String) {
        output.push_str(&format!(
            "/// Schema ID for this protocol.\n\
             pub const SCHEMA_ID: u16 = {};\n",
            self.ir.schema_id
        ));
        output.push_str(&format!(
            "/// Schema version for this protocol.\n\
             pub const SCHEMA_VERSION: u16 = {};\n",
            self.ir.schema_version
        ));
        output.push('\n');
    }
}

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

    #[test]
    fn test_generate_simple_schema() {
        let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
<sbe:messageSchema xmlns:sbe="http://fixprotocol.io/2016/sbe"
                   package="test" id="1" version="1" byteOrder="littleEndian">
    <types>
        <type name="uint64" primitiveType="uint64"/>
    </types>
    <sbe:message name="TestMessage" id="1" blockLength="8">
        <field name="value" id="1" type="uint64" offset="0"/>
    </sbe:message>
</sbe:messageSchema>"#;

        let schema = parse_schema(xml).expect("Failed to parse");
        let ir = SchemaIr::from_schema(&schema);
        let generator = Generator::new(&ir);
        let code = generator.generate();

        assert!(code.contains("SCHEMA_ID: u16 = 1"));
        assert!(code.contains("SCHEMA_VERSION: u16 = 1"));
        assert!(code.contains("TestMessageDecoder"));
        assert!(code.contains("TestMessageEncoder"));
    }
}