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(str: impl Into<Vec<u8>>) -> CString {
98    CString::new(
99        str.into()
100            .into_iter()
101            .map(|b| if b == 0 { b'?' } else { b })
102            .collect::<Vec<u8>>(),
103    )
104    .unwrap_or_default()
105}
106
107/// Consume a prefix of `input` while the predicate returns true, returning that slice.
108pub fn consume_while<'s>(input: &mut &'s str, predicate: impl FnMut(&char) -> bool) -> &'s str {
109    let len = input.chars().take_while(predicate).count();
110    if len == 0 {
111        return "";
112    }
113    let result = &input[..len];
114    *input = &input[len..];
115    result
116}
117
118pub fn extract_number<'s, const SIGNED: bool>(input: &mut &'s str) -> &'s str {
119    let mut end = 0;
120    let mut chars = input.chars().peekable();
121    if SIGNED && matches!(chars.peek(), Some('+') | Some('-')) {
122        chars.next();
123        end += 1;
124    }
125    for _ in chars.take_while(char::is_ascii_digit) {
126        end += 1;
127    }
128    let result = &input[..end];
129    *input = &input[end..];
130    result
131}
132
133pub fn print_timer(out: &mut String, quote: &str, h: i64, m: u8, s: u8, ns: u32) {
134    let mut subsecond = ns;
135    let mut width = 9;
136    while width > 1 && subsecond % 10 == 0 {
137        subsecond /= 10;
138        width -= 1;
139    }
140    let _ = write!(
141        out,
142        "{quote}{h:02}:{m:02}:{s:02}.{subsecond:0width$}{quote}",
143    );
144}
145
146#[macro_export]
147macro_rules! number_to_month {
148    ($month:expr, $throw:expr $(,)?) => {
149        match $month {
150            1 => Month::January,
151            2 => Month::February,
152            3 => Month::March,
153            4 => Month::April,
154            5 => Month::May,
155            6 => Month::June,
156            7 => Month::July,
157            8 => Month::August,
158            9 => Month::September,
159            10 => Month::October,
160            11 => Month::November,
161            12 => Month::December,
162            _ => $throw,
163        }
164    };
165}
166
167#[macro_export]
168macro_rules! month_to_number {
169    ($month:expr $(,)?) => {
170        match $month {
171            Month::January => 1,
172            Month::February => 2,
173            Month::March => 3,
174            Month::April => 4,
175            Month::May => 5,
176            Month::June => 6,
177            Month::July => 7,
178            Month::August => 8,
179            Month::September => 9,
180            Month::October => 10,
181            Month::November => 11,
182            Month::December => 12,
183        }
184    };
185}
186
187#[macro_export]
188/// Conditionally wrap a generated fragment in parentheses.
189macro_rules! possibly_parenthesized {
190    ($out:ident, $cond:expr, $v:expr) => {
191        if $cond {
192            $out.push('(');
193            $v;
194            $out.push(')');
195        } else {
196            $v;
197        }
198    };
199}
200
201#[macro_export]
202/// Truncate long strings for logging and error messages purpose.
203///
204/// Returns a `format_args!` that yields at most 497 characters from the start
205/// of the input followed by `...` when truncation occurred. Minimal overhead.
206///
207/// # Examples
208/// ```ignore
209/// use tank_core::truncate_long;
210/// let short = "SELECT 1";
211/// assert_eq!(format!("{}", truncate_long!(short)), "SELECT 1\n");
212/// let long = format!("SELECT {}", "X".repeat(600));
213/// let logged = format!("{}", truncate_long!(long));
214/// assert!(logged.starts_with("SELECT XXXXXX"));
215/// assert!(logged.ends_with("...\n"));
216/// ```
217macro_rules! truncate_long {
218    ($query:expr) => {
219        format_args!(
220            "{}{}",
221            &$query[..::std::cmp::min($query.len(), 497)].trim(),
222            if $query.len() > 497 { "...\n" } else { "" },
223        )
224    };
225}
226
227/// Sends the value through the channel and logs in case of error.
228///
229/// Parameters:
230/// * `$tx`: sender channel
231/// * `$value`: value to be sent
232///
233/// *Example*:
234/// ```ignore
235/// send_value!(tx, Ok(QueryResult::Row(row)));
236/// ```
237
238#[macro_export]
239macro_rules! send_value {
240    ($tx:ident, $value:expr) => {{
241        if let Err(e) = $tx.send($value) {
242            log::error!("{e:#}");
243        }
244    }};
245}
246
247/// Incrementally accumulates tokens from a speculative parse stream until one
248/// of the supplied parsers succeeds.
249///
250/// Returns `(accumulated_tokens, (parser1_option, parser2_option, ...))` with
251/// exactly one `Some(T)`: the first successful parser.
252#[doc(hidden)]
253#[macro_export]
254macro_rules! take_until {
255    ($original:expr, $($parser:expr),+ $(,)?) => {{
256        let macro_local_input = $original.fork();
257        let mut macro_local_result = (
258            TokenStream::new(),
259            ($({
260                let _ = $parser;
261                None
262            }),+),
263        );
264        loop {
265            if macro_local_input.is_empty() {
266                break;
267            }
268            let mut parsed = false;
269            let produced = ($({
270                let attempt = macro_local_input.fork();
271                if let Ok(content) = ($parser)(&attempt) {
272                    macro_local_input.advance_to(&attempt);
273                    parsed = true;
274                    Some(content)
275                } else {
276                    None
277                }
278            }),+);
279            if parsed {
280                macro_local_result.1 = produced;
281                break;
282            }
283            macro_local_result.0.append(macro_local_input.parse::<TokenTree>()?);
284        }
285        $original.advance_to(&macro_local_input);
286        macro_local_result
287    }};
288}
289
290#[macro_export]
291/// Implement the `Executor` trait for a transaction wrapper type by
292/// delegating each operation to an underlying connection object.
293///
294/// This reduces boilerplate across driver implementations. The macro expands
295/// into an `impl Executor for $transaction<'c>` with forwarding methods for
296/// `prepare`, `run`, `fetch`, `execute`, and `append`.
297///
298/// Parameters:
299/// * `$driver`: concrete driver type.
300/// * `$transaction`: transaction wrapper type (generic over lifetime `'c`).
301/// * `$connection`: field name on the transaction pointing to the connection.
302///
303/// # Examples
304/// ```ignore
305/// use crate::{YourDBConnection, YourDBDriver};
306/// use tank_core::{Error, Result, Transaction, impl_executor_transaction};
307///
308/// pub struct YourDBTransaction<'c> {
309///     connection: &'c mut YourDBConnection,
310/// }
311///
312/// impl_executor_transaction!(YourDBDriver, YourDBTransaction<'c>, connection);
313///
314/// impl<'c> Transaction<'c> for YourDBTransaction<'c> { ... }
315/// ```
316macro_rules! impl_executor_transaction {
317    // Case 1: Lifetime is present (necessary for transactions)
318    ($driver:ty, $transaction:ident $(< $lt:lifetime >)?, $connection:ident) => {
319       impl $(<$lt>)? ::tank_core::Executor for $transaction $(<$lt>)? {
320            type Driver = $driver;
321
322            fn accepts_multiple_statements(&self) -> bool {
323                self.$connection.accepts_multiple_statements()
324            }
325
326            fn driver(&self) -> &Self::Driver {
327                self.$connection.driver()
328            }
329
330            fn prepare(
331                &mut self,
332                query: String,
333            ) -> impl Future<Output = ::tank_core::Result<::tank_core::Query<Self::Driver>>> + Send
334            {
335                self.$connection.prepare(query)
336            }
337
338            fn run<'s>(
339                &'s mut self,
340                query: impl ::tank_core::AsQuery<Self::Driver> + 's,
341            ) -> impl ::tank_core::stream::Stream<
342                Item = ::tank_core::Result<::tank_core::QueryResult>,
343            > + Send {
344                self.$connection.run(query)
345            }
346
347            fn fetch<'s>(
348                &'s mut self,
349                query: impl ::tank_core::AsQuery<Self::Driver> + 's,
350            ) -> impl ::tank_core::stream::Stream<
351                Item = ::tank_core::Result<::tank_core::RowLabeled>,
352            > + Send
353            + 's {
354                self.$connection.fetch(query)
355            }
356
357            fn execute<'s>(
358                &'s mut self,
359                query: impl ::tank_core::AsQuery<Self::Driver> + 's,
360            ) -> impl Future<Output = ::tank_core::Result<::tank_core::RowsAffected>> + Send {
361                self.$connection.execute(query)
362            }
363
364            fn append<'a, E, It>(
365                &mut self,
366                entities: It,
367            ) -> impl Future<Output = ::tank_core::Result<::tank_core::RowsAffected>> + Send
368            where
369                E: ::tank_core::Entity + 'a,
370                It: IntoIterator<Item = &'a E> + Send,
371                <It as IntoIterator>::IntoIter: Send,
372            {
373                self.$connection.append(entities)
374            }
375        }
376    }
377}