macro_toolset/
string.rs

1//! Useful [`StringExt`] utilities for [`crate::str_concat`] macros
2
3#[cfg(feature = "feat-string-ext-base64")]
4pub mod base64;
5pub mod externs;
6pub mod general;
7#[cfg(feature = "feat-string-ext-hex")]
8pub mod hex;
9pub mod number;
10#[cfg(feature = "feat-string-ext-rand")]
11pub mod rand;
12#[cfg(feature = "feat-string-ext-urlencoding")]
13pub mod urlencoding;
14
15#[cfg(feature = "feat-string-ext-base64")]
16pub use base64::b64_padding;
17#[cfg(feature = "feat-string-ext-hex")]
18// Re-export the `HexStr` type for convenience.
19pub use hex::HexStr;
20// Re-export the `NumStr` type for convenience.
21pub use number::NumStr;
22#[cfg(feature = "feat-string-ext-rand")]
23// Re-export the `RandHexStr`, `RandStr` type for convenience.
24pub use rand::{RandHexStr, RandStr};
25
26wrapper! {
27    #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
28    /// Wrapper over [`String`]
29    pub StringExt(String)
30}
31
32#[macro_export]
33/// Fast concat [`String`] / &[`str`] / number.
34///
35/// For details of params accepted, please refers to [`StringT`].
36///
37/// # Examples
38///
39/// ```rust
40/// # use macro_toolset::{str_concat, string::NumStr};
41/// # fn main() {
42/// // General usage
43/// assert_eq!(
44///     str_concat!(
45///         NumStr::hex_default(0xa_usize), // HexStr
46///         "b", // &str
47///         "c".to_string(), // String
48///         1u8, // single number
49///         '😀', // char
50///         '�' // char
51///     ), "abc1😀�"
52/// );
53/// // with initial string
54/// let mut str_initial = "abc".to_string();
55/// str_concat!(str = str_initial; "1", "😀", "�");
56/// assert_eq!(
57///    str_initial, "abc1😀�"
58/// );
59///
60/// // with capacity
61/// assert_eq!(
62///    str_concat!(cap = 10; "abc", "1", "😀", "�"), "abc1😀�"
63/// );
64///
65/// // with separator
66/// assert_eq!(
67///   str_concat!(sep = ","; "abc", "1", "😀", "�"), "abc,1,😀,�"
68/// );
69/// # }
70/// ```
71macro_rules! str_concat {
72    ($($x:expr),*) => {
73        {
74            use $crate::string::StringExtT;
75
76            ($($x,)*).to_string_ext()
77        }
78    };
79    (str = $string_initial:expr; $($x:expr),*) => {
80        {
81            use $crate::string::PushAnyT;
82
83            $(
84                $string_initial.push_any($x);
85            )*
86        }
87    };
88    (cap = $cap:expr; $($x:expr),*) => {
89        {
90            use $crate::string::PushAnyT;
91
92            let mut string_final = String::with_capacity($cap);
93
94            $(
95                string_final.push_any($x);
96            )*
97
98            string_final
99        }
100    };
101    (sep = $sep:expr; $($x:expr),*) => {
102        {
103            use $crate::string::StringExtT;
104
105            ($($x,)*).to_string_ext_with_separator($sep)
106        }
107    };
108}
109
110#[deprecated(since = "0.8.0", note = "Use `str_concat!` instead")]
111pub use crate::str_concat as str_concat_v2;
112use crate::wrapper;
113
114/// Remove the trailing separator from the string.
115///
116/// Notice: will not check if the separator exists or not!
117macro_rules! remove_separator_tailing {
118    ($string:expr, $separator:expr) => {
119        let current_len = $string.len();
120        if let Some(target_len) = current_len.checked_sub($separator.len()) {
121            $string.truncate(target_len);
122        }
123    };
124}
125
126/// Trait helper for push any string-like type to the string.
127pub trait PushAnyT {
128    /// Push any string-like type to the string.
129    fn push_any<V>(&mut self, value: V)
130    where
131        V: StringT;
132
133    /// Push any string-like type to the string with a separator.
134    fn push_any_with_separator<V>(&mut self, value: V, sep: &str)
135    where
136        V: StringT;
137}
138
139impl PushAnyT for String {
140    #[inline]
141    fn push_any<V>(&mut self, value: V)
142    where
143        V: StringT,
144    {
145        // safe because of the `StringT` trait
146        #[allow(unsafe_code)]
147        value.encode_to_buf(unsafe { self.as_mut_vec() });
148    }
149
150    #[inline]
151    fn push_any_with_separator<V>(&mut self, value: V, sep: &str)
152    where
153        V: StringT,
154    {
155        // safe because of the `StringT` trait
156        #[allow(unsafe_code)]
157        let inner = unsafe { self.as_mut_vec() };
158
159        value.encode_to_buf_with_separator(inner, sep);
160
161        // If is `None`?
162        remove_separator_tailing!(inner, sep);
163    }
164}
165
166impl PushAnyT for Vec<u8> {
167    #[inline]
168    fn push_any<V>(&mut self, value: V)
169    where
170        V: StringT,
171    {
172        value.encode_to_buf(self);
173    }
174
175    #[inline]
176    fn push_any_with_separator<V>(&mut self, value: V, sep: &str)
177    where
178        V: StringT,
179    {
180        value.encode_to_buf_with_separator(self, sep);
181        remove_separator_tailing!(self, sep);
182    }
183}
184
185impl PushAnyT for bytes::BytesMut {
186    #[inline]
187    fn push_any<V>(&mut self, value: V)
188    where
189        V: StringT,
190    {
191        value.encode_to_bytes_buf(self);
192    }
193
194    #[inline]
195    fn push_any_with_separator<V>(&mut self, value: V, sep: &str)
196    where
197        V: StringT,
198    {
199        value.encode_to_bytes_buf_with_separator(self, sep);
200        remove_separator_tailing!(self, sep);
201    }
202}
203
204/// Trait for string-like types.
205pub trait StringT {
206    /// Push the value to the string (the underlying `Vec<u8>`).
207    fn encode_to_buf(self, string: &mut Vec<u8>);
208
209    /// Push the value to the string (the underlying `Vec<u8>`) with a
210    /// separator.
211    ///
212    /// The will be a tailing separator.
213    fn encode_to_buf_with_separator(self, string: &mut Vec<u8>, separator: &str);
214
215    /// Push the value to the string (the underlying `bytes::BytesMut`).
216    fn encode_to_bytes_buf(self, string: &mut bytes::BytesMut);
217
218    /// Push the value to the string (the underlying `bytes::BytesMut`) with a
219    /// separator.
220    ///
221    /// The will be a tailing separator.
222    fn encode_to_bytes_buf_with_separator(self, string: &mut bytes::BytesMut, separator: &str);
223}
224
225#[allow(clippy::len_without_is_empty)]
226/// Trait for string-like types, but extended with some methods making it not
227/// dyn-compatible.
228pub trait StringExtT: StringT + Sized {
229    #[inline]
230    /// With prefix.
231    fn with_prefix<P: StringExtT + Copy>(self, prefix: P) -> impl StringExtT {
232        general::tuple::SeplessTuple {
233            inner: (prefix, self),
234        }
235    }
236
237    #[inline]
238    /// With suffix.
239    fn with_suffix<S: StringExtT + Copy>(self, suffix: S) -> impl StringExtT {
240        general::tuple::SeplessTuple {
241            inner: (self, suffix),
242        }
243    }
244
245    #[inline]
246    /// Encode the value to the string.
247    fn to_string_ext(self) -> String {
248        let mut string_buf = String::with_capacity(64);
249
250        string_buf.push_any(self);
251
252        string_buf
253    }
254
255    #[inline]
256    /// Encode the value(s) to the string with separator.
257    fn to_string_ext_with_separator(self, separator: &str) -> String {
258        let mut string_buf = String::with_capacity(64);
259
260        string_buf.push_any_with_separator(self, separator);
261
262        string_buf
263    }
264
265    #[inline]
266    #[cfg(feature = "feat-string-ext-http")]
267    /// Encode the value to the string as a HTTP header value.
268    fn to_http_header_value(self) -> Result<http::HeaderValue, http::header::InvalidHeaderValue> {
269        let mut buf = self.to_string_ext().into_bytes();
270
271        // Avoid allocation if possible.
272        buf.truncate(buf.len());
273
274        http::HeaderValue::from_maybe_shared(bytes::Bytes::from(buf))
275    }
276}
277
278// =============================================================================
279
280#[doc(hidden)]
281#[macro_export]
282macro_rules! impl_for_shared_ref {
283    (COPIED: $($ty:ty)*) => {
284        $(
285            impl StringT for &$ty {
286                #[inline]
287                fn encode_to_buf(self, string: &mut Vec<u8>) {
288                    (*self).encode_to_buf(string);
289                }
290
291                #[inline]
292                fn encode_to_buf_with_separator(self, string: &mut Vec<u8>, separator: &str) {
293                    (*self).encode_to_buf_with_separator(string, separator);
294                }
295
296                #[inline]
297                fn encode_to_bytes_buf(self, string: &mut bytes::BytesMut) {
298                    (*self).encode_to_bytes_buf(string);
299                }
300
301                #[inline]
302                fn encode_to_bytes_buf_with_separator(self, string: &mut bytes::BytesMut, separator: &str) {
303                    (*self).encode_to_bytes_buf_with_separator(string, separator);
304                }
305            }
306
307            impl StringExtT for &$ty {}
308
309            impl StringT for &mut $ty {
310                #[inline]
311                fn encode_to_buf(self, string: &mut Vec<u8>) {
312                    (*self).encode_to_buf(string);
313                }
314
315                #[inline]
316                fn encode_to_buf_with_separator(self, string: &mut Vec<u8>, separator: &str) {
317                    (*self).encode_to_buf_with_separator(string, separator);
318                }
319
320                #[inline]
321                fn encode_to_bytes_buf(self, string: &mut bytes::BytesMut) {
322                    (*self).encode_to_bytes_buf(string);
323                }
324
325                #[inline]
326                fn encode_to_bytes_buf_with_separator(self, string: &mut bytes::BytesMut, separator: &str) {
327                    (*self).encode_to_bytes_buf_with_separator(string, separator);
328                }
329            }
330
331            impl StringExtT for &mut $ty {}
332
333            impl StringT for &&$ty {
334                #[inline]
335                fn encode_to_buf(self, string: &mut Vec<u8>) {
336                    (**self).encode_to_buf(string);
337                }
338
339                #[inline]
340                fn encode_to_buf_with_separator(self, string: &mut Vec<u8>, separator: &str) {
341                    (**self).encode_to_buf_with_separator(string, separator);
342                }
343
344                #[inline]
345                fn encode_to_bytes_buf(self, string: &mut bytes::BytesMut) {
346                    (**self).encode_to_bytes_buf(string);
347                }
348
349                #[inline]
350                fn encode_to_bytes_buf_with_separator(self, string: &mut bytes::BytesMut, separator: &str) {
351                    (**self).encode_to_bytes_buf_with_separator(string, separator);
352                }
353            }
354
355            impl StringExtT for &&$ty {}
356
357            impl StringT for &mut &$ty {
358                #[inline]
359                fn encode_to_buf(self, string: &mut Vec<u8>) {
360                    (**self).encode_to_buf(string);
361                }
362
363                #[inline]
364                fn encode_to_buf_with_separator(self, string: &mut Vec<u8>, separator: &str) {
365                    (**self).encode_to_buf_with_separator(string, separator);
366                }
367
368                #[inline]
369                fn encode_to_bytes_buf(self, string: &mut bytes::BytesMut) {
370                    (**self).encode_to_bytes_buf(string);
371                }
372
373                #[inline]
374                fn encode_to_bytes_buf_with_separator(self, string: &mut bytes::BytesMut, separator: &str) {
375                    (**self).encode_to_bytes_buf_with_separator(string, separator);
376                }
377            }
378
379            impl StringExtT for &mut &$ty {}
380
381            impl StringT for &&mut $ty {
382                #[inline]
383                fn encode_to_buf(self, string: &mut Vec<u8>) {
384                    (**self).encode_to_buf(string);
385                }
386
387                #[inline]
388                fn encode_to_buf_with_separator(self, string: &mut Vec<u8>, separator: &str) {
389                    (**self).encode_to_buf_with_separator(string, separator);
390                }
391
392                #[inline]
393                fn encode_to_bytes_buf(self, string: &mut bytes::BytesMut) {
394                    (**self).encode_to_bytes_buf(string);
395                }
396
397                #[inline]
398                fn encode_to_bytes_buf_with_separator(self, string: &mut bytes::BytesMut, separator: &str) {
399                    (**self).encode_to_bytes_buf_with_separator(string, separator);
400                }
401            }
402
403            impl StringExtT for &&mut $ty {}
404
405            impl StringT for &&&$ty {
406                #[inline]
407                fn encode_to_buf(self, string: &mut Vec<u8>) {
408                    (***self).encode_to_buf(string);
409                }
410
411                #[inline]
412                fn encode_to_buf_with_separator(self, string: &mut Vec<u8>, separator: &str) {
413                    (***self).encode_to_buf_with_separator(string, separator);
414                }
415
416                #[inline]
417                fn encode_to_bytes_buf(self, string: &mut bytes::BytesMut) {
418                    (***self).encode_to_bytes_buf(string);
419                }
420
421                #[inline]
422                fn encode_to_bytes_buf_with_separator(self, string: &mut bytes::BytesMut, separator: &str) {
423                    (***self).encode_to_bytes_buf_with_separator(string, separator);
424                }
425            }
426
427            impl StringExtT for &&&$ty {}
428        )*
429    };
430    (REF: $($ge:ident => $ty:ty)*) => {
431        $(
432            impl<$ge> StringT for $ty
433            where
434                for <'a> &'a $ge: StringT,
435            {
436                #[inline]
437                fn encode_to_buf(self, string: &mut Vec<u8>) {
438                    (&*self).encode_to_buf(string);
439                }
440
441                #[inline]
442                fn encode_to_buf_with_separator(self, string: &mut Vec<u8>, separator: &str) {
443                    (&*self).encode_to_buf_with_separator(string, separator);
444                }
445
446                #[inline]
447                fn encode_to_bytes_buf(self, string: &mut bytes::BytesMut) {
448                    (&*self).encode_to_bytes_buf(string);
449                }
450
451                #[inline]
452                fn encode_to_bytes_buf_with_separator(self, string: &mut bytes::BytesMut, separator: &str) {
453                    (&*self).encode_to_bytes_buf_with_separator(string, separator);
454                }
455            }
456
457            impl<$ge> StringExtT for $ty
458            where
459                for <'a> &'a $ge: StringExtT,
460            {}
461        )*
462    };
463}
464
465#[macro_export]
466#[doc(hidden)]
467/// impl_for_wrapper
468macro_rules! impl_for_wrapper {
469    (STRING_T: $($tt:tt)*) => {
470        $($tt)* {
471            #[inline]
472            fn encode_to_buf(self, string: &mut Vec<u8>) {
473                (&*self.inner).encode_to_buf(string);
474            }
475
476            #[inline]
477            fn encode_to_buf_with_separator(self, string: &mut Vec<u8>, separator: &str) {
478                (&*self.inner).encode_to_buf_with_separator(string, separator);
479            }
480
481            #[inline]
482            fn encode_to_bytes_buf(self, string: &mut bytes::BytesMut) {
483                (&*self.inner).encode_to_bytes_buf(string);
484            }
485
486            #[inline]
487            fn encode_to_bytes_buf_with_separator(self, string: &mut bytes::BytesMut, separator: &str) {
488                (&*self.inner).encode_to_bytes_buf_with_separator(string, separator);
489            }
490        }
491    };
492    (STRING_EXT_T: $($tt:tt)*) => {
493        $($tt)* {}
494    }
495}
496
497#[cfg(test)]
498#[allow(unused)]
499mod tests {
500    use super::*;
501
502    // Check dyn capable.
503    type BoxedString = Box<dyn StringT>;
504
505    #[test]
506    fn test_prefix_or_suffix() {
507        let exp1 = "world".with_prefix("hello");
508        assert_eq!(exp1.to_string_ext(), "helloworld");
509
510        let exp2 = str_concat!(sep = " "; ("hello", "world"));
511        assert_eq!(exp2, "hello world");
512
513        // dbg!(None::<()>
514        //     .with_suffix("-suffix")
515        //     .with_prefix("prefix-")
516        //     .to_string_ext());
517
518        let exp3 = str_concat!(
519            sep = " ";
520            None::<()>.with_prefix("prefix-")
521        );
522
523        assert_eq!(exp3, "");
524
525        let exp4 = str_concat!(
526            sep = " ";
527            "data",
528            None::<()>.with_prefix("prefix-"),
529            None::<()>,
530            None::<()>
531        );
532
533        assert_eq!(exp4, "data");
534
535        let exp5 = str_concat!(
536            sep = " ";
537            (None::<()>.with_prefix("prefix-"), None::<()>.with_prefix("prefix-")),
538            ("hello", "world"),
539            "hello".with_suffix(Some("-suffix")),
540            None::<()>.with_prefix("prefix-"),
541            "3hello".with_suffix(None::<()>).with_prefix(None::<()>),
542            None::<()>.with_prefix("prefix-").with_suffix("-suffix")
543        );
544
545        assert_eq!(exp5, "hello world hello-suffix 3hello");
546
547        let exp6 = str_concat!(
548            sep = "&";
549            [1, 2, 3].with_prefix("post_ids[]=")
550        );
551
552        assert_eq!(exp6, "post_ids[]=1&post_ids[]=2&post_ids[]=3")
553    }
554
555    #[test]
556    fn test_separator() {
557        let mut string = String::new();
558
559        string.push_any_with_separator(["b", "c"], ",");
560        assert_eq!(string, "b,c");
561        string.clear();
562
563        string.push_any(("a", "b", "c"));
564        assert_eq!(string, "abc");
565
566        string.push_any_with_separator(("a", "b", "c"), ",");
567        assert_eq!(string, "abca,b,c");
568
569        string.push_any_with_separator(("a", "b", "c", ("b", "c")), ",");
570        assert_eq!(string, "abca,b,ca,b,c,b,c");
571
572        string.push_any_with_separator(
573            (
574                &&&"a",
575                vec!["b"],
576                ("c"),
577                ["b", "c"],
578                "d".with_prefix("prefix-"),
579                "e".with_suffix("-suffix"),
580                "f".with_prefix("2prefix-").with_suffix("-suffix2"),
581                1u8,
582            ),
583            ",",
584        );
585        assert_eq!(
586            string,
587            "abca,b,ca,b,c,b,ca,b,c,b,c,prefix-d,e-suffix,2prefix-f-suffix2,1"
588        );
589    }
590}