Skip to main content

scythe_codegen/
resolve.rs

1use scythe_backend::manifest::BackendManifest;
2use scythe_backend::naming::to_snake_case;
3use scythe_backend::types::resolve_type_pair;
4
5use scythe_core::analyzer::{AnalyzedColumn, AnalyzedParam};
6use scythe_core::errors::{ErrorCode, ScytheError};
7
8use crate::backend_trait::{ResolvedColumn, ResolvedParam};
9use crate::overrides::{TypeOverride, find_override};
10
11/// Resolve analyzed columns into resolved columns using a backend manifest.
12///
13/// When `overrides` is non-empty, each column is checked against the override
14/// list before the normal type-resolution path. The first matching override
15/// replaces the column's neutral type.
16pub fn resolve_columns(
17    columns: &[AnalyzedColumn],
18    manifest: &BackendManifest,
19    overrides: &[TypeOverride],
20    source_table: &str,
21) -> Result<Vec<ResolvedColumn>, ScytheError> {
22    columns
23        .iter()
24        .map(|col| {
25            let column_match = if source_table.is_empty() {
26                String::new()
27            } else {
28                format!("{}.{}", source_table, col.name)
29            };
30            let effective_neutral_type =
31                find_override(overrides, &column_match, &col.neutral_type).unwrap_or(&col.neutral_type);
32
33            let (full_type, lang_type) = resolve_type_pair(effective_neutral_type, manifest, col.nullable)
34                .map(|(f, l)| (f.into_owned(), l.into_owned()))
35                .map_err(|e| {
36                    ScytheError::new(
37                        ErrorCode::InternalError,
38                        format!("type resolution failed for column '{}': {}", col.name, e),
39                    )
40                })?;
41            Ok(ResolvedColumn {
42                name: col.name.clone(),
43                field_name: to_snake_case(&col.name).into_owned(),
44                lang_type,
45                full_type,
46                neutral_type: effective_neutral_type.to_string(),
47                nullable: col.nullable,
48            })
49        })
50        .collect()
51}
52
53/// Resolve analyzed params into resolved params using a backend manifest.
54///
55/// When `overrides` is non-empty, each param is checked against the override
56/// list before the normal type-resolution path.
57pub fn resolve_params(
58    params: &[AnalyzedParam],
59    manifest: &BackendManifest,
60    overrides: &[TypeOverride],
61    source_table: &str,
62) -> Result<Vec<ResolvedParam>, ScytheError> {
63    params
64        .iter()
65        .map(|param| {
66            let column_match = if source_table.is_empty() {
67                String::new()
68            } else {
69                format!("{}.{}", source_table, param.name)
70            };
71            let effective_neutral_type =
72                find_override(overrides, &column_match, &param.neutral_type).unwrap_or(&param.neutral_type);
73
74            let (full_type, lang_type) = resolve_type_pair(effective_neutral_type, manifest, param.nullable)
75                .map(|(f, l)| (f.into_owned(), l.into_owned()))
76                .map_err(|e| {
77                    ScytheError::new(
78                        ErrorCode::InternalError,
79                        format!("type resolution failed for param '{}': {}", param.name, e),
80                    )
81                })?;
82            let borrowed_type = param_type_to_borrowed(&full_type);
83            Ok(ResolvedParam {
84                name: param.name.clone(),
85                field_name: to_snake_case(&param.name).into_owned(),
86                lang_type,
87                full_type,
88                borrowed_type,
89                neutral_type: effective_neutral_type.to_string(),
90                nullable: param.nullable,
91            })
92        })
93        .collect()
94}
95
96/// Convert a resolved Rust type to its borrowed form for function parameters.
97/// Copy types (primitives) stay as-is; String becomes &str; other non-Copy types get a & prefix.
98pub fn param_type_to_borrowed(rust_type: &str) -> String {
99    // Copy types that should stay owned in function params
100    let copy_types = ["bool", "i16", "i32", "i64", "f32", "f64", "u64"];
101    if copy_types.contains(&rust_type) {
102        return rust_type.to_string();
103    }
104    // String -> &str
105    if rust_type == "String" {
106        return "&str".to_string();
107    }
108    // Option<T> wrapping: Option<String> -> Option<&str>, Option<Copy> stays, Option<Other> -> Option<&Other>
109    if let Some(inner) = rust_type.strip_prefix("Option<").and_then(|s| s.strip_suffix('>')) {
110        let borrowed_inner = param_type_to_borrowed(inner);
111        return format!("Option<{}>", borrowed_inner);
112    }
113    // Vec<T> -> &[T] (slice reference)
114    if let Some(inner) = rust_type.strip_prefix("Vec<").and_then(|s| s.strip_suffix('>')) {
115        return format!("&[{}]", inner);
116    }
117    // Everything else gets a & prefix
118    format!("&{}", rust_type)
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn test_param_type_to_borrowed_string() {
127        assert_eq!(param_type_to_borrowed("String"), "&str");
128    }
129
130    #[test]
131    fn test_param_type_to_borrowed_vec() {
132        assert_eq!(param_type_to_borrowed("Vec<i32>"), "&[i32]");
133        assert_eq!(param_type_to_borrowed("Vec<String>"), "&[String]");
134    }
135
136    #[test]
137    fn test_param_type_to_borrowed_passthrough() {
138        assert_eq!(param_type_to_borrowed("i32"), "i32");
139        assert_eq!(param_type_to_borrowed("i64"), "i64");
140        assert_eq!(param_type_to_borrowed("bool"), "bool");
141        assert_eq!(param_type_to_borrowed("f64"), "f64");
142    }
143
144    #[test]
145    fn test_param_type_to_borrowed_option_string() {
146        assert_eq!(param_type_to_borrowed("Option<String>"), "Option<&str>");
147    }
148
149    #[test]
150    fn test_param_type_to_borrowed_option_copy() {
151        assert_eq!(param_type_to_borrowed("Option<i32>"), "Option<i32>");
152    }
153
154    #[test]
155    fn test_param_type_to_borrowed_other() {
156        assert_eq!(param_type_to_borrowed("Uuid"), "&Uuid");
157        assert_eq!(param_type_to_borrowed("NaiveDateTime"), "&NaiveDateTime");
158    }
159}