tank_core/
util.rs

1use proc_macro2::TokenStream;
2use quote::{ToTokens, TokenStreamExt, quote};
3use std::{borrow::Cow, cmp::min, collections::BTreeMap, ffi::CString, fmt::Write};
4use syn::Path;
5
6#[derive(Clone)]
7/// Polymorphic iterator adapter returning items from either variant.
8pub enum EitherIterator<A, B>
9where
10    A: Iterator,
11    B: Iterator<Item = A::Item>,
12{
13    Left(A),
14    Right(B),
15}
16impl<A, B> Iterator for EitherIterator<A, B>
17where
18    A: Iterator,
19    B: Iterator<Item = A::Item>,
20{
21    type Item = A::Item;
22    fn next(&mut self) -> Option<Self::Item> {
23        match self {
24            EitherIterator::Left(a) => a.next(),
25            EitherIterator::Right(b) => b.next(),
26        }
27    }
28}
29
30/// Quote a `BTreeMap<K, V>` into tokens.
31pub fn quote_btree_map<K: ToTokens, V: ToTokens>(value: &BTreeMap<K, V>) -> TokenStream {
32    let mut tokens = TokenStream::new();
33    for (k, v) in value {
34        let ks = k.to_token_stream();
35        let vs = v.to_token_stream();
36        tokens.append_all(quote! {
37            (#ks, #vs),
38        });
39    }
40    quote! {
41        ::std::collections::BTreeMap::from([
42            #tokens
43        ])
44    }
45}
46
47/// Quote a `Cow<T>` preserving borrowed vs owned status for generated code.
48pub fn quote_cow<T: ToOwned + ToTokens + ?Sized>(value: &Cow<T>) -> TokenStream
49where
50    <T as ToOwned>::Owned: ToTokens,
51{
52    match value {
53        Cow::Borrowed(v) => quote! { ::std::borrow::Cow::Borrowed(#v) },
54        Cow::Owned(v) => quote! { ::std::borrow::Cow::Borrowed(#v) },
55    }
56}
57
58/// Quote an `Option<T>` into tokens.
59pub fn quote_option<T: ToTokens>(value: &Option<T>) -> TokenStream {
60    match value {
61        None => quote! { None },
62        Some(v) => quote! { Some(#v) },
63    }
64}
65
66/// Determine if the trailing segments of a `syn::Path` match the expected identifiers.
67pub fn matches_path(path: &Path, expect: &[&str]) -> bool {
68    let len = min(path.segments.len(), expect.len());
69    path.segments
70        .iter()
71        .rev()
72        .take(len)
73        .map(|v| &v.ident)
74        .eq(expect.iter().rev().take(len))
75}
76
77/// Write an iterator of items separated by a delimiter into a string.
78pub fn separated_by<T, F>(
79    out: &mut String,
80    values: impl IntoIterator<Item = T>,
81    mut f: F,
82    separator: &str,
83) where
84    F: FnMut(&mut String, T),
85{
86    let mut len = out.len();
87    for v in values {
88        if out.len() > len {
89            out.push_str(separator);
90        }
91        len = out.len();
92        f(out, v);
93    }
94}
95
96/// Convenience wrapper converting into a `CString`, panicking on interior NUL.
97pub fn as_c_string<S: Into<Vec<u8>>>(str: S) -> CString {
98    CString::new(str.into()).expect("Expected a valid C string")
99}
100
101/// Consume a prefix of `input` while the predicate returns true, returning that slice.
102pub fn consume_while<'s>(input: &mut &'s str, predicate: impl FnMut(&char) -> bool) -> &'s str {
103    let len = input.chars().take_while(predicate).count();
104    if len == 0 {
105        return "";
106    }
107    let result = &input[..len];
108    *input = &input[len..];
109    result
110}
111
112pub fn extract_number<'s, const SIGNED: bool>(input: &mut &'s str) -> &'s str {
113    let mut end = 0;
114    let mut chars = input.chars().peekable();
115    if SIGNED && matches!(chars.peek(), Some('+') | Some('-')) {
116        chars.next();
117        end += 1;
118    }
119    for _ in chars.take_while(char::is_ascii_digit) {
120        end += 1;
121    }
122    let result = &input[..end];
123    *input = &input[end..];
124    result
125}
126
127pub fn print_timer(out: &mut String, quote: &str, h: i64, m: u8, s: u8, ns: u32) {
128    let mut subsecond = ns;
129    let mut width = 9;
130    while width > 1 && subsecond % 10 == 0 {
131        subsecond /= 10;
132        width -= 1;
133    }
134    let _ = write!(
135        out,
136        "{quote}{h:02}:{m:02}:{s:02}.{subsecond:0width$}{quote}",
137    );
138}
139
140#[macro_export]
141macro_rules! number_to_month {
142    ($month:expr, $throw:expr $(,)?) => {
143        match $month {
144            1 => Month::January,
145            2 => Month::February,
146            3 => Month::March,
147            4 => Month::April,
148            5 => Month::May,
149            6 => Month::June,
150            7 => Month::July,
151            8 => Month::August,
152            9 => Month::September,
153            10 => Month::October,
154            11 => Month::November,
155            12 => Month::December,
156            _ => $throw,
157        }
158    };
159}
160
161#[macro_export]
162macro_rules! month_to_number {
163    ($month:expr $(,)?) => {
164        match $month {
165            Month::January => 1,
166            Month::February => 2,
167            Month::March => 3,
168            Month::April => 4,
169            Month::May => 5,
170            Month::June => 6,
171            Month::July => 7,
172            Month::August => 8,
173            Month::September => 9,
174            Month::October => 10,
175            Month::November => 11,
176            Month::December => 12,
177        }
178    };
179}
180
181#[macro_export]
182/// Conditionally wrap a generated fragment in parentheses.
183macro_rules! possibly_parenthesized {
184    ($out:ident, $cond:expr, $v:expr) => {
185        if $cond {
186            $out.push('(');
187            $v;
188            $out.push(')');
189        } else {
190            $v;
191        }
192    };
193}
194
195#[macro_export]
196/// Truncate long strings for logging and error messages purpose.
197///
198/// Returns a `format_args!` that yields at most 497 characters from the start
199/// of the input followed by `...` when truncation occurred. Minimal overhead.
200///
201/// # Examples
202/// ```rust
203/// use tank_core::truncate_long;
204/// let short = "SELECT 1";
205/// assert_eq!(format!("{}", truncate_long!(short)), "SELECT 1\n");
206/// let long = format!("SELECT {}", "X".repeat(600));
207/// let logged = format!("{}", truncate_long!(long));
208/// assert!(logged.starts_with("SELECT XXXXXX"));
209/// assert!(logged.ends_with("...\n"));
210/// ```
211macro_rules! truncate_long {
212    ($query:expr) => {
213        format_args!(
214            "{}{}",
215            &$query[..::std::cmp::min($query.len(), 497)].trim(),
216            if $query.len() > 497 { "...\n" } else { "" },
217        )
218    };
219}
220
221/// Sends the value through the channel and logs in case of error.
222///
223/// Parameters:
224/// * `$tx`: sender channel
225/// * `$value`: value to be sent
226///
227/// *Example*:
228/// ```rust
229/// send_value!(tx, Ok(QueryResult::Row(row)));
230/// ```
231
232#[macro_export]
233macro_rules! send_value {
234    ($tx:ident, $value:expr) => {{
235        if let Err(e) = $tx.send($value) {
236            log::error!("{e:#}");
237        }
238    }};
239}
240
241/// Incrementally accumulates tokens from a speculative parse stream until one
242/// of the supplied parsers succeeds.
243///
244/// Returns `(accumulated_tokens, (parser1_option, parser2_option, ...))` with
245/// exactly one `Some(T)`: the first successful parser.
246#[doc(hidden)]
247#[macro_export]
248macro_rules! take_until {
249    ($original:expr, $($parser:expr),+ $(,)?) => {{
250        let macro_local_input = $original.fork();
251        let mut macro_local_result = (
252            TokenStream::new(),
253            ($({
254                let _ = $parser;
255                None
256            }),+),
257        );
258        loop {
259            if macro_local_input.is_empty() {
260                break;
261            }
262            let mut parsed = false;
263            let produced = ($({
264                let attempt = macro_local_input.fork();
265                if let Ok(content) = ($parser)(&attempt) {
266                    macro_local_input.advance_to(&attempt);
267                    parsed = true;
268                    Some(content)
269                } else {
270                    None
271                }
272            }),+);
273            if parsed {
274                macro_local_result.1 = produced;
275                break;
276            }
277            macro_local_result.0.append(macro_local_input.parse::<TokenTree>()?);
278        }
279        $original.advance_to(&macro_local_input);
280        macro_local_result
281    }};
282}
283
284#[macro_export]
285/// Implement the `Executor` trait for a transaction wrapper type by
286/// delegating each operation to an underlying connection object.
287///
288/// This reduces boilerplate across driver implementations. The macro expands
289/// into an `impl Executor for $transaction<'c>` with forwarding methods for
290/// `prepare`, `run`, `fetch`, `execute`, and `append`.
291///
292/// Parameters:
293/// * `$driver`: concrete driver type.
294/// * `$transaction`: transaction wrapper type (generic over lifetime `'c`).
295/// * `$connection`: field name on the transaction pointing to the connection.
296///
297/// # Examples
298/// ```rust
299/// use crate::{YourDBConnection, YourDBDriver};
300/// use tank_core::{Error, Result, Transaction, impl_executor_transaction};
301///
302/// pub struct YourDBTransaction<'c> {
303///     connection: &'c mut YourDBConnection,
304/// }
305///
306/// impl_executor_transaction!(YourDBDriver, YourDBTransaction<'c>, connection);
307///
308/// impl<'c> Transaction<'c> for YourDBTransaction<'c> { ... }
309/// ```
310macro_rules! impl_executor_transaction {
311    // Case 1: Lifetime is present (necessary for transactions)
312    ($driver:ty, $transaction:ident $(< $lt:lifetime >)?, $connection:ident) => {
313       impl $(<$lt>)? ::tank_core::Executor for $transaction $(<$lt>)? {
314            type Driver = $driver;
315
316            fn driver(&self) -> &Self::Driver {
317                self.$connection.driver()
318            }
319
320            fn prepare(
321                &mut self,
322                query: String,
323            ) -> impl Future<Output = ::tank_core::Result<::tank_core::Query<Self::Driver>>> + Send
324            {
325                self.$connection.prepare(query)
326            }
327
328            fn run<'s>(
329                &'s mut self,
330                query: impl ::tank_core::AsQuery<Self::Driver> + 's,
331            ) -> impl ::tank_core::stream::Stream<
332                Item = ::tank_core::Result<::tank_core::QueryResult>,
333            > + Send {
334                self.$connection.run(query)
335            }
336
337            fn fetch<'s>(
338                &'s mut self,
339                query: impl ::tank_core::AsQuery<Self::Driver> + 's,
340            ) -> impl ::tank_core::stream::Stream<
341                Item = ::tank_core::Result<::tank_core::RowLabeled>,
342            > + Send
343            + 's {
344                self.$connection.fetch(query)
345            }
346
347            fn execute<'s>(
348                &'s mut self,
349                query: impl ::tank_core::AsQuery<Self::Driver> + 's,
350            ) -> impl Future<Output = ::tank_core::Result<::tank_core::RowsAffected>> + Send {
351                self.$connection.execute(query)
352            }
353
354            fn append<'a, E, It>(
355                &mut self,
356                entities: It,
357            ) -> impl Future<Output = ::tank_core::Result<::tank_core::RowsAffected>> + Send
358            where
359                E: ::tank_core::Entity + 'a,
360                It: IntoIterator<Item = &'a E> + Send,
361            {
362                self.$connection.append(entities)
363            }
364        }
365    }
366}