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(¯o_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}