reth_rpc_server_types/
module.rs

1use std::{collections::HashSet, fmt, str::FromStr};
2
3use serde::{Deserialize, Serialize, Serializer};
4use strum::{AsRefStr, EnumIter, IntoStaticStr, ParseError, VariantArray, VariantNames};
5
6/// Describes the modules that should be installed.
7///
8/// # Example
9///
10/// Create a [`RpcModuleSelection`] from a selection.
11///
12/// ```
13/// use reth_rpc_server_types::{RethRpcModule, RpcModuleSelection};
14/// let config: RpcModuleSelection = vec![RethRpcModule::Eth].into();
15/// ```
16#[derive(Debug, Default, Clone, Eq, PartialEq)]
17pub enum RpcModuleSelection {
18    /// Use _all_ available modules.
19    All,
20    /// The default modules `eth`, `net`, `web3`
21    #[default]
22    Standard,
23    /// Only use the configured modules.
24    Selection(HashSet<RethRpcModule>),
25}
26
27// === impl RpcModuleSelection ===
28
29impl RpcModuleSelection {
30    /// The standard modules to instantiate by default `eth`, `net`, `web3`
31    pub const STANDARD_MODULES: [RethRpcModule; 3] =
32        [RethRpcModule::Eth, RethRpcModule::Net, RethRpcModule::Web3];
33
34    /// Returns a selection of [`RethRpcModule`] with all [`RethRpcModule::all_variants`].
35    pub fn all_modules() -> HashSet<RethRpcModule> {
36        RethRpcModule::modules().into_iter().collect()
37    }
38
39    /// Returns the [`RpcModuleSelection::STANDARD_MODULES`] as a selection.
40    pub fn standard_modules() -> HashSet<RethRpcModule> {
41        HashSet::from(Self::STANDARD_MODULES)
42    }
43
44    /// All modules that are available by default on IPC.
45    ///
46    /// By default all modules are available on IPC.
47    pub fn default_ipc_modules() -> HashSet<RethRpcModule> {
48        Self::all_modules()
49    }
50
51    /// Creates a new _unique_ [`RpcModuleSelection::Selection`] from the given items.
52    ///
53    /// # Note
54    ///
55    /// This will dedupe the selection and remove duplicates while preserving the order.
56    ///
57    /// # Example
58    ///
59    /// Create a selection from the [`RethRpcModule`] string identifiers
60    ///
61    /// ```
62    /// use reth_rpc_server_types::{RethRpcModule, RpcModuleSelection};
63    /// let selection = vec!["eth", "admin"];
64    /// let config = RpcModuleSelection::try_from_selection(selection).unwrap();
65    /// assert_eq!(config, RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]));
66    /// ```
67    ///
68    /// Create a unique selection from the [`RethRpcModule`] string identifiers
69    ///
70    /// ```
71    /// use reth_rpc_server_types::{RethRpcModule, RpcModuleSelection};
72    /// let selection = vec!["eth", "admin", "eth", "admin"];
73    /// let config = RpcModuleSelection::try_from_selection(selection).unwrap();
74    /// assert_eq!(config, RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]));
75    /// ```
76    pub fn try_from_selection<I, T>(selection: I) -> Result<Self, T::Error>
77    where
78        I: IntoIterator<Item = T>,
79        T: TryInto<RethRpcModule>,
80    {
81        selection.into_iter().map(TryInto::try_into).collect()
82    }
83
84    /// Returns the number of modules in the selection
85    pub fn len(&self) -> usize {
86        match self {
87            Self::All => RethRpcModule::variant_count(),
88            Self::Standard => Self::STANDARD_MODULES.len(),
89            Self::Selection(s) => s.len(),
90        }
91    }
92
93    /// Returns true if no selection is configured
94    pub fn is_empty(&self) -> bool {
95        match self {
96            Self::Selection(sel) => sel.is_empty(),
97            _ => false,
98        }
99    }
100
101    /// Returns true if all modules are selected
102    pub const fn is_all(&self) -> bool {
103        matches!(self, Self::All)
104    }
105
106    /// Returns an iterator over all configured [`RethRpcModule`]
107    pub fn iter_selection(&self) -> Box<dyn Iterator<Item = RethRpcModule> + '_> {
108        match self {
109            Self::All => Box::new(RethRpcModule::modules().into_iter()),
110            Self::Standard => Box::new(Self::STANDARD_MODULES.iter().copied()),
111            Self::Selection(s) => Box::new(s.iter().copied()),
112        }
113    }
114
115    /// Clones the set of configured [`RethRpcModule`].
116    pub fn to_selection(&self) -> HashSet<RethRpcModule> {
117        match self {
118            Self::All => Self::all_modules(),
119            Self::Standard => Self::standard_modules(),
120            Self::Selection(s) => s.clone(),
121        }
122    }
123
124    /// Converts the selection into a [`HashSet`].
125    pub fn into_selection(self) -> HashSet<RethRpcModule> {
126        match self {
127            Self::All => Self::all_modules(),
128            Self::Standard => Self::standard_modules(),
129            Self::Selection(s) => s,
130        }
131    }
132
133    /// Returns true if both selections are identical.
134    pub fn are_identical(http: Option<&Self>, ws: Option<&Self>) -> bool {
135        match (http, ws) {
136            // Shortcut for common case to avoid iterating later
137            (Some(Self::All), Some(other)) | (Some(other), Some(Self::All)) => {
138                other.len() == RethRpcModule::variant_count()
139            }
140
141            // If either side is disabled, then the other must be empty
142            (Some(some), None) | (None, Some(some)) => some.is_empty(),
143
144            (Some(http), Some(ws)) => http.to_selection() == ws.to_selection(),
145            (None, None) => true,
146        }
147    }
148
149    /// Returns true if the selection contains the given module.
150    pub fn contains(&self, module: &RethRpcModule) -> bool {
151        match self {
152            Self::All => true,
153            Self::Standard => Self::STANDARD_MODULES.contains(module),
154            Self::Selection(s) => s.contains(module),
155        }
156    }
157
158    /// Adds a module to the selection.
159    ///
160    /// If the selection is `All`, this is a no-op.
161    /// Otherwise, converts to a `Selection` and adds the module.
162    pub fn push(&mut self, module: RethRpcModule) {
163        if !self.is_all() {
164            let mut modules = self.to_selection();
165            modules.insert(module);
166            *self = Self::Selection(modules);
167        }
168    }
169
170    /// Returns a new selection with the given module added.
171    ///
172    /// If the selection is `All`, returns `All`.
173    /// Otherwise, converts to a `Selection` and adds the module.
174    pub fn append(self, module: RethRpcModule) -> Self {
175        if self.is_all() {
176            Self::All
177        } else {
178            let mut modules = self.into_selection();
179            modules.insert(module);
180            Self::Selection(modules)
181        }
182    }
183
184    /// Extends the selection with modules from an iterator.
185    ///
186    /// If the selection is `All`, this is a no-op.
187    /// Otherwise, converts to a `Selection` and adds the modules.
188    pub fn extend<I>(&mut self, iter: I)
189    where
190        I: IntoIterator<Item = RethRpcModule>,
191    {
192        if !self.is_all() {
193            let mut modules = self.to_selection();
194            modules.extend(iter);
195            *self = Self::Selection(modules);
196        }
197    }
198
199    /// Returns a new selection with modules from an iterator added.
200    ///
201    /// If the selection is `All`, returns `All`.
202    /// Otherwise, converts to a `Selection` and adds the modules.
203    pub fn extended<I>(self, iter: I) -> Self
204    where
205        I: IntoIterator<Item = RethRpcModule>,
206    {
207        if self.is_all() {
208            Self::All
209        } else {
210            let mut modules = self.into_selection();
211            modules.extend(iter);
212            Self::Selection(modules)
213        }
214    }
215}
216
217impl From<&HashSet<RethRpcModule>> for RpcModuleSelection {
218    fn from(s: &HashSet<RethRpcModule>) -> Self {
219        Self::from(s.clone())
220    }
221}
222
223impl From<HashSet<RethRpcModule>> for RpcModuleSelection {
224    fn from(s: HashSet<RethRpcModule>) -> Self {
225        Self::Selection(s)
226    }
227}
228
229impl From<&[RethRpcModule]> for RpcModuleSelection {
230    fn from(s: &[RethRpcModule]) -> Self {
231        Self::Selection(s.iter().copied().collect())
232    }
233}
234
235impl From<Vec<RethRpcModule>> for RpcModuleSelection {
236    fn from(s: Vec<RethRpcModule>) -> Self {
237        Self::Selection(s.into_iter().collect())
238    }
239}
240
241impl<const N: usize> From<[RethRpcModule; N]> for RpcModuleSelection {
242    fn from(s: [RethRpcModule; N]) -> Self {
243        Self::Selection(s.iter().copied().collect())
244    }
245}
246
247impl<'a> FromIterator<&'a RethRpcModule> for RpcModuleSelection {
248    fn from_iter<I>(iter: I) -> Self
249    where
250        I: IntoIterator<Item = &'a RethRpcModule>,
251    {
252        iter.into_iter().copied().collect()
253    }
254}
255
256impl FromIterator<RethRpcModule> for RpcModuleSelection {
257    fn from_iter<I>(iter: I) -> Self
258    where
259        I: IntoIterator<Item = RethRpcModule>,
260    {
261        Self::Selection(iter.into_iter().collect())
262    }
263}
264
265impl FromStr for RpcModuleSelection {
266    type Err = ParseError;
267
268    fn from_str(s: &str) -> Result<Self, Self::Err> {
269        if s.is_empty() {
270            return Ok(Self::Selection(Default::default()))
271        }
272        let mut modules = s.split(',').map(str::trim).peekable();
273        let first = modules.peek().copied().ok_or(ParseError::VariantNotFound)?;
274        // We convert to lowercase to make the comparison case-insensitive
275        //
276        // This is a way to allow typing "all" and "ALL" and "All" and "aLl" etc.
277        match first.to_lowercase().as_str() {
278            "all" => Ok(Self::All),
279            "none" => Ok(Self::Selection(Default::default())),
280            _ => Self::try_from_selection(modules),
281        }
282    }
283}
284
285impl fmt::Display for RpcModuleSelection {
286    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287        write!(
288            f,
289            "[{}]",
290            self.iter_selection().map(|s| s.to_string()).collect::<Vec<_>>().join(", ")
291        )
292    }
293}
294
295/// Represents RPC modules that are supported by reth
296#[derive(
297    Debug,
298    Clone,
299    Copy,
300    Eq,
301    PartialEq,
302    Hash,
303    AsRefStr,
304    IntoStaticStr,
305    VariantNames,
306    VariantArray,
307    EnumIter,
308    Deserialize,
309)]
310#[serde(rename_all = "snake_case")]
311#[strum(serialize_all = "kebab-case")]
312pub enum RethRpcModule {
313    /// `admin_` module
314    Admin,
315    /// `debug_` module
316    Debug,
317    /// `eth_` module
318    Eth,
319    /// `net_` module
320    Net,
321    /// `trace_` module
322    Trace,
323    /// `txpool_` module
324    Txpool,
325    /// `web3_` module
326    Web3,
327    /// `rpc_` module
328    Rpc,
329    /// `reth_` module
330    Reth,
331    /// `ots_` module
332    Ots,
333    /// `flashbots_` module
334    Flashbots,
335    /// `miner_` module
336    Miner,
337    /// `mev_` module
338    Mev,
339}
340
341// === impl RethRpcModule ===
342
343impl RethRpcModule {
344    /// Returns the number of variants in the enum
345    pub const fn variant_count() -> usize {
346        <Self as VariantArray>::VARIANTS.len()
347    }
348
349    /// Returns all variant names of the enum
350    pub const fn all_variant_names() -> &'static [&'static str] {
351        <Self as VariantNames>::VARIANTS
352    }
353
354    /// Returns all variants of the enum
355    pub const fn all_variants() -> &'static [Self] {
356        <Self as VariantArray>::VARIANTS
357    }
358
359    /// Returns all variants of the enum
360    pub fn modules() -> impl IntoIterator<Item = Self> {
361        use strum::IntoEnumIterator;
362        Self::iter()
363    }
364
365    /// Returns the string representation of the module.
366    #[inline]
367    pub fn as_str(&self) -> &'static str {
368        self.into()
369    }
370}
371
372impl FromStr for RethRpcModule {
373    type Err = ParseError;
374
375    fn from_str(s: &str) -> Result<Self, Self::Err> {
376        Ok(match s {
377            "admin" => Self::Admin,
378            "debug" => Self::Debug,
379            "eth" => Self::Eth,
380            "net" => Self::Net,
381            "trace" => Self::Trace,
382            "txpool" => Self::Txpool,
383            "web3" => Self::Web3,
384            "rpc" => Self::Rpc,
385            "reth" => Self::Reth,
386            "ots" => Self::Ots,
387            "flashbots" => Self::Flashbots,
388            "miner" => Self::Miner,
389            "mev" => Self::Mev,
390            _ => return Err(ParseError::VariantNotFound),
391        })
392    }
393}
394
395impl TryFrom<&str> for RethRpcModule {
396    type Error = ParseError;
397    fn try_from(s: &str) -> Result<Self, <Self as TryFrom<&str>>::Error> {
398        FromStr::from_str(s)
399    }
400}
401
402impl fmt::Display for RethRpcModule {
403    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
404        f.pad(self.as_ref())
405    }
406}
407
408impl Serialize for RethRpcModule {
409    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
410    where
411        S: Serializer,
412    {
413        s.serialize_str(self.as_ref())
414    }
415}
416
417#[cfg(test)]
418mod test {
419    use super::*;
420
421    #[test]
422    fn test_all_modules() {
423        let all_modules = RpcModuleSelection::all_modules();
424        assert_eq!(all_modules.len(), RethRpcModule::variant_count());
425    }
426
427    #[test]
428    fn test_standard_modules() {
429        let standard_modules = RpcModuleSelection::standard_modules();
430        let expected_modules: HashSet<RethRpcModule> =
431            HashSet::from([RethRpcModule::Eth, RethRpcModule::Net, RethRpcModule::Web3]);
432        assert_eq!(standard_modules, expected_modules);
433    }
434
435    #[test]
436    fn test_default_ipc_modules() {
437        let default_ipc_modules = RpcModuleSelection::default_ipc_modules();
438        assert_eq!(default_ipc_modules, RpcModuleSelection::all_modules());
439    }
440
441    #[test]
442    fn test_try_from_selection_success() {
443        let selection = vec!["eth", "admin"];
444        let config = RpcModuleSelection::try_from_selection(selection).unwrap();
445        assert_eq!(config, RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]));
446    }
447
448    #[test]
449    fn test_rpc_module_selection_len() {
450        let all_modules = RpcModuleSelection::All;
451        let standard = RpcModuleSelection::Standard;
452        let selection = RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]);
453
454        assert_eq!(all_modules.len(), RethRpcModule::variant_count());
455        assert_eq!(standard.len(), 3);
456        assert_eq!(selection.len(), 2);
457    }
458
459    #[test]
460    fn test_rpc_module_selection_is_empty() {
461        let empty_selection = RpcModuleSelection::from(HashSet::new());
462        assert!(empty_selection.is_empty());
463
464        let non_empty_selection = RpcModuleSelection::from([RethRpcModule::Eth]);
465        assert!(!non_empty_selection.is_empty());
466    }
467
468    #[test]
469    fn test_rpc_module_selection_iter_selection() {
470        let all_modules = RpcModuleSelection::All;
471        let standard = RpcModuleSelection::Standard;
472        let selection = RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]);
473
474        assert_eq!(all_modules.iter_selection().count(), RethRpcModule::variant_count());
475        assert_eq!(standard.iter_selection().count(), 3);
476        assert_eq!(selection.iter_selection().count(), 2);
477    }
478
479    #[test]
480    fn test_rpc_module_selection_to_selection() {
481        let all_modules = RpcModuleSelection::All;
482        let standard = RpcModuleSelection::Standard;
483        let selection = RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]);
484
485        assert_eq!(all_modules.to_selection(), RpcModuleSelection::all_modules());
486        assert_eq!(standard.to_selection(), RpcModuleSelection::standard_modules());
487        assert_eq!(
488            selection.to_selection(),
489            HashSet::from([RethRpcModule::Eth, RethRpcModule::Admin])
490        );
491    }
492
493    #[test]
494    fn test_rpc_module_selection_are_identical() {
495        // Test scenario: both selections are `All`
496        //
497        // Since both selections include all possible RPC modules, they should be considered
498        // identical.
499        let all_modules = RpcModuleSelection::All;
500        assert!(RpcModuleSelection::are_identical(Some(&all_modules), Some(&all_modules)));
501
502        // Test scenario: both `http` and `ws` are `None`
503        //
504        // When both arguments are `None`, the function should return `true` because no modules are
505        // selected.
506        assert!(RpcModuleSelection::are_identical(None, None));
507
508        // Test scenario: both selections contain identical sets of specific modules
509        //
510        // In this case, both selections contain the same modules (`Eth` and `Admin`),
511        // so they should be considered identical.
512        let selection1 = RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]);
513        let selection2 = RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]);
514        assert!(RpcModuleSelection::are_identical(Some(&selection1), Some(&selection2)));
515
516        // Test scenario: one selection is `All`, the other is `Standard`
517        //
518        // `All` includes all possible modules, while `Standard` includes a specific set of modules.
519        // Since `Standard` does not cover all modules, these two selections should not be
520        // considered identical.
521        let standard = RpcModuleSelection::Standard;
522        assert!(!RpcModuleSelection::are_identical(Some(&all_modules), Some(&standard)));
523
524        // Test scenario: one is `None`, the other is an empty selection
525        //
526        // When one selection is `None` and the other is an empty selection (no modules),
527        // they should be considered identical because neither selects any modules.
528        let empty_selection = RpcModuleSelection::Selection(HashSet::new());
529        assert!(RpcModuleSelection::are_identical(None, Some(&empty_selection)));
530        assert!(RpcModuleSelection::are_identical(Some(&empty_selection), None));
531
532        // Test scenario: one is `None`, the other is a non-empty selection
533        //
534        // If one selection is `None` and the other contains modules, they should not be considered
535        // identical because `None` represents no selection, while the other explicitly
536        // selects modules.
537        let non_empty_selection = RpcModuleSelection::from([RethRpcModule::Eth]);
538        assert!(!RpcModuleSelection::are_identical(None, Some(&non_empty_selection)));
539        assert!(!RpcModuleSelection::are_identical(Some(&non_empty_selection), None));
540
541        // Test scenario: `All` vs. non-full selection
542        //
543        // If one selection is `All` (which includes all modules) and the other contains only a
544        // subset of modules, they should not be considered identical.
545        let partial_selection = RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Net]);
546        assert!(!RpcModuleSelection::are_identical(Some(&all_modules), Some(&partial_selection)));
547
548        // Test scenario: full selection vs `All`
549        //
550        // If the other selection explicitly selects all available modules, it should be identical
551        // to `All`.
552        let full_selection =
553            RpcModuleSelection::from(RethRpcModule::modules().into_iter().collect::<HashSet<_>>());
554        assert!(RpcModuleSelection::are_identical(Some(&all_modules), Some(&full_selection)));
555
556        // Test scenario: different non-empty selections
557        //
558        // If the two selections contain different sets of modules, they should not be considered
559        // identical.
560        let selection3 = RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Net]);
561        let selection4 = RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Web3]);
562        assert!(!RpcModuleSelection::are_identical(Some(&selection3), Some(&selection4)));
563
564        // Test scenario: `Standard` vs an equivalent selection
565        // The `Standard` selection includes a predefined set of modules. If we explicitly create
566        // a selection with the same set of modules, they should be considered identical.
567        let matching_standard =
568            RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Net, RethRpcModule::Web3]);
569        assert!(RpcModuleSelection::are_identical(Some(&standard), Some(&matching_standard)));
570
571        // Test scenario: `Standard` vs non-matching selection
572        //
573        // If the selection does not match the modules included in `Standard`, they should not be
574        // considered identical.
575        let non_matching_standard =
576            RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Net]);
577        assert!(!RpcModuleSelection::are_identical(Some(&standard), Some(&non_matching_standard)));
578    }
579
580    #[test]
581    fn test_rpc_module_selection_append() {
582        // Test append on Standard selection
583        let selection = RpcModuleSelection::Standard;
584        let new_selection = selection.append(RethRpcModule::Admin);
585        assert!(new_selection.contains(&RethRpcModule::Eth));
586        assert!(new_selection.contains(&RethRpcModule::Net));
587        assert!(new_selection.contains(&RethRpcModule::Web3));
588        assert!(new_selection.contains(&RethRpcModule::Admin));
589
590        // Test append on empty Selection
591        let selection = RpcModuleSelection::Selection(HashSet::new());
592        let new_selection = selection.append(RethRpcModule::Eth);
593        assert!(new_selection.contains(&RethRpcModule::Eth));
594        assert_eq!(new_selection.len(), 1);
595
596        // Test append on All (should return All)
597        let selection = RpcModuleSelection::All;
598        let new_selection = selection.append(RethRpcModule::Eth);
599        assert_eq!(new_selection, RpcModuleSelection::All);
600    }
601
602    #[test]
603    fn test_rpc_module_selection_extend() {
604        // Test extend on Standard selection
605        let mut selection = RpcModuleSelection::Standard;
606        selection.extend(vec![RethRpcModule::Admin, RethRpcModule::Debug]);
607        assert!(selection.contains(&RethRpcModule::Eth));
608        assert!(selection.contains(&RethRpcModule::Net));
609        assert!(selection.contains(&RethRpcModule::Web3));
610        assert!(selection.contains(&RethRpcModule::Admin));
611        assert!(selection.contains(&RethRpcModule::Debug));
612
613        // Test extend on empty Selection
614        let mut selection = RpcModuleSelection::Selection(HashSet::new());
615        selection.extend(vec![RethRpcModule::Eth, RethRpcModule::Admin]);
616        assert!(selection.contains(&RethRpcModule::Eth));
617        assert!(selection.contains(&RethRpcModule::Admin));
618        assert_eq!(selection.len(), 2);
619
620        // Test extend on All (should be no-op)
621        let mut selection = RpcModuleSelection::All;
622        selection.extend(vec![RethRpcModule::Eth, RethRpcModule::Admin]);
623        assert_eq!(selection, RpcModuleSelection::All);
624    }
625
626    #[test]
627    fn test_rpc_module_selection_from_str() {
628        // Test empty string returns default selection
629        let result = RpcModuleSelection::from_str("");
630        assert!(result.is_ok());
631        assert_eq!(result.unwrap(), RpcModuleSelection::Selection(Default::default()));
632
633        // Test "all" (case insensitive) returns All variant
634        let result = RpcModuleSelection::from_str("all");
635        assert!(result.is_ok());
636        assert_eq!(result.unwrap(), RpcModuleSelection::All);
637
638        let result = RpcModuleSelection::from_str("All");
639        assert!(result.is_ok());
640        assert_eq!(result.unwrap(), RpcModuleSelection::All);
641
642        let result = RpcModuleSelection::from_str("ALL");
643        assert!(result.is_ok());
644        assert_eq!(result.unwrap(), RpcModuleSelection::All);
645
646        // Test "none" (case insensitive) returns empty selection
647        let result = RpcModuleSelection::from_str("none");
648        assert!(result.is_ok());
649        assert_eq!(result.unwrap(), RpcModuleSelection::Selection(Default::default()));
650
651        let result = RpcModuleSelection::from_str("None");
652        assert!(result.is_ok());
653        assert_eq!(result.unwrap(), RpcModuleSelection::Selection(Default::default()));
654
655        let result = RpcModuleSelection::from_str("NONE");
656        assert!(result.is_ok());
657        assert_eq!(result.unwrap(), RpcModuleSelection::Selection(Default::default()));
658
659        // Test valid selections: "eth,admin"
660        let result = RpcModuleSelection::from_str("eth,admin");
661        assert!(result.is_ok());
662        let expected_selection =
663            RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]);
664        assert_eq!(result.unwrap(), expected_selection);
665
666        // Test valid selection with extra spaces: " eth , admin "
667        let result = RpcModuleSelection::from_str(" eth , admin ");
668        assert!(result.is_ok());
669        assert_eq!(result.unwrap(), expected_selection);
670
671        // Test invalid selection should return error
672        let result = RpcModuleSelection::from_str("invalid,unknown");
673        assert!(result.is_err());
674        assert_eq!(result.unwrap_err(), ParseError::VariantNotFound);
675
676        // Test single valid selection: "eth"
677        let result = RpcModuleSelection::from_str("eth");
678        assert!(result.is_ok());
679        let expected_selection = RpcModuleSelection::from([RethRpcModule::Eth]);
680        assert_eq!(result.unwrap(), expected_selection);
681
682        // Test single invalid selection: "unknown"
683        let result = RpcModuleSelection::from_str("unknown");
684        assert!(result.is_err());
685        assert_eq!(result.unwrap_err(), ParseError::VariantNotFound);
686    }
687}