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                join_group: col.join_group.clone(),
49                nullable_before_join: col.nullable_before_join,
50                sql_type: col.sql_type.clone(),
51            })
52        })
53        .collect()
54}
55
56/// Resolve analyzed params into resolved params using a backend manifest.
57///
58/// When `overrides` is non-empty, each param is checked against the override
59/// list before the normal type-resolution path.
60pub fn resolve_params(
61    params: &[AnalyzedParam],
62    manifest: &BackendManifest,
63    overrides: &[TypeOverride],
64    source_table: &str,
65) -> Result<Vec<ResolvedParam>, ScytheError> {
66    params
67        .iter()
68        .map(|param| {
69            let column_match = if source_table.is_empty() {
70                String::new()
71            } else {
72                format!("{}.{}", source_table, param.name)
73            };
74            let effective_neutral_type =
75                find_override(overrides, &column_match, &param.neutral_type).unwrap_or(&param.neutral_type);
76
77            let (full_type, lang_type) = 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    let copy_types = ["bool", "i16", "i32", "i64", "f32", "f64", "u64"];
103    if copy_types.contains(&rust_type) {
104        return rust_type.to_string();
105    }
106    if rust_type == "String" {
107        return "&str".to_string();
108    }
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    if let Some(inner) = rust_type.strip_prefix("Vec<").and_then(|s| s.strip_suffix('>')) {
114        return format!("&[{}]", inner);
115    }
116    format!("&{}", rust_type)
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn test_param_type_to_borrowed_string() {
125        assert_eq!(param_type_to_borrowed("String"), "&str");
126    }
127
128    #[test]
129    fn test_param_type_to_borrowed_vec() {
130        assert_eq!(param_type_to_borrowed("Vec<i32>"), "&[i32]");
131        assert_eq!(param_type_to_borrowed("Vec<String>"), "&[String]");
132    }
133
134    #[test]
135    fn test_param_type_to_borrowed_passthrough() {
136        assert_eq!(param_type_to_borrowed("i32"), "i32");
137        assert_eq!(param_type_to_borrowed("i64"), "i64");
138        assert_eq!(param_type_to_borrowed("bool"), "bool");
139        assert_eq!(param_type_to_borrowed("f64"), "f64");
140    }
141
142    #[test]
143    fn test_param_type_to_borrowed_option_string() {
144        assert_eq!(param_type_to_borrowed("Option<String>"), "Option<&str>");
145    }
146
147    #[test]
148    fn test_param_type_to_borrowed_option_copy() {
149        assert_eq!(param_type_to_borrowed("Option<i32>"), "Option<i32>");
150    }
151
152    #[test]
153    fn test_param_type_to_borrowed_other() {
154        assert_eq!(param_type_to_borrowed("Uuid"), "&Uuid");
155        assert_eq!(param_type_to_borrowed("NaiveDateTime"), "&NaiveDateTime");
156    }
157}