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