tank_core/
util.rs

1use proc_macro2::TokenStream;
2use quote::{ToTokens, quote};
3use std::{borrow::Cow, cmp::min, ffi::CString};
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 `Cow<T>` preserving borrowed vs owned status for generated code.
31pub fn quote_cow<T: ToOwned + ToTokens + ?Sized>(value: &Cow<T>) -> TokenStream
32where
33    <T as ToOwned>::Owned: ToTokens,
34{
35    match value {
36        Cow::Borrowed(v) => quote! { ::std::borrow::Cow::Borrowed(#v) },
37        Cow::Owned(v) => quote! { ::std::borrow::Cow::Borrowed(#v) },
38    }
39}
40
41/// Quote an `Option<T>` into tokens.
42pub fn quote_option<T: ToTokens>(value: &Option<T>) -> TokenStream {
43    match value {
44        None => quote! { None },
45        Some(v) => quote! { Some(#v) },
46    }
47}
48
49/// Determine if the trailing segments of a `syn::Path` match the expected identifiers.
50pub fn matches_path(path: &Path, expect: &[&str]) -> bool {
51    let len = min(path.segments.len(), expect.len());
52    path.segments
53        .iter()
54        .rev()
55        .take(len)
56        .map(|v| &v.ident)
57        .eq(expect.iter().rev().take(len))
58}
59
60/// Write an iterator of items separated by a delimiter into a string.
61pub fn separated_by<T, F>(
62    out: &mut String,
63    values: impl IntoIterator<Item = T>,
64    mut f: F,
65    separator: &str,
66) where
67    F: FnMut(&mut String, T),
68{
69    let mut len = out.len();
70    for v in values {
71        if out.len() > len {
72            out.push_str(separator);
73        }
74        len = out.len();
75        f(out, v);
76    }
77}
78
79/// Convenience wrapper converting into a `CString`, panicking on interior NUL.
80pub fn as_c_string<S: Into<Vec<u8>>>(str: S) -> CString {
81    CString::new(str.into()).expect("Expected a valid C string")
82}
83
84/// Consume a prefix of `input` while the predicate returns true, returning that slice.
85pub fn consume_while<'s>(input: &mut &'s str, predicate: impl FnMut(&char) -> bool) -> &'s str {
86    let len = input.chars().take_while(predicate).count();
87    if len == 0 {
88        return "";
89    }
90    let result = &input[..len];
91    *input = &input[len..];
92    result
93}
94
95#[macro_export]
96/// Conditionally wrap a generated fragment in parentheses.
97///
98/// This is used by SQL writers where certain precedence or grouping must be
99/// preserved only under specific syntactic circumstances (for example nested
100/// joins or composite boolean expressions). It evaluates `$v` exactly once,
101/// writing surrounding `(` `)` into the mutable output buffer `$out` iff
102/// `$cond` is true.
103///
104/// # Examples
105/// ```rust
106/// # use tank_core::possibly_parenthesized;
107/// let mut out = String::new();
108/// let cond = true;
109/// possibly_parenthesized!(out, cond, { out.push_str("A OR B"); });
110/// assert_eq!(out, "(A OR B)");
111/// ```
112macro_rules! possibly_parenthesized {
113    ($out:ident, $cond:expr, $v:expr) => {
114        if $cond {
115            $out.push('(');
116            $v;
117            $out.push(')');
118        } else {
119            $v;
120        }
121    };
122}
123
124#[macro_export]
125/// Truncate a long string (typically a SQL query) for logging and error
126/// messages purpose, preserving a newline terminator.
127///
128/// Returns a `format_args!` that yields at most 497 characters from the start
129/// of the input followed by `...` when truncation occurred. Chosen length keeps
130/// messages concise while retaining useful context near the beginning of a
131/// query. Trailing whitespace is trimmed.
132///
133/// # Examples
134/// ```rust
135/// # use tank_core::truncate_long;
136/// let short = "SELECT 1";
137/// assert_eq!(format!("{}", truncate_long!(short)), "SELECT 1\n");
138/// let long = format!("SELECT {}", "X".repeat(600));
139/// let logged = format!("{}", truncate_long!(long));
140/// assert!(logged.starts_with("SELECT "));
141/// assert!(logged.ends_with("...\n"));
142/// ```
143macro_rules! truncate_long {
144    ($query:expr) => {
145        format_args!(
146            "{}{}\n",
147            &$query[..::std::cmp::min($query.len(), 497)].trim_end(),
148            if $query.len() > 497 { "..." } else { "" },
149        )
150    };
151}
152
153/// Sends the value through the channel and logs in case of error.
154#[macro_export]
155macro_rules! send_value {
156    ($tx:ident, $value:expr) => {{
157        if let Err(e) = $tx.send($value) {
158            log::error!("{:#}", e);
159        }
160    }};
161}
162
163/// Incrementally accumulates tokens from a speculative parse stream until one
164/// of the supplied parsers succeeds.
165///
166/// Each `$parser` is invoked against a forked cursor so failures do not
167/// advance the main stream. When a parser succeeds its result is stored and
168/// the loop terminates. If none succeed before the input is exhausted, the
169/// macro returns all consumed tokens and a tuple of `None` results.
170///
171/// Returns `(accumulated_tokens, (parser1_result, parser2_result, ...))` with
172/// exactly one `Some(T)` (the first successful parser) or all `None` when no
173/// parser matched.
174///
175/// Useful for parsing sequences like `lhs JOIN rhs ON condition` where the
176/// boundary between components is context-sensitive.
177#[macro_export]
178macro_rules! take_until {
179    ($original:expr, $($parser:expr),+ $(,)?) => {{
180        let macro_local_input = $original.fork();
181        let mut macro_local_result = (
182            TokenStream::new(),
183            ($({
184                let _ = $parser;
185                None
186            }),+),
187        );
188        loop {
189            if macro_local_input.is_empty() {
190                break;
191            }
192            let mut parsed = false;
193            let produced = ($({
194                let attempt = macro_local_input.fork();
195                if let Ok(content) = ($parser)(&attempt) {
196                    macro_local_input.advance_to(&attempt);
197                    parsed = true;
198                    Some(content)
199                } else {
200                    None
201                }
202            }),+);
203            if parsed {
204                macro_local_result.1 = produced;
205                break;
206            }
207            macro_local_result.0.append(macro_local_input.parse::<TokenTree>()?);
208        }
209        $original.advance_to(&macro_local_input);
210        macro_local_result
211    }};
212}
213
214#[macro_export]
215/// Implement the `Executor` trait for a transaction wrapper type by
216/// delegating each operation to an underlying connection object.
217///
218/// This reduces boilerplate across driver implementations. The macro expands
219/// into an `impl Executor for $transaction<'c>` with forwarding methods for
220/// `prepare`, `run`, `fetch`, `execute`, and `append`.
221///
222/// Parameters:
223/// * `$driver`: concrete driver type.
224/// * `$transaction`: transaction wrapper type (generic over lifetime `'c`).
225/// * `$connection`: field name on the transaction pointing to the connection.
226///
227/// # Examples
228/// ```rust
229/// # use tank_core::impl_executor_transaction;
230/// # struct MyDriver;
231/// # struct Conn { /* ... */ }
232/// # struct Tx<'c> { connection: &'c Conn }
233/// // impl_executor_transaction!(MyDriver, Tx, connection);
234/// ```
235macro_rules! impl_executor_transaction {
236    ($driver:ty, $transaction:ident, $connection:ident) => {
237        impl<'c> ::tank_core::Executor for $transaction<'c> {
238            type Driver = $driver;
239
240            fn driver(&self) -> &Self::Driver {
241                self.$connection.driver()
242            }
243
244            fn prepare(
245                &mut self,
246                query: String,
247            ) -> impl Future<Output = ::tank_core::Result<::tank_core::Query<Self::Driver>>> + Send
248            {
249                self.$connection.prepare(query)
250            }
251
252            fn run(
253                &mut self,
254                query: ::tank_core::Query<Self::Driver>,
255            ) -> impl ::tank_core::stream::Stream<
256                Item = ::tank_core::Result<::tank_core::QueryResult>,
257            > + Send {
258                self.$connection.run(query)
259            }
260
261            fn fetch<'s>(
262                &'s mut self,
263                query: ::tank_core::Query<Self::Driver>,
264            ) -> impl ::tank_core::stream::Stream<
265                Item = ::tank_core::Result<::tank_core::RowLabeled>,
266            > + Send
267            + 's {
268                self.$connection.fetch(query)
269            }
270
271            fn execute(
272                &mut self,
273                query: ::tank_core::Query<Self::Driver>,
274            ) -> impl Future<Output = ::tank_core::Result<::tank_core::RowsAffected>> + Send {
275                self.$connection.execute(query)
276            }
277
278            fn append<'a, E, It>(
279                &mut self,
280                entities: It,
281            ) -> impl Future<Output = ::tank_core::Result<::tank_core::RowsAffected>> + Send
282            where
283                E: ::tank_core::Entity + 'a,
284                It: IntoIterator<Item = &'a E> + Send,
285            {
286                self.$connection.append(entities)
287            }
288        }
289    };
290}