reth_rpc_server_types/
module.rs

1use std::{collections::HashSet, fmt, str::FromStr};
2
3use serde::{Deserialize, Serialize, Serializer};
4use strum::{ParseError, 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().cloned()),
111            Self::Selection(s) => Box::new(s.iter().cloned()),
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().cloned().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.into_iter().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().cloned().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(Debug, Clone, Eq, PartialEq, Hash, VariantNames, Deserialize)]
297#[serde(rename_all = "snake_case")]
298#[strum(serialize_all = "kebab-case")]
299pub enum RethRpcModule {
300    /// `admin_` module
301    Admin,
302    /// `debug_` module
303    Debug,
304    /// `eth_` module
305    Eth,
306    /// `net_` module
307    Net,
308    /// `trace_` module
309    Trace,
310    /// `txpool_` module
311    Txpool,
312    /// `web3_` module
313    Web3,
314    /// `rpc_` module
315    Rpc,
316    /// `reth_` module
317    Reth,
318    /// `ots_` module
319    Ots,
320    /// `flashbots_` module
321    Flashbots,
322    /// `miner_` module
323    Miner,
324    /// `mev_` module
325    Mev,
326    /// `testing_` module
327    Testing,
328    /// Custom RPC module not part of the standard set
329    #[strum(default)]
330    #[serde(untagged)]
331    Other(String),
332}
333
334// === impl RethRpcModule ===
335
336impl RethRpcModule {
337    /// All standard variants (excludes Other)
338    const STANDARD_VARIANTS: &'static [Self] = &[
339        Self::Admin,
340        Self::Debug,
341        Self::Eth,
342        Self::Net,
343        Self::Trace,
344        Self::Txpool,
345        Self::Web3,
346        Self::Rpc,
347        Self::Reth,
348        Self::Ots,
349        Self::Flashbots,
350        Self::Miner,
351        Self::Mev,
352        Self::Testing,
353    ];
354
355    /// Returns the number of standard variants (excludes Other)
356    pub const fn variant_count() -> usize {
357        Self::STANDARD_VARIANTS.len()
358    }
359
360    /// Returns all variant names including Other (for parsing)
361    pub const fn all_variant_names() -> &'static [&'static str] {
362        <Self as VariantNames>::VARIANTS
363    }
364
365    /// Returns standard variant names (excludes "other") for CLI display
366    pub fn standard_variant_names() -> impl Iterator<Item = &'static str> {
367        <Self as VariantNames>::VARIANTS.iter().copied().filter(|&name| name != "other")
368    }
369
370    /// Returns all standard variants (excludes Other)
371    pub const fn all_variants() -> &'static [Self] {
372        Self::STANDARD_VARIANTS
373    }
374
375    /// Returns iterator over standard modules only
376    pub fn modules() -> impl IntoIterator<Item = Self> + Clone {
377        Self::STANDARD_VARIANTS.iter().cloned()
378    }
379
380    /// Returns the string representation of the module.
381    pub fn as_str(&self) -> &str {
382        match self {
383            Self::Other(s) => s.as_str(),
384            _ => self.as_ref(), // Uses AsRefStr trait
385        }
386    }
387
388    /// Returns true if this is an `Other` variant.
389    pub const fn is_other(&self) -> bool {
390        matches!(self, Self::Other(_))
391    }
392}
393
394impl AsRef<str> for RethRpcModule {
395    fn as_ref(&self) -> &str {
396        match self {
397            Self::Other(s) => s.as_str(),
398            // For standard variants, use the derive-generated static strings
399            Self::Admin => "admin",
400            Self::Debug => "debug",
401            Self::Eth => "eth",
402            Self::Net => "net",
403            Self::Trace => "trace",
404            Self::Txpool => "txpool",
405            Self::Web3 => "web3",
406            Self::Rpc => "rpc",
407            Self::Reth => "reth",
408            Self::Ots => "ots",
409            Self::Flashbots => "flashbots",
410            Self::Miner => "miner",
411            Self::Mev => "mev",
412            Self::Testing => "testing",
413        }
414    }
415}
416
417impl FromStr for RethRpcModule {
418    type Err = ParseError;
419
420    fn from_str(s: &str) -> Result<Self, Self::Err> {
421        Ok(match s {
422            "admin" => Self::Admin,
423            "debug" => Self::Debug,
424            "eth" => Self::Eth,
425            "net" => Self::Net,
426            "trace" => Self::Trace,
427            "txpool" => Self::Txpool,
428            "web3" => Self::Web3,
429            "rpc" => Self::Rpc,
430            "reth" => Self::Reth,
431            "ots" => Self::Ots,
432            "flashbots" => Self::Flashbots,
433            "miner" => Self::Miner,
434            "mev" => Self::Mev,
435            "testing" => Self::Testing,
436            // Any unknown module becomes Other
437            other => Self::Other(other.to_string()),
438        })
439    }
440}
441
442impl TryFrom<&str> for RethRpcModule {
443    type Error = ParseError;
444    fn try_from(s: &str) -> Result<Self, <Self as TryFrom<&str>>::Error> {
445        FromStr::from_str(s)
446    }
447}
448
449impl fmt::Display for RethRpcModule {
450    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
451        f.pad(self.as_ref())
452    }
453}
454
455impl Serialize for RethRpcModule {
456    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
457    where
458        S: Serializer,
459    {
460        s.serialize_str(self.as_str())
461    }
462}
463
464/// Trait for validating RPC module selections.
465///
466/// This allows customizing how RPC module names are validated when parsing
467/// CLI arguments or configuration.
468pub trait RpcModuleValidator: Clone + Send + Sync + 'static {
469    /// Parse and validate an RPC module selection string.
470    fn parse_selection(s: &str) -> Result<RpcModuleSelection, String>;
471
472    /// Validates RPC module selection that was already parsed.
473    ///
474    /// This is used to validate modules that were parsed as `Other` variants
475    /// to ensure they meet the validation rules of the specific implementation.
476    fn validate_selection(modules: &RpcModuleSelection, arg_name: &str) -> Result<(), String> {
477        // Re-validate the modules using the parser's validator
478        // This is necessary because the clap value parser accepts any input
479        // and we need to validate according to the specific parser's rules
480        let RpcModuleSelection::Selection(module_set) = modules else {
481            // All or Standard variants are always valid
482            return Ok(());
483        };
484
485        for module in module_set {
486            let RethRpcModule::Other(name) = module else {
487                // Standard modules are always valid
488                continue;
489            };
490
491            // Try to parse and validate using the configured validator
492            // This will check for typos and other validation rules
493            Self::parse_selection(name)
494                .map_err(|e| format!("Invalid RPC module '{name}' in {arg_name}: {e}"))?;
495        }
496
497        Ok(())
498    }
499}
500
501/// Default validator that rejects unknown module names.
502///
503/// This validator only accepts known RPC module names.
504#[derive(Debug, Clone, Copy)]
505pub struct DefaultRpcModuleValidator;
506
507impl RpcModuleValidator for DefaultRpcModuleValidator {
508    fn parse_selection(s: &str) -> Result<RpcModuleSelection, String> {
509        // First try standard parsing
510        let selection = RpcModuleSelection::from_str(s)
511            .map_err(|e| format!("Failed to parse RPC modules: {}", e))?;
512
513        // Validate each module in the selection
514        if let RpcModuleSelection::Selection(modules) = &selection {
515            for module in modules {
516                if let RethRpcModule::Other(name) = module {
517                    return Err(format!("Unknown RPC module: '{}'", name));
518                }
519            }
520        }
521
522        Ok(selection)
523    }
524}
525
526/// Lenient validator that accepts any module name without validation.
527///
528/// This validator accepts any module name, including unknown ones.
529#[derive(Debug, Clone, Copy)]
530pub struct LenientRpcModuleValidator;
531
532impl RpcModuleValidator for LenientRpcModuleValidator {
533    fn parse_selection(s: &str) -> Result<RpcModuleSelection, String> {
534        RpcModuleSelection::from_str(s).map_err(|e| format!("Failed to parse RPC modules: {}", e))
535    }
536}
537
538#[cfg(test)]
539mod test {
540    use super::*;
541
542    #[test]
543    fn test_all_modules() {
544        let all_modules = RpcModuleSelection::all_modules();
545        assert_eq!(all_modules.len(), RethRpcModule::variant_count());
546    }
547
548    #[test]
549    fn test_standard_modules() {
550        let standard_modules = RpcModuleSelection::standard_modules();
551        let expected_modules: HashSet<RethRpcModule> =
552            HashSet::from([RethRpcModule::Eth, RethRpcModule::Net, RethRpcModule::Web3]);
553        assert_eq!(standard_modules, expected_modules);
554    }
555
556    #[test]
557    fn test_default_ipc_modules() {
558        let default_ipc_modules = RpcModuleSelection::default_ipc_modules();
559        assert_eq!(default_ipc_modules, RpcModuleSelection::all_modules());
560    }
561
562    #[test]
563    fn test_try_from_selection_success() {
564        let selection = vec!["eth", "admin"];
565        let config = RpcModuleSelection::try_from_selection(selection).unwrap();
566        assert_eq!(config, RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]));
567    }
568
569    #[test]
570    fn test_rpc_module_selection_len() {
571        let all_modules = RpcModuleSelection::All;
572        let standard = RpcModuleSelection::Standard;
573        let selection = RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]);
574
575        assert_eq!(all_modules.len(), RethRpcModule::variant_count());
576        assert_eq!(standard.len(), 3);
577        assert_eq!(selection.len(), 2);
578    }
579
580    #[test]
581    fn test_rpc_module_selection_is_empty() {
582        let empty_selection = RpcModuleSelection::from(HashSet::new());
583        assert!(empty_selection.is_empty());
584
585        let non_empty_selection = RpcModuleSelection::from([RethRpcModule::Eth]);
586        assert!(!non_empty_selection.is_empty());
587    }
588
589    #[test]
590    fn test_rpc_module_selection_iter_selection() {
591        let all_modules = RpcModuleSelection::All;
592        let standard = RpcModuleSelection::Standard;
593        let selection = RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]);
594
595        assert_eq!(all_modules.iter_selection().count(), RethRpcModule::variant_count());
596        assert_eq!(standard.iter_selection().count(), 3);
597        assert_eq!(selection.iter_selection().count(), 2);
598    }
599
600    #[test]
601    fn test_rpc_module_selection_to_selection() {
602        let all_modules = RpcModuleSelection::All;
603        let standard = RpcModuleSelection::Standard;
604        let selection = RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]);
605
606        assert_eq!(all_modules.to_selection(), RpcModuleSelection::all_modules());
607        assert_eq!(standard.to_selection(), RpcModuleSelection::standard_modules());
608        assert_eq!(
609            selection.to_selection(),
610            HashSet::from([RethRpcModule::Eth, RethRpcModule::Admin])
611        );
612    }
613
614    #[test]
615    fn test_rpc_module_selection_are_identical() {
616        // Test scenario: both selections are `All`
617        //
618        // Since both selections include all possible RPC modules, they should be considered
619        // identical.
620        let all_modules = RpcModuleSelection::All;
621        assert!(RpcModuleSelection::are_identical(Some(&all_modules), Some(&all_modules)));
622
623        // Test scenario: both `http` and `ws` are `None`
624        //
625        // When both arguments are `None`, the function should return `true` because no modules are
626        // selected.
627        assert!(RpcModuleSelection::are_identical(None, None));
628
629        // Test scenario: both selections contain identical sets of specific modules
630        //
631        // In this case, both selections contain the same modules (`Eth` and `Admin`),
632        // so they should be considered identical.
633        let selection1 = RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]);
634        let selection2 = RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]);
635        assert!(RpcModuleSelection::are_identical(Some(&selection1), Some(&selection2)));
636
637        // Test scenario: one selection is `All`, the other is `Standard`
638        //
639        // `All` includes all possible modules, while `Standard` includes a specific set of modules.
640        // Since `Standard` does not cover all modules, these two selections should not be
641        // considered identical.
642        let standard = RpcModuleSelection::Standard;
643        assert!(!RpcModuleSelection::are_identical(Some(&all_modules), Some(&standard)));
644
645        // Test scenario: one is `None`, the other is an empty selection
646        //
647        // When one selection is `None` and the other is an empty selection (no modules),
648        // they should be considered identical because neither selects any modules.
649        let empty_selection = RpcModuleSelection::Selection(HashSet::new());
650        assert!(RpcModuleSelection::are_identical(None, Some(&empty_selection)));
651        assert!(RpcModuleSelection::are_identical(Some(&empty_selection), None));
652
653        // Test scenario: one is `None`, the other is a non-empty selection
654        //
655        // If one selection is `None` and the other contains modules, they should not be considered
656        // identical because `None` represents no selection, while the other explicitly
657        // selects modules.
658        let non_empty_selection = RpcModuleSelection::from([RethRpcModule::Eth]);
659        assert!(!RpcModuleSelection::are_identical(None, Some(&non_empty_selection)));
660        assert!(!RpcModuleSelection::are_identical(Some(&non_empty_selection), None));
661
662        // Test scenario: `All` vs. non-full selection
663        //
664        // If one selection is `All` (which includes all modules) and the other contains only a
665        // subset of modules, they should not be considered identical.
666        let partial_selection = RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Net]);
667        assert!(!RpcModuleSelection::are_identical(Some(&all_modules), Some(&partial_selection)));
668
669        // Test scenario: full selection vs `All`
670        //
671        // If the other selection explicitly selects all available modules, it should be identical
672        // to `All`.
673        let full_selection =
674            RpcModuleSelection::from(RethRpcModule::modules().into_iter().collect::<HashSet<_>>());
675        assert!(RpcModuleSelection::are_identical(Some(&all_modules), Some(&full_selection)));
676
677        // Test scenario: different non-empty selections
678        //
679        // If the two selections contain different sets of modules, they should not be considered
680        // identical.
681        let selection3 = RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Net]);
682        let selection4 = RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Web3]);
683        assert!(!RpcModuleSelection::are_identical(Some(&selection3), Some(&selection4)));
684
685        // Test scenario: `Standard` vs an equivalent selection
686        // The `Standard` selection includes a predefined set of modules. If we explicitly create
687        // a selection with the same set of modules, they should be considered identical.
688        let matching_standard =
689            RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Net, RethRpcModule::Web3]);
690        assert!(RpcModuleSelection::are_identical(Some(&standard), Some(&matching_standard)));
691
692        // Test scenario: `Standard` vs non-matching selection
693        //
694        // If the selection does not match the modules included in `Standard`, they should not be
695        // considered identical.
696        let non_matching_standard =
697            RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Net]);
698        assert!(!RpcModuleSelection::are_identical(Some(&standard), Some(&non_matching_standard)));
699    }
700
701    #[test]
702    fn test_rpc_module_selection_append() {
703        // Test append on Standard selection
704        let selection = RpcModuleSelection::Standard;
705        let new_selection = selection.append(RethRpcModule::Admin);
706        assert!(new_selection.contains(&RethRpcModule::Eth));
707        assert!(new_selection.contains(&RethRpcModule::Net));
708        assert!(new_selection.contains(&RethRpcModule::Web3));
709        assert!(new_selection.contains(&RethRpcModule::Admin));
710
711        // Test append on empty Selection
712        let selection = RpcModuleSelection::Selection(HashSet::new());
713        let new_selection = selection.append(RethRpcModule::Eth);
714        assert!(new_selection.contains(&RethRpcModule::Eth));
715        assert_eq!(new_selection.len(), 1);
716
717        // Test append on All (should return All)
718        let selection = RpcModuleSelection::All;
719        let new_selection = selection.append(RethRpcModule::Eth);
720        assert_eq!(new_selection, RpcModuleSelection::All);
721    }
722
723    #[test]
724    fn test_rpc_module_selection_extend() {
725        // Test extend on Standard selection
726        let mut selection = RpcModuleSelection::Standard;
727        selection.extend(vec![RethRpcModule::Admin, RethRpcModule::Debug]);
728        assert!(selection.contains(&RethRpcModule::Eth));
729        assert!(selection.contains(&RethRpcModule::Net));
730        assert!(selection.contains(&RethRpcModule::Web3));
731        assert!(selection.contains(&RethRpcModule::Admin));
732        assert!(selection.contains(&RethRpcModule::Debug));
733
734        // Test extend on empty Selection
735        let mut selection = RpcModuleSelection::Selection(HashSet::new());
736        selection.extend(vec![RethRpcModule::Eth, RethRpcModule::Admin]);
737        assert!(selection.contains(&RethRpcModule::Eth));
738        assert!(selection.contains(&RethRpcModule::Admin));
739        assert_eq!(selection.len(), 2);
740
741        // Test extend on All (should be no-op)
742        let mut selection = RpcModuleSelection::All;
743        selection.extend(vec![RethRpcModule::Eth, RethRpcModule::Admin]);
744        assert_eq!(selection, RpcModuleSelection::All);
745    }
746
747    #[test]
748    fn test_rpc_module_selection_from_str() {
749        // Test empty string returns default selection
750        let result = RpcModuleSelection::from_str("");
751        assert!(result.is_ok());
752        assert_eq!(result.unwrap(), RpcModuleSelection::Selection(Default::default()));
753
754        // Test "all" (case insensitive) returns All variant
755        let result = RpcModuleSelection::from_str("all");
756        assert!(result.is_ok());
757        assert_eq!(result.unwrap(), RpcModuleSelection::All);
758
759        let result = RpcModuleSelection::from_str("All");
760        assert!(result.is_ok());
761        assert_eq!(result.unwrap(), RpcModuleSelection::All);
762
763        let result = RpcModuleSelection::from_str("ALL");
764        assert!(result.is_ok());
765        assert_eq!(result.unwrap(), RpcModuleSelection::All);
766
767        // Test "none" (case insensitive) returns empty selection
768        let result = RpcModuleSelection::from_str("none");
769        assert!(result.is_ok());
770        assert_eq!(result.unwrap(), RpcModuleSelection::Selection(Default::default()));
771
772        let result = RpcModuleSelection::from_str("None");
773        assert!(result.is_ok());
774        assert_eq!(result.unwrap(), RpcModuleSelection::Selection(Default::default()));
775
776        let result = RpcModuleSelection::from_str("NONE");
777        assert!(result.is_ok());
778        assert_eq!(result.unwrap(), RpcModuleSelection::Selection(Default::default()));
779
780        // Test valid selections: "eth,admin"
781        let result = RpcModuleSelection::from_str("eth,admin");
782        assert!(result.is_ok());
783        let expected_selection =
784            RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]);
785        assert_eq!(result.unwrap(), expected_selection);
786
787        // Test valid selection with extra spaces: " eth , admin "
788        let result = RpcModuleSelection::from_str(" eth , admin ");
789        assert!(result.is_ok());
790        assert_eq!(result.unwrap(), expected_selection);
791
792        // Test custom module selections now work (no longer return errors)
793        let result = RpcModuleSelection::from_str("invalid,unknown");
794        assert!(result.is_ok());
795        let selection = result.unwrap();
796        assert!(selection.contains(&RethRpcModule::Other("invalid".to_string())));
797        assert!(selection.contains(&RethRpcModule::Other("unknown".to_string())));
798
799        // Test single valid selection: "eth"
800        let result = RpcModuleSelection::from_str("eth");
801        assert!(result.is_ok());
802        let expected_selection = RpcModuleSelection::from([RethRpcModule::Eth]);
803        assert_eq!(result.unwrap(), expected_selection);
804
805        // Test single custom module selection: "unknown" now becomes Other
806        let result = RpcModuleSelection::from_str("unknown");
807        assert!(result.is_ok());
808        let expected_selection =
809            RpcModuleSelection::from([RethRpcModule::Other("unknown".to_string())]);
810        assert_eq!(result.unwrap(), expected_selection);
811    }
812
813    #[test]
814    fn test_rpc_module_other_variant() {
815        // Test parsing custom module
816        let custom_module = RethRpcModule::from_str("myCustomModule").unwrap();
817        assert_eq!(custom_module, RethRpcModule::Other("myCustomModule".to_string()));
818
819        // Test as_str for Other variant
820        assert_eq!(custom_module.as_str(), "myCustomModule");
821
822        // Test as_ref for Other variant
823        assert_eq!(custom_module.as_ref(), "myCustomModule");
824
825        // Test Display impl
826        assert_eq!(custom_module.to_string(), "myCustomModule");
827    }
828
829    #[test]
830    fn test_rpc_module_selection_with_mixed_modules() {
831        // Test selection with both standard and custom modules
832        let result = RpcModuleSelection::from_str("eth,admin,myCustomModule,anotherCustom");
833        assert!(result.is_ok());
834
835        let selection = result.unwrap();
836        assert!(selection.contains(&RethRpcModule::Eth));
837        assert!(selection.contains(&RethRpcModule::Admin));
838        assert!(selection.contains(&RethRpcModule::Other("myCustomModule".to_string())));
839        assert!(selection.contains(&RethRpcModule::Other("anotherCustom".to_string())));
840    }
841
842    #[test]
843    fn test_rpc_module_all_excludes_custom() {
844        // Test that All selection doesn't include custom modules
845        let all_selection = RpcModuleSelection::All;
846
847        // All should contain standard modules
848        assert!(all_selection.contains(&RethRpcModule::Eth));
849        assert!(all_selection.contains(&RethRpcModule::Admin));
850
851        // But All doesn't explicitly contain custom modules
852        // (though contains() returns true for all modules when selection is All)
853        assert_eq!(all_selection.len(), RethRpcModule::variant_count());
854    }
855
856    #[test]
857    fn test_rpc_module_equality_with_other() {
858        let other1 = RethRpcModule::Other("custom".to_string());
859        let other2 = RethRpcModule::Other("custom".to_string());
860        let other3 = RethRpcModule::Other("different".to_string());
861
862        assert_eq!(other1, other2);
863        assert_ne!(other1, other3);
864        assert_ne!(other1, RethRpcModule::Eth);
865    }
866
867    #[test]
868    fn test_rpc_module_is_other() {
869        // Standard modules should return false
870        assert!(!RethRpcModule::Eth.is_other());
871        assert!(!RethRpcModule::Admin.is_other());
872        assert!(!RethRpcModule::Debug.is_other());
873
874        // Other variants should return true
875        assert!(RethRpcModule::Other("custom".to_string()).is_other());
876        assert!(RethRpcModule::Other("mycustomrpc".to_string()).is_other());
877    }
878
879    #[test]
880    fn test_standard_variant_names_excludes_other() {
881        let standard_names: Vec<_> = RethRpcModule::standard_variant_names().collect();
882
883        // Verify "other" is not in the list
884        assert!(!standard_names.contains(&"other"));
885
886        // Should have exactly as many names as STANDARD_VARIANTS
887        assert_eq!(standard_names.len(), RethRpcModule::STANDARD_VARIANTS.len());
888
889        // Verify all standard variants have their names in the list
890        for variant in RethRpcModule::STANDARD_VARIANTS {
891            assert!(standard_names.contains(&variant.as_ref()));
892        }
893    }
894
895    #[test]
896    fn test_default_validator_accepts_standard_modules() {
897        // Should accept standard modules
898        let result = DefaultRpcModuleValidator::parse_selection("eth,admin,debug");
899        assert!(result.is_ok());
900
901        let selection = result.unwrap();
902        assert!(matches!(selection, RpcModuleSelection::Selection(_)));
903    }
904
905    #[test]
906    fn test_default_validator_rejects_unknown_modules() {
907        // Should reject unknown module names
908        let result = DefaultRpcModuleValidator::parse_selection("eth,mycustom");
909        assert!(result.is_err());
910        assert!(result.unwrap_err().contains("Unknown RPC module: 'mycustom'"));
911
912        let result = DefaultRpcModuleValidator::parse_selection("unknownmodule");
913        assert!(result.is_err());
914        assert!(result.unwrap_err().contains("Unknown RPC module: 'unknownmodule'"));
915
916        let result = DefaultRpcModuleValidator::parse_selection("eth,admin,xyz123");
917        assert!(result.is_err());
918        assert!(result.unwrap_err().contains("Unknown RPC module: 'xyz123'"));
919    }
920
921    #[test]
922    fn test_default_validator_all_selection() {
923        // Should accept "all" selection
924        let result = DefaultRpcModuleValidator::parse_selection("all");
925        assert!(result.is_ok());
926        assert_eq!(result.unwrap(), RpcModuleSelection::All);
927    }
928
929    #[test]
930    fn test_default_validator_none_selection() {
931        // Should accept "none" selection
932        let result = DefaultRpcModuleValidator::parse_selection("none");
933        assert!(result.is_ok());
934        assert_eq!(result.unwrap(), RpcModuleSelection::Selection(Default::default()));
935    }
936
937    #[test]
938    fn test_lenient_validator_accepts_unknown_modules() {
939        // Lenient validator should accept any module name without validation
940        let result = LenientRpcModuleValidator::parse_selection("eht,adimn,xyz123,customrpc");
941        assert!(result.is_ok());
942
943        let selection = result.unwrap();
944        if let RpcModuleSelection::Selection(modules) = selection {
945            assert!(modules.contains(&RethRpcModule::Other("eht".to_string())));
946            assert!(modules.contains(&RethRpcModule::Other("adimn".to_string())));
947            assert!(modules.contains(&RethRpcModule::Other("xyz123".to_string())));
948            assert!(modules.contains(&RethRpcModule::Other("customrpc".to_string())));
949        } else {
950            panic!("Expected Selection variant");
951        }
952    }
953
954    #[test]
955    fn test_default_validator_mixed_standard_and_custom() {
956        // Should reject mix of standard and custom modules
957        let result = DefaultRpcModuleValidator::parse_selection("eth,admin,mycustom,debug");
958        assert!(result.is_err());
959        assert!(result.unwrap_err().contains("Unknown RPC module: 'mycustom'"));
960    }
961}