use std::collections::BTreeMap;
use std::{any::TypeId, error::Error};
pub trait TypeRegistry {
fn register_type(&mut self, type_id: TypeId, info: TypeInfo) -> Result<(), Box<dyn Error>>;
}
type DependencyAdder = fn(&mut dyn TypeRegistry) -> Result<(), Box<dyn Error>>;
pub trait Reflection {
fn reflect() -> TypeInfo;
fn type_id() -> TypeId
where
Self: 'static,
{
TypeId::of::<Self>()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TypeInfo {
pub name: String,
pub module_path: String,
pub data: DataKind,
pub attributes: TypeAttributes,
pub docs: Vec<String>,
pub dependencies: Vec<DependencyAdder>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum DataKind {
Struct(StructInfo),
Enum(EnumInfo),
}
#[derive(Debug, Clone, PartialEq)]
pub struct StructInfo {
pub fields: FieldsInfo,
}
#[derive(Debug, Clone, PartialEq)]
pub struct EnumInfo {
pub variants: Vec<VariantInfo>,
pub representation: EnumRepresentation,
}
#[derive(Debug, Clone, PartialEq)]
pub enum FieldsInfo {
Named(Vec<FieldInfo>),
Unnamed(Vec<FieldInfo>), Unit,
}
#[derive(Debug, Clone, PartialEq)]
pub struct FieldInfo {
pub name: Option<String>,
pub ty: TypeRef,
pub attributes: FieldAttributes,
pub docs: Vec<String>,
pub index: usize,
}
#[derive(Debug, Clone, PartialEq)]
pub struct VariantInfo {
pub name: String,
pub fields: FieldsInfo,
pub attributes: VariantAttributes,
pub docs: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum TypeRef {
Primitive(PrimitiveType),
Path {
path: String,
generic_args: Vec<TypeRef>,
is_reflect_to: bool,
#[doc(hidden)]
type_id: Option<TypeId>,
},
Tuple(Vec<TypeRef>),
Array { elem_type: Box<TypeRef>, len: usize },
Option(Box<TypeRef>),
Result {
ok_type: Box<TypeRef>,
err_type: Box<TypeRef>,
},
Box(Box<TypeRef>),
Vec(Box<TypeRef>),
Map {
key_type: Box<TypeRef>,
value_type: Box<TypeRef>,
},
Unit,
Never,
Unsupported(String), }
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum PrimitiveType {
String,
Str,
Bool,
I8,
I16,
I32,
I64,
I128,
Isize,
U8,
U16,
U32,
U64,
U128,
Usize,
F32,
F64,
PathBuf,
Char,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct TypeAttributes {
pub rename: Option<String>,
pub rename_all: Option<RenameRuleValue>,
pub other: BTreeMap<String, String>, }
#[derive(Debug, Clone, PartialEq, Default)]
pub struct FieldAttributes {
pub rename: Option<String>,
pub skip: bool,
pub skip_serializing: bool,
pub skip_deserializing: bool,
pub skip_serializing_if: Option<String>, pub flatten: bool,
pub other: BTreeMap<String, String>,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct VariantAttributes {
pub rename: Option<String>,
pub skip: bool,
pub skip_serializing: bool,
pub skip_deserializing: bool,
pub other: BTreeMap<String, String>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum EnumRepresentation {
ExternallyTagged, InternallyTagged { tag: String },
AdjacentlyTagged { tag: String, content: String },
Untagged,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RenameRuleValue {
Rule(String), }
pub fn to_pascal_case(s: &str) -> String {
let mut result = String::with_capacity(s.len());
let mut capitalize_next = true;
for c in s.chars() {
if c == '_' || c == '-' {
capitalize_next = true;
} else if capitalize_next {
result.push(c.to_ascii_uppercase());
capitalize_next = false;
} else {
result.push(c);
}
}
result
}
pub fn to_snake_case(s: &str) -> String {
if s.is_empty() {
return String::new();
}
let mut output = String::with_capacity(s.len() + s.len() / 3); let mut chars = s.chars().peekable();
let mut last_char_was_upper = false;
while let Some(c) = chars.next() {
if c.is_uppercase() {
if !output.is_empty() && !output.ends_with('_') {
let next_is_lower = chars.peek().is_some_and(|next| next.is_lowercase());
if !last_char_was_upper || next_is_lower {
output.push('_');
}
}
output.push(c.to_ascii_lowercase());
last_char_was_upper = true;
} else if c == '-' || c == '_' || c.is_whitespace() {
if !output.is_empty() && !output.ends_with('_') {
output.push('_');
}
last_char_was_upper = false;
} else {
output.push(c);
last_char_was_upper = false;
}
}
output.trim_matches('_').to_string()
}
pub fn to_screaming_snake_case(s: &str) -> String {
to_snake_case(s).to_ascii_uppercase()
}
pub fn to_kebab_case(s: &str) -> String {
to_snake_case(s).replace('_', "-")
}
pub fn to_camel_case(s: &str) -> String {
let pascal = to_pascal_case(s);
if let Some(first) = pascal.chars().next() {
format!("{}{}", first.to_lowercase(), &pascal[first.len_utf8()..])
} else {
pascal }
}
pub fn apply_rename_rule(original: &str, rule: &Option<RenameRuleValue>) -> Option<String> {
match rule {
Some(RenameRuleValue::Rule(rule_str)) => match rule_str.as_str() {
"lowercase" => Some(original.to_lowercase()),
"UPPERCASE" => Some(original.to_uppercase()),
"PascalCase" => Some(to_pascal_case(original)),
"camelCase" => Some(to_camel_case(original)),
"snake_case" => Some(to_snake_case(original)),
"SCREAMING_SNAKE_CASE" => Some(to_screaming_snake_case(original)),
"kebab-case" => Some(to_kebab_case(original)),
_ => {
eprintln!("Warning: Unknown rename_all rule '{}'", rule_str);
None
}
},
None => None,
}
}