pub mod config;
pub mod generator;
pub mod injection_guard;
pub mod loop_verifier;
pub mod migration_mapper;
pub mod model_mapper;
pub mod repository_mapper;
#[cfg(feature = "openapi-reverse")]
pub mod db_schema;
pub use config::{NamingConvention, ReverseGenConfig};
pub use generator::{OpenApiReverseGenerator, ReverseGenResult};
pub use injection_guard::OpenApiInjectionGuard;
pub use loop_verifier::{ApiFirstLoopVerifier, LoopReport};
pub use migration_mapper::OpenApiToMigrationMapper;
pub use model_mapper::{Constraint, ModelField, RustType, SchemaToModelMapper};
pub use repository_mapper::OpenApiToRepositoryMapper;
#[cfg(feature = "openapi-reverse")]
pub use db_schema::{
ConstraintType, CrudApiEndpoint, DbColumn, DbConstraint, DbIndex, DbSchema, DbSchemaReader,
DbSchemaToCrudApiMapper, DbSchemaToOpenApiMapper, DbTable, FullReverseLoopVerifier, HttpMethod,
ReverseGenLog,
};
use thiserror::Error;
#[derive(Debug, Clone, Error)]
pub enum ReverseGenError {
#[error("OpenAPI spec parse failed at {path}: {reason}")]
SpecParseFailed { path: String, reason: String },
#[error("unsupported schema construct {construct} at {schema}, skipped")]
UnsupportedSchemaConstruct { construct: String, schema: String },
#[error("loop verification diff: {diff}")]
LoopVerificationDiff { diff: String },
#[error("injection detected in spec, refusing to execute embedded code")]
InjectionDetected,
#[error("unsigned spec not trusted, provide signature or use --trust-unsigned")]
UnsignedSpec,
#[error("user logic in editable region preserved, only skeleton updated")]
UserLogicOverwrite,
}
pub fn to_pascal_case(s: &str) -> String {
let mut result = String::new();
let mut next_upper = true;
for ch in s.chars() {
if ch == '_' || ch == '-' || ch == ' ' {
next_upper = true;
} else if next_upper {
result.push(ch.to_ascii_uppercase());
next_upper = false;
} else {
result.push(ch);
}
}
result
}
pub fn to_snake_case(s: &str) -> String {
let mut result = String::new();
let mut prev_lower = false;
for ch in s.chars() {
if ch.is_ascii_uppercase() {
if prev_lower {
result.push('_');
}
result.push(ch.to_ascii_lowercase());
prev_lower = false;
} else if ch == '-' || ch == ' ' {
result.push('_');
prev_lower = false;
} else {
result.push(ch);
prev_lower = ch.is_ascii_lowercase() || ch.is_ascii_digit();
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_to_pascal_case() {
assert_eq!(to_pascal_case("user"), "User");
assert_eq!(to_pascal_case("user_profile"), "UserProfile");
assert_eq!(to_pascal_case("user-profile"), "UserProfile");
assert_eq!(to_pascal_case("user profile"), "UserProfile");
}
#[test]
fn test_to_snake_case() {
assert_eq!(to_snake_case("User"), "user");
assert_eq!(to_snake_case("UserProfile"), "user_profile");
assert_eq!(to_snake_case("user-profile"), "user_profile");
assert_eq!(to_snake_case("userProfile"), "user_profile");
}
#[test]
fn test_reverse_gen_error_display() {
let err = ReverseGenError::SpecParseFailed {
path: "components.schemas.User".to_string(),
reason: "invalid type".to_string(),
};
assert!(err.to_string().contains("components.schemas.User"));
assert!(err.to_string().contains("invalid type"));
let err2 = ReverseGenError::InjectionDetected;
assert!(err2.to_string().contains("injection detected"));
}
}