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