use std::fmt::Write as _;
use crate::GeneratedCode;
const DECIMAL_IMPORT: &str = "github.com/shopspring/decimal";
pub(crate) fn go_file_header(
preamble: &str,
stdlib_imports: &[&str],
third_party_imports: &[&str],
uses_time: bool,
uses_decimal: bool,
) -> String {
let mut header = String::from(preamble);
header.push_str("\n\nimport (\n");
for import in stdlib_imports {
let _ = writeln!(header, "\t\"{import}\"");
}
if uses_time {
header.push_str("\t\"time\"\n");
}
if !third_party_imports.is_empty() || uses_decimal {
header.push('\n');
for import in third_party_imports {
let _ = writeln!(header, "\t\"{import}\"");
}
if uses_decimal {
let _ = writeln!(header, "\t\"{DECIMAL_IMPORT}\"");
}
}
header.push_str(")\n");
header
}
fn generated_code_contains(code: &GeneratedCode, needle: &str) -> bool {
[
code.enum_def.as_deref(),
code.model_struct.as_deref(),
code.row_struct.as_deref(),
code.query_fn.as_deref(),
]
.into_iter()
.flatten()
.any(|fragment| fragment.contains(needle))
|| code.nested_struct_defs.iter().any(|def| def.code.contains(needle))
}
pub(crate) fn generated_code_uses_time(generated: &[GeneratedCode]) -> bool {
generated.iter().any(|code| generated_code_contains(code, "time."))
}
pub(crate) fn generated_code_uses_decimal(generated: &[GeneratedCode]) -> bool {
generated.iter().any(|code| generated_code_contains(code, "decimal."))
}
#[cfg(test)]
mod tests {
use super::*;
fn code_with_row_struct(row_struct: &str) -> GeneratedCode {
GeneratedCode::build(|c| {
c.row_struct = Some(row_struct.to_string());
})
}
#[test]
fn test_go_file_header_single_group_omits_time_when_unused() {
let header = go_file_header("package queries", &["context", "database/sql"], &[], false, false);
assert_eq!(
header,
"package queries\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n)\n"
);
}
#[test]
fn test_go_file_header_single_group_includes_time_when_used() {
let header = go_file_header("package queries", &["context", "database/sql"], &[], true, false);
assert_eq!(
header,
"package queries\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"time\"\n)\n"
);
}
#[test]
fn test_go_file_header_two_group_matches_pgx_fixture_when_both_used() {
let preamble =
"// Code generated by scythe. DO NOT EDIT.\n// Run `goimports -w .` to fix imports.\npackage queries";
let header = go_file_header(preamble, &["context"], &["github.com/jackc/pgx/v5/pgxpool"], true, true);
assert_eq!(
header,
"// Code generated by scythe. DO NOT EDIT.\n// Run `goimports -w .` to fix imports.\npackage queries\n\n\
import (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/jackc/pgx/v5/pgxpool\"\n\t\"github.com/shopspring/\
decimal\"\n)\n"
);
}
#[test]
fn test_go_file_header_two_group_keeps_third_party_group_without_decimal() {
let header = go_file_header(
"package queries",
&["context"],
&["github.com/jackc/pgx/v5/pgxpool"],
false,
false,
);
assert_eq!(
header,
"package queries\n\nimport (\n\t\"context\"\n\n\t\"github.com/jackc/pgx/v5/pgxpool\"\n)\n"
);
}
#[test]
fn test_generated_code_uses_time_false_for_int_and_string_only() {
let generated = [code_with_row_struct("type Row struct {\n\tId int32\n\tName string\n}")];
assert!(!generated_code_uses_time(&generated));
assert!(!generated_code_uses_decimal(&generated));
}
#[test]
fn test_generated_code_uses_time_true_when_time_time_present() {
let generated = [code_with_row_struct("type Row struct {\n\tCreatedAt time.Time\n}")];
assert!(generated_code_uses_time(&generated));
assert!(!generated_code_uses_decimal(&generated));
}
#[test]
fn test_generated_code_uses_decimal_true_when_decimal_decimal_present() {
let generated = [code_with_row_struct("type Row struct {\n\tTotal decimal.Decimal\n}")];
assert!(!generated_code_uses_time(&generated));
assert!(generated_code_uses_decimal(&generated));
}
#[test]
fn test_generated_code_uses_time_checks_nested_struct_defs() {
let generated = [GeneratedCode::build(|c| {
c.nested_struct_defs.push(crate::NestedStructDef {
name: "nested".to_string(),
code: "type Nested struct {\n\tSeenAt time.Time\n}".to_string(),
});
})];
assert!(generated_code_uses_time(&generated));
}
}