1 unstable release
| new 0.1.1 | Feb 20, 2026 |
|---|
#314 in Build Utils
Used in unistructgen-openapi-pars…
215KB
3.5K
SLoC
🦀 UniStructGen Codegen
Генератор идиоматичного Rust-кода из Intermediate Representation
📋 Содержание
- Обзор
- Установка
- Быстрый старт
- RustRenderer
- RenderOptions
- RustRendererBuilder
- Обработка типов
- Валидация
- Обработка ошибок
- Примеры
🎯 Обзор
unistructgen-codegen — это модуль генерации Rust-кода из IR (Intermediate Representation). Он обеспечивает:
- Идиоматичный Rust — правильное форматирование, стиль и соглашения
- Derive macros — автоматическое добавление Debug, Clone, PartialEq, serde
- Документация — сохранение doc-комментариев из IR
- Атрибуты — поддержка #[serde(...)], #[validate(...)], etc.
- Type safety — корректное маппирование IR типов в Rust типы
- Вложенные типы — генерация всех связанных структур
Поддерживаемые возможности
| Возможность | Описание |
|---|---|
| Structs | Публичные структуры с полями |
| Enums | Перечисления с вариантами |
| Doc comments | /// Документация |
| Derives | #[derive(...)] |
| Serde attributes | #[serde(rename = "...")] |
| Validation | #[validate(...)] |
| Nested types | Автоматическое разрешение зависимостей |
| Option/Vec/Map | Option<T>, Vec<T>, HashMap<K, V> |
📦 Установка
[dependencies]
unistructgen-codegen = "0.1"
unistructgen-core = "0.1"
🚀 Быстрый старт
use unistructgen_codegen::{RustRenderer, RenderOptions};
use unistructgen_core::{
CodeGenerator, IRModule, IRStruct, IRType, IRField,
IRTypeRef, PrimitiveKind
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Создаём IR модуль
let mut module = IRModule::new("users".to_string());
// Создаём структуру
let mut user = IRStruct::new("User".to_string());
user.doc = Some("Represents a user in the system".to_string());
user.add_derive("Debug".to_string());
user.add_derive("Clone".to_string());
user.add_derive("serde::Serialize".to_string());
user.add_derive("serde::Deserialize".to_string());
user.add_field(IRField::new(
"id".to_string(),
IRTypeRef::Primitive(PrimitiveKind::I64)
));
user.add_field(IRField::new(
"email".to_string(),
IRTypeRef::Primitive(PrimitiveKind::String)
));
let mut age = IRField::new(
"age".to_string(),
IRTypeRef::Option(Box::new(IRTypeRef::Primitive(PrimitiveKind::I32)))
);
age.doc = Some("User's age (optional)".to_string());
user.add_field(age);
module.add_type(IRType::Struct(user));
// Генерируем код
let renderer = RustRenderer::new(RenderOptions::default());
let code = renderer.generate(&module)?;
println!("{}", code);
Ok(())
}
Результат:
// Generated by unistructgen v0.1.0
// Do not edit this file manually
#![allow(dead_code)]
#![allow(unused_imports)]
/// Represents a user in the system
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct User {
pub id: i64,
pub email: String,
/// User's age (optional)
pub age: Option<i32>,
}
🎨 RustRenderer
Основной класс для генерации Rust-кода.
Создание
use unistructgen_codegen::{RustRenderer, RenderOptions};
// С дефолтными опциями
let renderer = RustRenderer::new(RenderOptions::default());
// С кастомными опциями
let renderer = RustRenderer::new(RenderOptions {
add_header: false, // Без заголовка "Generated by..."
add_clippy_allows: false, // Без #![allow(...)]
});
Методы
use unistructgen_core::CodeGenerator;
// Основной метод генерации (через трейт CodeGenerator)
let code = renderer.generate(&module)?;
// Альтернативный метод (прямой вызов)
let code = renderer.render(&module)?;
// Метаданные генератора
let metadata = renderer.metadata();
println!("Language: {}", renderer.language()); // "Rust"
println!("Extension: {}", renderer.file_extension()); // "rs"
// Валидация IR перед генерацией
renderer.validate(&module)?;
// Форматирование (placeholder для rustfmt)
let formatted = renderer.format(code)?;
⚙️ RenderOptions
Конфигурация генерации кода.
Опции
| Опция | Тип | По умолчанию | Описание |
|---|---|---|---|
add_header |
bool |
true |
Добавлять заголовок с версией |
add_clippy_allows |
bool |
true |
Добавлять #![allow(...)] |
Примеры
use unistructgen_codegen::RenderOptions;
// Для production: с заголовком и clippy allows
let prod_options = RenderOptions::default();
// Для тестов/макросов: чистый код
let test_options = RenderOptions {
add_header: false,
add_clippy_allows: false,
};
// Только заголовок, без clippy
let custom_options = RenderOptions {
add_header: true,
add_clippy_allows: false,
};
🏗️ RustRendererBuilder
Fluent-билдер для создания рендерера.
use unistructgen_codegen::RustRendererBuilder;
let renderer = RustRendererBuilder::new()
.with_header(true)
.with_clippy_allows(true)
.build();
🔤 Обработка типов
Примитивные типы
| PrimitiveKind | Rust Type |
|---|---|
String |
String |
I8 |
i8 |
I16 |
i16 |
I32 |
i32 |
I64 |
i64 |
I128 |
i128 |
U8 |
u8 |
U16 |
u16 |
U32 |
u32 |
U64 |
u64 |
U128 |
u128 |
F32 |
f32 |
F64 |
f64 |
Bool |
bool |
Char |
char |
DateTime |
chrono::DateTime<chrono::Utc> |
Uuid |
uuid::Uuid |
Decimal |
rust_decimal::Decimal |
Json |
serde_json::Value |
Составные типы
// Option<T>
IRTypeRef::Option(Box::new(IRTypeRef::Primitive(PrimitiveKind::String)))
// → Option<String>
// Vec<T>
IRTypeRef::Vec(Box::new(IRTypeRef::Primitive(PrimitiveKind::I32)))
// → Vec<i32>
// HashMap<K, V>
IRTypeRef::Map(
Box::new(IRTypeRef::Primitive(PrimitiveKind::String)),
Box::new(IRTypeRef::Primitive(PrimitiveKind::I64))
)
// → std::collections::HashMap<String, i64>
// Ссылка на тип
IRTypeRef::Named("Address".to_string())
// → Address
Вложенные типы
// Option<Vec<String>>
IRTypeRef::Option(Box::new(
IRTypeRef::Vec(Box::new(
IRTypeRef::Primitive(PrimitiveKind::String)
))
))
// → Option<Vec<String>>
// Vec<HashMap<String, User>>
IRTypeRef::Vec(Box::new(
IRTypeRef::Map(
Box::new(IRTypeRef::Primitive(PrimitiveKind::String)),
Box::new(IRTypeRef::Named("User".to_string()))
)
))
// → Vec<std::collections::HashMap<String, User>>
✅ Валидация
Генератор валидирует атрибуты из FieldConstraints и создаёт #[validate(...)].
Поддерживаемые ограничения
use unistructgen_core::FieldConstraints;
let constraints = FieldConstraints {
// Длина строки/массива
min_length: Some(3),
max_length: Some(100),
// → #[validate(length(min = 3, max = 100))]
// Диапазон чисел
min_value: Some(0.0),
max_value: Some(100.0),
// → #[validate(range(min = 0, max = 100))]
// Regex паттерн
pattern: Some(r"^\w+$".to_string()),
// → #[validate(regex = "^\w+$")]
// Формат
format: Some("email".to_string()),
// → #[validate(email)]
};
Пример генерации
let mut field = IRField::new("email".to_string(), IRTypeRef::Primitive(PrimitiveKind::String));
field.constraints = FieldConstraints {
min_length: Some(5),
max_length: Some(255),
format: Some("email".to_string()),
..Default::default()
};
// Результат:
// #[validate(length(min = 5, max = 255), email)]
// pub email: String,
❌ Обработка ошибок
CodegenError
use unistructgen_codegen::CodegenError;
pub enum CodegenError {
/// Ошибка рендеринга
RenderError {
component: String, // "struct", "field", "enum"
context: String, // Имя типа
message: String,
},
/// Ошибка форматирования
FormatError {
context: String,
source: std::fmt::Error,
},
/// Ошибка валидации
ValidationError {
reason: String,
suggestion: Option<String>,
},
/// Невалидный идентификатор
InvalidIdentifier {
name: String,
context: String, // "struct name", "field name"
reason: String,
},
/// Неподдерживаемый тип
UnsupportedType {
type_name: String,
context: String,
reason: String,
alternative: Option<String>,
},
/// Превышена максимальная глубина вложенности
MaxDepthExceeded {
context: String,
max_depth: usize,
},
}
Обработка ошибок
use unistructgen_codegen::{RustRenderer, RenderOptions, CodegenError};
let renderer = RustRenderer::new(RenderOptions::default());
match renderer.generate(&module) {
Ok(code) => println!("{}", code),
Err(CodegenError::ValidationError { reason, suggestion }) => {
eprintln!("Validation failed: {}", reason);
if let Some(sug) = suggestion {
eprintln!("Suggestion: {}", sug);
}
},
Err(CodegenError::InvalidIdentifier { name, context, reason }) => {
eprintln!("Invalid {} '{}': {}", context, name, reason);
},
Err(e) => eprintln!("Error: {}", e),
}
Добавление suggestions
use unistructgen_codegen::CodegenError;
let error = CodegenError::validation_error("Module is empty")
.with_suggestion("Add at least one struct or enum to the module");
📝 Примеры
Генерация структуры с serde
use unistructgen_codegen::{RustRenderer, RenderOptions};
use unistructgen_core::*;
let mut user = IRStruct::new("User".to_string());
user.add_derive("serde::Serialize".to_string());
user.add_derive("serde::Deserialize".to_string());
let mut id_field = IRField::new("id".to_string(), IRTypeRef::Primitive(PrimitiveKind::I64));
let mut name_field = IRField::new("user_name".to_string(), IRTypeRef::Primitive(PrimitiveKind::String));
name_field.source_name = Some("userName".to_string());
name_field.attributes.push("serde(rename = \"userName\")".to_string());
user.add_field(id_field);
user.add_field(name_field);
let mut module = IRModule::new("users".to_string());
module.add_type(IRType::Struct(user));
let renderer = RustRenderer::new(RenderOptions::default());
let code = renderer.generate(&module)?;
Результат:
#[derive(serde::Serialize, serde::Deserialize)]
pub struct User {
pub id: i64,
#[serde(rename = "userName")]
pub user_name: String,
}
Генерация enum
use unistructgen_core::*;
let status = IREnum {
name: "OrderStatus".to_string(),
variants: vec![
IREnumVariant {
name: "Pending".to_string(),
source_value: None,
doc: Some("Order is pending".to_string()),
},
IREnumVariant {
name: "InProgress".to_string(),
source_value: Some("in_progress".to_string()),
doc: None,
},
IREnumVariant {
name: "Completed".to_string(),
source_value: None,
doc: None,
},
],
derives: vec!["Debug".to_string(), "Clone".to_string(), "serde::Serialize".to_string()],
doc: Some("Order status enum".to_string()),
};
let mut module = IRModule::new("orders".to_string());
module.add_type(IRType::Enum(status));
let renderer = RustRenderer::new(RenderOptions::default());
let code = renderer.generate(&module)?;
Результат:
/// Order status enum
#[derive(Debug, Clone, serde::Serialize)]
pub enum OrderStatus {
/// Order is pending
Pending,
#[serde(rename = "in_progress")]
InProgress,
Completed,
}
Генерация с валидацией
let mut email_field = IRField::new(
"email".to_string(),
IRTypeRef::Primitive(PrimitiveKind::String)
);
email_field.doc = Some("User's email address".to_string());
email_field.constraints = FieldConstraints {
min_length: Some(5),
max_length: Some(255),
format: Some("email".to_string()),
pattern: Some(r"^[\w@.]+$".to_string()),
..Default::default()
};
let mut age_field = IRField::new(
"age".to_string(),
IRTypeRef::Primitive(PrimitiveKind::I32)
);
age_field.constraints = FieldConstraints {
min_value: Some(0.0),
max_value: Some(150.0),
..Default::default()
};
Результат:
/// User's email address
#[validate(length(min = 5, max = 255), regex = "^[\w@.]+$", email)]
pub email: String,
#[validate(range(min = 0, max = 150))]
pub age: i32,
🔗 Связанные модули
- unistructgen-core — IR, трейты, pipeline
- unistructgen-json-parser — JSON → IR
- unistructgen-markdown-parser — Markdown → IR
- unistructgen-openapi-parser — OpenAPI → IR
🗺 Roadmap
- Интеграция с
rustfmtдля форматирования - Генерация
implблоков - Генерация
From/Intoтрейтов - Генерация
Builderпаттерна - Поддержка
#[cfg(...)]атрибутов
📜 Лицензия
MIT или Apache-2.0 — на ваш выбор.
Dependencies
~1–2.2MB
~45K SLoC