halo/
scan.rs

1//! Scan:为 `Struct::addr*` 提供可写入的“扫描目标”(对齐 go-sqlbuilder 的 Addr/Scan 体验)。
2//!
3//! go 中 `database/sql` 通过 `Scan(dest...)` 写入指针;Rust 没有统一反射式 Scan API。
4//! 本实现提供一个最小子集:把“字符串 token”写入到字段(用于对齐 go 的单测与示例)。
5
6use crate::valuer::SqlValuer;
7use std::marker::PhantomData;
8
9/// 扫描/解析错误。
10#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
11pub enum ScanError {
12    #[error("sql_builder: not enough tokens")]
13    NotEnoughTokens,
14    #[error("sql_builder: failed to parse int: {0}")]
15    ParseInt(#[from] std::num::ParseIntError),
16    #[error("sql_builder: failed to parse float")]
17    ParseFloat,
18    #[error("sql_builder: failed to parse bool")]
19    ParseBool,
20    #[error("sql_builder: scan into Option<T> is not supported")]
21    UnsupportedOption,
22    #[error("sql_builder: scan into this type is not supported")]
23    UnsupportedType,
24}
25
26/// 从字符串 token 写入自身(最小子集)。
27pub trait ScanFromStr {
28    fn scan_from_str(&mut self, s: &str) -> Result<(), ScanError>;
29}
30
31impl ScanFromStr for String {
32    fn scan_from_str(&mut self, s: &str) -> Result<(), ScanError> {
33        self.clear();
34        self.push_str(s);
35        Ok(())
36    }
37}
38
39impl ScanFromStr for i64 {
40    fn scan_from_str(&mut self, s: &str) -> Result<(), ScanError> {
41        *self = s.parse::<i64>()?;
42        Ok(())
43    }
44}
45
46impl ScanFromStr for i32 {
47    fn scan_from_str(&mut self, s: &str) -> Result<(), ScanError> {
48        *self = s.parse::<i32>()?;
49        Ok(())
50    }
51}
52
53impl ScanFromStr for u64 {
54    fn scan_from_str(&mut self, s: &str) -> Result<(), ScanError> {
55        *self = s.parse::<u64>()?;
56        Ok(())
57    }
58}
59
60impl ScanFromStr for u16 {
61    fn scan_from_str(&mut self, s: &str) -> Result<(), ScanError> {
62        *self = s.parse::<u16>()?;
63        Ok(())
64    }
65}
66
67impl ScanFromStr for f64 {
68    fn scan_from_str(&mut self, s: &str) -> Result<(), ScanError> {
69        *self = s.parse::<f64>().map_err(|_| ScanError::ParseFloat)?;
70        Ok(())
71    }
72}
73
74impl ScanFromStr for bool {
75    fn scan_from_str(&mut self, s: &str) -> Result<(), ScanError> {
76        match s {
77            "true" | "TRUE" | "1" => {
78                *self = true;
79                Ok(())
80            }
81            "false" | "FALSE" | "0" => {
82                *self = false;
83                Ok(())
84            }
85            _ => Err(ScanError::ParseBool),
86        }
87    }
88}
89
90impl<T: ScanFromStr> ScanFromStr for Option<T> {
91    fn scan_from_str(&mut self, s: &str) -> Result<(), ScanError> {
92        if s.eq_ignore_ascii_case("null") {
93            *self = None;
94            return Ok(());
95        }
96        let _ = s;
97        Err(ScanError::UnsupportedOption)
98    }
99}
100
101impl ScanFromStr for Box<dyn SqlValuer> {
102    fn scan_from_str(&mut self, _s: &str) -> Result<(), ScanError> {
103        Err(ScanError::UnsupportedType)
104    }
105}
106
107type Setter = fn(*mut (), &str) -> Result<(), ScanError>;
108
109fn set_impl<T: ScanFromStr>(ptr: *mut (), s: &str) -> Result<(), ScanError> {
110    // SAFETY: ptr 由宏从真实字段地址构造,且 lifetime 由 ScanCell 约束。
111    let r = unsafe { &mut *(ptr as *mut T) };
112    r.scan_from_str(s)
113}
114
115/// 一个可写入的扫描目标(类似 go 的指针 dest)。
116#[derive(Debug)]
117pub struct ScanCell<'a> {
118    ptr: *mut (),
119    set: Setter,
120    _pd: PhantomData<&'a mut ()>,
121}
122
123impl<'a> ScanCell<'a> {
124    pub fn from_ptr<T: ScanFromStr>(ptr: *mut T) -> Self {
125        Self {
126            ptr: ptr as *mut (),
127            set: set_impl::<T>,
128            _pd: PhantomData,
129        }
130    }
131
132    pub fn set_from_str(&mut self, s: &str) -> Result<(), ScanError> {
133        (self.set)(self.ptr, s)
134    }
135}
136
137/// 按空白分割输入,把每个 token 写入对应的 dest。
138pub fn scan_tokens(input: &str, mut dests: Vec<ScanCell<'_>>) -> Result<(), ScanError> {
139    let mut it = input.split_whitespace();
140    for d in dests.iter_mut() {
141        let token = it.next().ok_or(ScanError::NotEnoughTokens)?;
142        d.set_from_str(token)?;
143    }
144    Ok(())
145}