use scythe_backend::manifest::BackendManifest;
use scythe_backend::naming::to_snake_case;
use scythe_backend::types::resolve_type_pair;
use scythe_core::analyzer::{AnalyzedColumn, AnalyzedParam};
use scythe_core::errors::{ErrorCode, ScytheError};
use crate::backend_trait::{ResolvedColumn, ResolvedParam};
use crate::overrides::{TypeOverride, find_override};
pub fn resolve_columns(
columns: &[AnalyzedColumn],
manifest: &BackendManifest,
overrides: &[TypeOverride],
source_table: &str,
) -> Result<Vec<ResolvedColumn>, ScytheError> {
columns
.iter()
.map(|col| {
let column_match = if source_table.is_empty() {
String::new()
} else {
format!("{}.{}", source_table, col.name)
};
let effective_neutral_type = find_override(overrides, &column_match, &col.neutral_type)
.unwrap_or(&col.neutral_type);
let (full_type, lang_type) =
resolve_type_pair(effective_neutral_type, manifest, col.nullable)
.map(|(f, l)| (f.into_owned(), l.into_owned()))
.map_err(|e| {
ScytheError::new(
ErrorCode::InternalError,
format!("type resolution failed for column '{}': {}", col.name, e),
)
})?;
Ok(ResolvedColumn {
name: col.name.clone(),
field_name: to_snake_case(&col.name).into_owned(),
lang_type,
full_type,
neutral_type: effective_neutral_type.to_string(),
nullable: col.nullable,
})
})
.collect()
}
pub fn resolve_params(
params: &[AnalyzedParam],
manifest: &BackendManifest,
overrides: &[TypeOverride],
source_table: &str,
) -> Result<Vec<ResolvedParam>, ScytheError> {
params
.iter()
.map(|param| {
let column_match = if source_table.is_empty() {
String::new()
} else {
format!("{}.{}", source_table, param.name)
};
let effective_neutral_type =
find_override(overrides, &column_match, ¶m.neutral_type)
.unwrap_or(¶m.neutral_type);
let (full_type, lang_type) =
resolve_type_pair(effective_neutral_type, manifest, param.nullable)
.map(|(f, l)| (f.into_owned(), l.into_owned()))
.map_err(|e| {
ScytheError::new(
ErrorCode::InternalError,
format!("type resolution failed for param '{}': {}", param.name, e),
)
})?;
let borrowed_type = param_type_to_borrowed(&full_type);
Ok(ResolvedParam {
name: param.name.clone(),
field_name: to_snake_case(¶m.name).into_owned(),
lang_type,
full_type,
borrowed_type,
neutral_type: effective_neutral_type.to_string(),
nullable: param.nullable,
})
})
.collect()
}
pub fn param_type_to_borrowed(rust_type: &str) -> String {
let copy_types = ["bool", "i16", "i32", "i64", "f32", "f64", "u64"];
if copy_types.contains(&rust_type) {
return rust_type.to_string();
}
if rust_type == "String" {
return "&str".to_string();
}
if let Some(inner) = rust_type
.strip_prefix("Option<")
.and_then(|s| s.strip_suffix('>'))
{
let borrowed_inner = param_type_to_borrowed(inner);
return format!("Option<{}>", borrowed_inner);
}
if let Some(inner) = rust_type
.strip_prefix("Vec<")
.and_then(|s| s.strip_suffix('>'))
{
return format!("&[{}]", inner);
}
format!("&{}", rust_type)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_param_type_to_borrowed_string() {
assert_eq!(param_type_to_borrowed("String"), "&str");
}
#[test]
fn test_param_type_to_borrowed_vec() {
assert_eq!(param_type_to_borrowed("Vec<i32>"), "&[i32]");
assert_eq!(param_type_to_borrowed("Vec<String>"), "&[String]");
}
#[test]
fn test_param_type_to_borrowed_passthrough() {
assert_eq!(param_type_to_borrowed("i32"), "i32");
assert_eq!(param_type_to_borrowed("i64"), "i64");
assert_eq!(param_type_to_borrowed("bool"), "bool");
assert_eq!(param_type_to_borrowed("f64"), "f64");
}
#[test]
fn test_param_type_to_borrowed_option_string() {
assert_eq!(param_type_to_borrowed("Option<String>"), "Option<&str>");
}
#[test]
fn test_param_type_to_borrowed_option_copy() {
assert_eq!(param_type_to_borrowed("Option<i32>"), "Option<i32>");
}
#[test]
fn test_param_type_to_borrowed_other() {
assert_eq!(param_type_to_borrowed("Uuid"), "&Uuid");
assert_eq!(param_type_to_borrowed("NaiveDateTime"), "&NaiveDateTime");
}
}