Skip to main content

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 modules except `testing`, which requires an explicit selection.
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 all modules that do not require an explicit selection.
35    pub fn all_modules() -> HashSet<RethRpcModule> {
36        Self::all_modules_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, excluding `testing`.
45    pub fn default_ipc_modules() -> HashSet<RethRpcModule> {
46        Self::all_modules()
47    }
48
49    /// Creates a new _unique_ [`RpcModuleSelection::Selection`] from the given items.
50    ///
51    /// # Note
52    ///
53    /// This will dedupe the selection and remove duplicates while preserving the order.
54    ///
55    /// # Example
56    ///
57    /// Create a selection from the [`RethRpcModule`] string identifiers
58    ///
59    /// ```
60    /// use reth_rpc_server_types::{RethRpcModule, RpcModuleSelection};
61    /// let selection = vec!["eth", "admin"];
62    /// let config = RpcModuleSelection::try_from_selection(selection).unwrap();
63    /// assert_eq!(config, RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]));
64    /// ```
65    ///
66    /// Create a unique selection from the [`RethRpcModule`] string identifiers
67    ///
68    /// ```
69    /// use reth_rpc_server_types::{RethRpcModule, RpcModuleSelection};
70    /// let selection = vec!["eth", "admin", "eth", "admin"];
71    /// let config = RpcModuleSelection::try_from_selection(selection).unwrap();
72    /// assert_eq!(config, RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]));
73    /// ```
74    pub fn try_from_selection<I, T>(selection: I) -> Result<Self, T::Error>
75    where
76        I: IntoIterator<Item = T>,
77        T: TryInto<RethRpcModule>,
78    {
79        selection.into_iter().map(TryInto::try_into).collect()
80    }
81
82    /// Returns the number of modules in the selection
83    pub fn len(&self) -> usize {
84        match self {
85            Self::All => Self::all_modules_iter().count(),
86            Self::Standard => Self::STANDARD_MODULES.len(),
87            Self::Selection(s) => s.len(),
88        }
89    }
90
91    /// Returns true if no selection is configured
92    pub fn is_empty(&self) -> bool {
93        match self {
94            Self::Selection(sel) => sel.is_empty(),
95            _ => false,
96        }
97    }
98
99    /// Returns true if all modules are selected
100    pub const fn is_all(&self) -> bool {
101        matches!(self, Self::All)
102    }
103
104    /// Returns an iterator over all configured [`RethRpcModule`]
105    pub fn iter_selection(&self) -> Box<dyn Iterator<Item = RethRpcModule> + '_> {
106        match self {
107            Self::All => Box::new(Self::all_modules_iter()),
108            Self::Standard => Box::new(Self::STANDARD_MODULES.iter().cloned()),
109            Self::Selection(s) => Box::new(s.iter().cloned()),
110        }
111    }
112
113    /// Clones the set of configured [`RethRpcModule`].
114    pub fn to_selection(&self) -> HashSet<RethRpcModule> {
115        match self {
116            Self::All => Self::all_modules(),
117            Self::Standard => Self::standard_modules(),
118            Self::Selection(s) => s.clone(),
119        }
120    }
121
122    /// Converts the selection into a [`HashSet`].
123    pub fn into_selection(self) -> HashSet<RethRpcModule> {
124        match self {
125            Self::All => Self::all_modules(),
126            Self::Standard => Self::standard_modules(),
127            Self::Selection(s) => s,
128        }
129    }
130
131    /// Returns true if both selections are identical.
132    pub fn are_identical(http: Option<&Self>, ws: Option<&Self>) -> bool {
133        match (http, ws) {
134            // If either side is disabled, then the other must be empty
135            (Some(some), None) | (None, Some(some)) => some.is_empty(),
136
137            (Some(http), Some(ws)) => http.to_selection() == ws.to_selection(),
138            (None, None) => true,
139        }
140    }
141
142    /// Returns true if the selection contains the given module.
143    pub fn contains(&self, module: &RethRpcModule) -> bool {
144        match self {
145            Self::All => !matches!(module, RethRpcModule::Testing),
146            Self::Standard => Self::STANDARD_MODULES.contains(module),
147            Self::Selection(s) => s.contains(module),
148        }
149    }
150
151    /// Adds a module to the selection.
152    ///
153    /// If the selection is `All`, this is a no-op.
154    /// Otherwise, converts to a `Selection` and adds the module.
155    pub fn push(&mut self, module: RethRpcModule) {
156        if !self.is_all() {
157            let mut modules = self.to_selection();
158            modules.insert(module);
159            *self = Self::Selection(modules);
160        }
161    }
162
163    /// Returns a new selection with the given module added.
164    ///
165    /// If the selection is `All`, returns `All`.
166    /// Otherwise, converts to a `Selection` and adds the module.
167    pub fn append(self, module: RethRpcModule) -> Self {
168        if self.is_all() {
169            Self::All
170        } else {
171            let mut modules = self.into_selection();
172            modules.insert(module);
173            Self::Selection(modules)
174        }
175    }
176
177    /// Extends the selection with modules from an iterator.
178    ///
179    /// If the selection is `All`, this is a no-op.
180    /// Otherwise, converts to a `Selection` and adds the modules.
181    pub fn extend<I>(&mut self, iter: I)
182    where
183        I: IntoIterator<Item = RethRpcModule>,
184    {
185        if !self.is_all() {
186            let mut modules = self.to_selection();
187            modules.extend(iter);
188            *self = Self::Selection(modules);
189        }
190    }
191
192    /// Returns a new selection with modules from an iterator added.
193    ///
194    /// If the selection is `All`, returns `All`.
195    /// Otherwise, converts to a `Selection` and adds the modules.
196    pub fn extended<I>(self, iter: I) -> Self
197    where
198        I: IntoIterator<Item = RethRpcModule>,
199    {
200        if self.is_all() {
201            Self::All
202        } else {
203            let mut modules = self.into_selection();
204            modules.extend(iter);
205            Self::Selection(modules)
206        }
207    }
208
209    fn all_modules_iter() -> impl Iterator<Item = RethRpcModule> {
210        RethRpcModule::modules().into_iter().filter(|module| *module != RethRpcModule::Testing)
211    }
212}
213
214impl From<&HashSet<RethRpcModule>> for RpcModuleSelection {
215    fn from(s: &HashSet<RethRpcModule>) -> Self {
216        Self::from(s.clone())
217    }
218}
219
220impl From<HashSet<RethRpcModule>> for RpcModuleSelection {
221    fn from(s: HashSet<RethRpcModule>) -> Self {
222        Self::Selection(s)
223    }
224}
225
226impl From<&[RethRpcModule]> for RpcModuleSelection {
227    fn from(s: &[RethRpcModule]) -> Self {
228        Self::Selection(s.iter().cloned().collect())
229    }
230}
231
232impl From<Vec<RethRpcModule>> for RpcModuleSelection {
233    fn from(s: Vec<RethRpcModule>) -> Self {
234        Self::Selection(s.into_iter().collect())
235    }
236}
237
238impl<const N: usize> From<[RethRpcModule; N]> for RpcModuleSelection {
239    fn from(s: [RethRpcModule; N]) -> Self {
240        Self::Selection(s.into_iter().collect())
241    }
242}
243
244impl<'a> FromIterator<&'a RethRpcModule> for RpcModuleSelection {
245    fn from_iter<I>(iter: I) -> Self
246    where
247        I: IntoIterator<Item = &'a RethRpcModule>,
248    {
249        iter.into_iter().cloned().collect()
250    }
251}
252
253impl FromIterator<RethRpcModule> for RpcModuleSelection {
254    fn from_iter<I>(iter: I) -> Self
255    where
256        I: IntoIterator<Item = RethRpcModule>,
257    {
258        Self::Selection(iter.into_iter().collect())
259    }
260}
261
262impl FromStr for RpcModuleSelection {
263    type Err = ParseError;
264
265    fn from_str(s: &str) -> Result<Self, Self::Err> {
266        if s.is_empty() {
267            return Ok(Self::Selection(Default::default()))
268        }
269        let mut modules = s.split(',').map(str::trim).peekable();
270        let first = modules.peek().copied().ok_or(ParseError::VariantNotFound)?;
271        // We convert to lowercase to make the comparison case-insensitive
272        //
273        // This is a way to allow typing "all" and "ALL" and "All" and "aLl" etc.
274        match first.to_lowercase().as_str() {
275            "all" => Ok(Self::All),
276            "none" => Ok(Self::Selection(Default::default())),
277            _ => Self::try_from_selection(modules),
278        }
279    }
280}
281
282impl fmt::Display for RpcModuleSelection {
283    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
284        write!(
285            f,
286            "[{}]",
287            self.iter_selection().map(|s| s.to_string()).collect::<Vec<_>>().join(", ")
288        )
289    }
290}
291
292/// Represents RPC modules that are supported by reth
293#[derive(Debug, Clone, Eq, PartialEq, Hash, VariantNames, Deserialize)]
294#[serde(rename_all = "snake_case")]
295#[strum(serialize_all = "kebab-case")]
296pub enum RethRpcModule {
297    /// `admin_` module
298    Admin,
299    /// `debug_` module
300    Debug,
301    /// `eth_` module
302    Eth,
303    /// `net_` module
304    Net,
305    /// `trace_` module
306    Trace,
307    /// `txpool_` module
308    Txpool,
309    /// `web3_` module
310    Web3,
311    /// `rpc_` module
312    Rpc,
313    /// `reth_` module
314    Reth,
315    /// `ots_` module
316    Ots,
317    /// `flashbots_` module
318    Flashbots,
319    /// `miner_` module
320    Miner,
321    /// `mev_` module
322    Mev,
323    /// `testing_` module
324    Testing,
325    /// Custom RPC module not part of the standard set
326    #[strum(default)]
327    #[serde(untagged)]
328    Other(String),
329}
330
331// === impl RethRpcModule ===
332
333impl RethRpcModule {
334    /// All standard variants (excludes Other)
335    const STANDARD_VARIANTS: &'static [Self] = &[
336        Self::Admin,
337        Self::Debug,
338        Self::Eth,
339        Self::Net,
340        Self::Trace,
341        Self::Txpool,
342        Self::Web3,
343        Self::Rpc,
344        Self::Reth,
345        Self::Ots,
346        Self::Flashbots,
347        Self::Miner,
348        Self::Mev,
349        Self::Testing,
350    ];
351
352    /// Returns the number of standard variants (excludes Other)
353    pub const fn variant_count() -> usize {
354        Self::STANDARD_VARIANTS.len()
355    }
356
357    /// Returns all variant names including Other (for parsing)
358    pub const fn all_variant_names() -> &'static [&'static str] {
359        <Self as VariantNames>::VARIANTS
360    }
361
362    /// Returns standard variant names (excludes "other") for CLI display
363    pub fn standard_variant_names() -> impl Iterator<Item = &'static str> {
364        <Self as VariantNames>::VARIANTS.iter().copied().filter(|&name| name != "other")
365    }
366
367    /// Returns all standard variants (excludes Other)
368    pub const fn all_variants() -> &'static [Self] {
369        Self::STANDARD_VARIANTS
370    }
371
372    /// Returns iterator over standard modules only
373    pub fn modules() -> impl IntoIterator<Item = Self> + Clone {
374        Self::STANDARD_VARIANTS.iter().cloned()
375    }
376
377    /// Returns the string representation of the module.
378    pub fn as_str(&self) -> &str {
379        match self {
380            Self::Other(s) => s.as_str(),
381            _ => self.as_ref(), // Uses AsRefStr trait
382        }
383    }
384
385    /// Returns true if this is an `Other` variant.
386    pub const fn is_other(&self) -> bool {
387        matches!(self, Self::Other(_))
388    }
389}
390
391impl AsRef<str> for RethRpcModule {
392    fn as_ref(&self) -> &str {
393        match self {
394            Self::Other(s) => s.as_str(),
395            // For standard variants, use the derive-generated static strings
396            Self::Admin => "admin",
397            Self::Debug => "debug",
398            Self::Eth => "eth",
399            Self::Net => "net",
400            Self::Trace => "trace",
401            Self::Txpool => "txpool",
402            Self::Web3 => "web3",
403            Self::Rpc => "rpc",
404            Self::Reth => "reth",
405            Self::Ots => "ots",
406            Self::Flashbots => "flashbots",
407            Self::Miner => "miner",
408            Self::Mev => "mev",
409            Self::Testing => "testing",
410        }
411    }
412}
413
414impl FromStr for RethRpcModule {
415    type Err = ParseError;
416
417    fn from_str(s: &str) -> Result<Self, Self::Err> {
418        Ok(match s {
419            "admin" => Self::Admin,
420            "debug" => Self::Debug,
421            "eth" => Self::Eth,
422            "net" => Self::Net,
423            "trace" => Self::Trace,
424            "txpool" => Self::Txpool,
425            "web3" => Self::Web3,
426            "rpc" => Self::Rpc,
427            "reth" => Self::Reth,
428            "ots" => Self::Ots,
429            "flashbots" => Self::Flashbots,
430            "miner" => Self::Miner,
431            "mev" => Self::Mev,
432            "testing" => Self::Testing,
433            // Any unknown module becomes Other
434            other => Self::Other(other.to_string()),
435        })
436    }
437}
438
439impl TryFrom<&str> for RethRpcModule {
440    type Error = ParseError;
441    fn try_from(s: &str) -> Result<Self, <Self as TryFrom<&str>>::Error> {
442        FromStr::from_str(s)
443    }
444}
445
446impl fmt::Display for RethRpcModule {
447    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
448        f.pad(self.as_ref())
449    }
450}
451
452impl Serialize for RethRpcModule {
453    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
454    where
455        S: Serializer,
456    {
457        s.serialize_str(self.as_str())
458    }
459}
460
461/// Trait for validating RPC module selections.
462///
463/// This allows customizing how RPC module names are validated when parsing
464/// CLI arguments or configuration.
465pub trait RpcModuleValidator: Clone + Send + Sync + 'static {
466    /// Parse and validate an RPC module selection string.
467    fn parse_selection(s: &str) -> Result<RpcModuleSelection, String>;
468
469    /// Validates RPC module selection that was already parsed.
470    ///
471    /// This is used to validate modules that were parsed as `Other` variants
472    /// to ensure they meet the validation rules of the specific implementation.
473    fn validate_selection(modules: &RpcModuleSelection, arg_name: &str) -> Result<(), String> {
474        // Re-validate the modules using the parser's validator
475        // This is necessary because the clap value parser accepts any input
476        // and we need to validate according to the specific parser's rules
477        let RpcModuleSelection::Selection(module_set) = modules else {
478            // All or Standard variants are always valid
479            return Ok(());
480        };
481
482        for module in module_set {
483            let RethRpcModule::Other(name) = module else {
484                // Standard modules are always valid
485                continue;
486            };
487
488            // Try to parse and validate using the configured validator
489            // This will check for typos and other validation rules
490            Self::parse_selection(name)
491                .map_err(|e| format!("Invalid RPC module '{name}' in {arg_name}: {e}"))?;
492        }
493
494        Ok(())
495    }
496}
497
498/// Default validator that rejects unknown module names.
499///
500/// This validator only accepts known RPC module names.
501#[derive(Debug, Clone, Copy)]
502pub struct DefaultRpcModuleValidator;
503
504impl RpcModuleValidator for DefaultRpcModuleValidator {
505    fn parse_selection(s: &str) -> Result<RpcModuleSelection, String> {
506        // First try standard parsing
507        let selection = RpcModuleSelection::from_str(s)
508            .map_err(|e| format!("Failed to parse RPC modules: {}", e))?;
509
510        // Validate each module in the selection
511        if let RpcModuleSelection::Selection(modules) = &selection {
512            for module in modules {
513                if let RethRpcModule::Other(name) = module {
514                    return Err(format!("Unknown RPC module: '{}'", name));
515                }
516            }
517        }
518
519        Ok(selection)
520    }
521}
522
523/// Lenient validator that accepts any module name without validation.
524///
525/// This validator accepts any module name, including unknown ones.
526#[derive(Debug, Clone, Copy)]
527pub struct LenientRpcModuleValidator;
528
529impl RpcModuleValidator for LenientRpcModuleValidator {
530    fn parse_selection(s: &str) -> Result<RpcModuleSelection, String> {
531        RpcModuleSelection::from_str(s).map_err(|e| format!("Failed to parse RPC modules: {}", e))
532    }
533}
534
535#[cfg(test)]
536mod test {
537    use super::*;
538
539    #[test]
540    fn test_all_modules() {
541        let all_modules = RpcModuleSelection::all_modules();
542        assert_eq!(all_modules.len(), RethRpcModule::variant_count() - 1);
543        assert!(!all_modules.contains(&RethRpcModule::Testing));
544    }
545
546    #[test]
547    fn test_testing_requires_explicit_selection() {
548        let all = RpcModuleSelection::All;
549        let explicit = RpcModuleSelection::from([RethRpcModule::Testing]);
550
551        assert!(!all.contains(&RethRpcModule::Testing));
552        assert!(!all.to_selection().contains(&RethRpcModule::Testing));
553        assert!(!all.into_selection().contains(&RethRpcModule::Testing));
554        assert!(explicit.contains(&RethRpcModule::Testing));
555        assert!(explicit.iter_selection().any(|module| module == RethRpcModule::Testing));
556    }
557
558    #[test]
559    fn test_standard_modules() {
560        let standard_modules = RpcModuleSelection::standard_modules();
561        let expected_modules: HashSet<RethRpcModule> =
562            HashSet::from([RethRpcModule::Eth, RethRpcModule::Net, RethRpcModule::Web3]);
563        assert_eq!(standard_modules, expected_modules);
564    }
565
566    #[test]
567    fn test_default_ipc_modules() {
568        let default_ipc_modules = RpcModuleSelection::default_ipc_modules();
569        assert_eq!(default_ipc_modules, RpcModuleSelection::all_modules());
570        assert!(!default_ipc_modules.contains(&RethRpcModule::Testing));
571    }
572
573    #[test]
574    fn test_try_from_selection_success() {
575        let selection = vec!["eth", "admin"];
576        let config = RpcModuleSelection::try_from_selection(selection).unwrap();
577        assert_eq!(config, RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]));
578    }
579
580    #[test]
581    fn test_rpc_module_selection_len() {
582        let all_modules = RpcModuleSelection::All;
583        let standard = RpcModuleSelection::Standard;
584        let selection = RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]);
585
586        assert_eq!(all_modules.len(), RethRpcModule::variant_count() - 1);
587        assert_eq!(standard.len(), 3);
588        assert_eq!(selection.len(), 2);
589    }
590
591    #[test]
592    fn test_rpc_module_selection_is_empty() {
593        let empty_selection = RpcModuleSelection::from(HashSet::new());
594        assert!(empty_selection.is_empty());
595
596        let non_empty_selection = RpcModuleSelection::from([RethRpcModule::Eth]);
597        assert!(!non_empty_selection.is_empty());
598    }
599
600    #[test]
601    fn test_rpc_module_selection_iter_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.iter_selection().count(), RethRpcModule::variant_count() - 1);
607        assert!(!all_modules.iter_selection().any(|module| module == RethRpcModule::Testing));
608        assert_eq!(standard.iter_selection().count(), 3);
609        assert_eq!(selection.iter_selection().count(), 2);
610    }
611
612    #[test]
613    fn test_rpc_module_selection_to_selection() {
614        let all_modules = RpcModuleSelection::All;
615        let standard = RpcModuleSelection::Standard;
616        let selection = RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]);
617
618        assert_eq!(all_modules.to_selection(), RpcModuleSelection::all_modules());
619        assert_eq!(standard.to_selection(), RpcModuleSelection::standard_modules());
620        assert_eq!(
621            selection.to_selection(),
622            HashSet::from([RethRpcModule::Eth, RethRpcModule::Admin])
623        );
624    }
625
626    #[test]
627    fn test_rpc_module_selection_are_identical() {
628        // Test scenario: both selections are `All`
629        //
630        // Both selections include the same modules, excluding `testing`.
631        let all_modules = RpcModuleSelection::All;
632        assert!(RpcModuleSelection::are_identical(Some(&all_modules), Some(&all_modules)));
633
634        // Test scenario: both `http` and `ws` are `None`
635        //
636        // When both arguments are `None`, the function should return `true` because no modules are
637        // selected.
638        assert!(RpcModuleSelection::are_identical(None, None));
639
640        // Test scenario: both selections contain identical sets of specific modules
641        //
642        // In this case, both selections contain the same modules (`Eth` and `Admin`),
643        // so they should be considered identical.
644        let selection1 = RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]);
645        let selection2 = RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]);
646        assert!(RpcModuleSelection::are_identical(Some(&selection1), Some(&selection2)));
647
648        // Test scenario: one selection is `All`, the other is `Standard`
649        //
650        // `All` includes all possible modules, while `Standard` includes a specific set of modules.
651        // Since `Standard` does not cover all modules, these two selections should not be
652        // considered identical.
653        let standard = RpcModuleSelection::Standard;
654        assert!(!RpcModuleSelection::are_identical(Some(&all_modules), Some(&standard)));
655
656        // Test scenario: one is `None`, the other is an empty selection
657        //
658        // When one selection is `None` and the other is an empty selection (no modules),
659        // they should be considered identical because neither selects any modules.
660        let empty_selection = RpcModuleSelection::Selection(HashSet::new());
661        assert!(RpcModuleSelection::are_identical(None, Some(&empty_selection)));
662        assert!(RpcModuleSelection::are_identical(Some(&empty_selection), None));
663
664        // Test scenario: one is `None`, the other is a non-empty selection
665        //
666        // If one selection is `None` and the other contains modules, they should not be considered
667        // identical because `None` represents no selection, while the other explicitly
668        // selects modules.
669        let non_empty_selection = RpcModuleSelection::from([RethRpcModule::Eth]);
670        assert!(!RpcModuleSelection::are_identical(None, Some(&non_empty_selection)));
671        assert!(!RpcModuleSelection::are_identical(Some(&non_empty_selection), None));
672
673        // Test scenario: `All` vs. non-full selection
674        //
675        // If one selection is `All` (which includes all modules) and the other contains only a
676        // subset of modules, they should not be considered identical.
677        let partial_selection = RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Net]);
678        assert!(!RpcModuleSelection::are_identical(Some(&all_modules), Some(&partial_selection)));
679
680        // Test scenario: full selection vs `All`
681        //
682        // An explicit selection of the same modules is identical to `All`.
683        let full_selection = RpcModuleSelection::from(RpcModuleSelection::all_modules());
684        assert!(RpcModuleSelection::are_identical(Some(&all_modules), Some(&full_selection)));
685
686        // Explicitly including `testing` makes the selections different.
687        let mut testing_selection = RpcModuleSelection::all_modules();
688        testing_selection.insert(RethRpcModule::Testing);
689        let testing_selection = RpcModuleSelection::from(testing_selection);
690        assert!(!RpcModuleSelection::are_identical(Some(&all_modules), Some(&testing_selection),));
691
692        // Test scenario: different non-empty selections
693        //
694        // If the two selections contain different sets of modules, they should not be considered
695        // identical.
696        let selection3 = RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Net]);
697        let selection4 = RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Web3]);
698        assert!(!RpcModuleSelection::are_identical(Some(&selection3), Some(&selection4)));
699
700        // Test scenario: `Standard` vs an equivalent selection
701        // The `Standard` selection includes a predefined set of modules. If we explicitly create
702        // a selection with the same set of modules, they should be considered identical.
703        let matching_standard =
704            RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Net, RethRpcModule::Web3]);
705        assert!(RpcModuleSelection::are_identical(Some(&standard), Some(&matching_standard)));
706
707        // Test scenario: `Standard` vs non-matching selection
708        //
709        // If the selection does not match the modules included in `Standard`, they should not be
710        // considered identical.
711        let non_matching_standard =
712            RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Net]);
713        assert!(!RpcModuleSelection::are_identical(Some(&standard), Some(&non_matching_standard)));
714    }
715
716    #[test]
717    fn test_rpc_module_selection_append() {
718        // Test append on Standard selection
719        let selection = RpcModuleSelection::Standard;
720        let new_selection = selection.append(RethRpcModule::Admin);
721        assert!(new_selection.contains(&RethRpcModule::Eth));
722        assert!(new_selection.contains(&RethRpcModule::Net));
723        assert!(new_selection.contains(&RethRpcModule::Web3));
724        assert!(new_selection.contains(&RethRpcModule::Admin));
725
726        // Test append on empty Selection
727        let selection = RpcModuleSelection::Selection(HashSet::new());
728        let new_selection = selection.append(RethRpcModule::Eth);
729        assert!(new_selection.contains(&RethRpcModule::Eth));
730        assert_eq!(new_selection.len(), 1);
731
732        // Test append on All (should return All)
733        let selection = RpcModuleSelection::All;
734        let new_selection = selection.append(RethRpcModule::Eth);
735        assert_eq!(new_selection, RpcModuleSelection::All);
736    }
737
738    #[test]
739    fn test_rpc_module_selection_extend() {
740        // Test extend on Standard selection
741        let mut selection = RpcModuleSelection::Standard;
742        selection.extend(vec![RethRpcModule::Admin, RethRpcModule::Debug]);
743        assert!(selection.contains(&RethRpcModule::Eth));
744        assert!(selection.contains(&RethRpcModule::Net));
745        assert!(selection.contains(&RethRpcModule::Web3));
746        assert!(selection.contains(&RethRpcModule::Admin));
747        assert!(selection.contains(&RethRpcModule::Debug));
748
749        // Test extend on empty Selection
750        let mut selection = RpcModuleSelection::Selection(HashSet::new());
751        selection.extend(vec![RethRpcModule::Eth, RethRpcModule::Admin]);
752        assert!(selection.contains(&RethRpcModule::Eth));
753        assert!(selection.contains(&RethRpcModule::Admin));
754        assert_eq!(selection.len(), 2);
755
756        // Test extend on All (should be no-op)
757        let mut selection = RpcModuleSelection::All;
758        selection.extend(vec![RethRpcModule::Eth, RethRpcModule::Admin]);
759        assert_eq!(selection, RpcModuleSelection::All);
760    }
761
762    #[test]
763    fn test_rpc_module_selection_from_str() {
764        // Test empty string returns default selection
765        let result = RpcModuleSelection::from_str("");
766        assert!(result.is_ok());
767        assert_eq!(result.unwrap(), RpcModuleSelection::Selection(Default::default()));
768
769        // Test "all" (case insensitive) returns All variant
770        let result = RpcModuleSelection::from_str("all");
771        assert!(result.is_ok());
772        assert_eq!(result.unwrap(), RpcModuleSelection::All);
773
774        let result = RpcModuleSelection::from_str("All");
775        assert!(result.is_ok());
776        assert_eq!(result.unwrap(), RpcModuleSelection::All);
777
778        let result = RpcModuleSelection::from_str("ALL");
779        assert!(result.is_ok());
780        assert_eq!(result.unwrap(), RpcModuleSelection::All);
781
782        // Test "none" (case insensitive) returns empty selection
783        let result = RpcModuleSelection::from_str("none");
784        assert!(result.is_ok());
785        assert_eq!(result.unwrap(), RpcModuleSelection::Selection(Default::default()));
786
787        let result = RpcModuleSelection::from_str("None");
788        assert!(result.is_ok());
789        assert_eq!(result.unwrap(), RpcModuleSelection::Selection(Default::default()));
790
791        let result = RpcModuleSelection::from_str("NONE");
792        assert!(result.is_ok());
793        assert_eq!(result.unwrap(), RpcModuleSelection::Selection(Default::default()));
794
795        // Test valid selections: "eth,admin"
796        let result = RpcModuleSelection::from_str("eth,admin");
797        assert!(result.is_ok());
798        let expected_selection =
799            RpcModuleSelection::from([RethRpcModule::Eth, RethRpcModule::Admin]);
800        assert_eq!(result.unwrap(), expected_selection);
801
802        // Test valid selection with extra spaces: " eth , admin "
803        let result = RpcModuleSelection::from_str(" eth , admin ");
804        assert!(result.is_ok());
805        assert_eq!(result.unwrap(), expected_selection);
806
807        // Test custom module selections now work (no longer return errors)
808        let result = RpcModuleSelection::from_str("invalid,unknown");
809        assert!(result.is_ok());
810        let selection = result.unwrap();
811        assert!(selection.contains(&RethRpcModule::Other("invalid".to_string())));
812        assert!(selection.contains(&RethRpcModule::Other("unknown".to_string())));
813
814        // Test single valid selection: "eth"
815        let result = RpcModuleSelection::from_str("eth");
816        assert!(result.is_ok());
817        let expected_selection = RpcModuleSelection::from([RethRpcModule::Eth]);
818        assert_eq!(result.unwrap(), expected_selection);
819
820        // Test single custom module selection: "unknown" now becomes Other
821        let result = RpcModuleSelection::from_str("unknown");
822        assert!(result.is_ok());
823        let expected_selection =
824            RpcModuleSelection::from([RethRpcModule::Other("unknown".to_string())]);
825        assert_eq!(result.unwrap(), expected_selection);
826    }
827
828    #[test]
829    fn test_rpc_module_other_variant() {
830        // Test parsing custom module
831        let custom_module = RethRpcModule::from_str("myCustomModule").unwrap();
832        assert_eq!(custom_module, RethRpcModule::Other("myCustomModule".to_string()));
833
834        // Test as_str for Other variant
835        assert_eq!(custom_module.as_str(), "myCustomModule");
836
837        // Test as_ref for Other variant
838        assert_eq!(custom_module.as_ref(), "myCustomModule");
839
840        // Test Display impl
841        assert_eq!(custom_module.to_string(), "myCustomModule");
842    }
843
844    #[test]
845    fn test_rpc_module_selection_with_mixed_modules() {
846        // Test selection with both standard and custom modules
847        let result = RpcModuleSelection::from_str("eth,admin,myCustomModule,anotherCustom");
848        assert!(result.is_ok());
849
850        let selection = result.unwrap();
851        assert!(selection.contains(&RethRpcModule::Eth));
852        assert!(selection.contains(&RethRpcModule::Admin));
853        assert!(selection.contains(&RethRpcModule::Other("myCustomModule".to_string())));
854        assert!(selection.contains(&RethRpcModule::Other("anotherCustom".to_string())));
855    }
856
857    #[test]
858    fn test_rpc_module_all_excludes_custom() {
859        // Test that All selection doesn't include custom modules
860        let all_selection = RpcModuleSelection::All;
861
862        // All should contain standard modules
863        assert!(all_selection.contains(&RethRpcModule::Eth));
864        assert!(all_selection.contains(&RethRpcModule::Admin));
865
866        // But All doesn't explicitly contain custom modules
867        // (though contains() returns true for all modules when selection is All)
868        assert_eq!(all_selection.len(), RethRpcModule::variant_count() - 1);
869    }
870
871    #[test]
872    fn test_rpc_module_equality_with_other() {
873        let other1 = RethRpcModule::Other("custom".to_string());
874        let other2 = RethRpcModule::Other("custom".to_string());
875        let other3 = RethRpcModule::Other("different".to_string());
876
877        assert_eq!(other1, other2);
878        assert_ne!(other1, other3);
879        assert_ne!(other1, RethRpcModule::Eth);
880    }
881
882    #[test]
883    fn test_rpc_module_is_other() {
884        // Standard modules should return false
885        assert!(!RethRpcModule::Eth.is_other());
886        assert!(!RethRpcModule::Admin.is_other());
887        assert!(!RethRpcModule::Debug.is_other());
888
889        // Other variants should return true
890        assert!(RethRpcModule::Other("custom".to_string()).is_other());
891        assert!(RethRpcModule::Other("mycustomrpc".to_string()).is_other());
892    }
893
894    #[test]
895    fn test_standard_variant_names_excludes_other() {
896        let standard_names: Vec<_> = RethRpcModule::standard_variant_names().collect();
897
898        // Verify "other" is not in the list
899        assert!(!standard_names.contains(&"other"));
900
901        // Should have exactly as many names as STANDARD_VARIANTS
902        assert_eq!(standard_names.len(), RethRpcModule::STANDARD_VARIANTS.len());
903
904        // Verify all standard variants have their names in the list
905        for variant in RethRpcModule::STANDARD_VARIANTS {
906            assert!(standard_names.contains(&variant.as_ref()));
907        }
908    }
909
910    #[test]
911    fn test_default_validator_accepts_standard_modules() {
912        // Should accept standard modules
913        let result = DefaultRpcModuleValidator::parse_selection("eth,admin,debug");
914        assert!(result.is_ok());
915
916        let selection = result.unwrap();
917        assert!(matches!(selection, RpcModuleSelection::Selection(_)));
918    }
919
920    #[test]
921    fn test_default_validator_rejects_unknown_modules() {
922        // Should reject unknown module names
923        let result = DefaultRpcModuleValidator::parse_selection("eth,mycustom");
924        assert!(result.is_err());
925        assert!(result.unwrap_err().contains("Unknown RPC module: 'mycustom'"));
926
927        let result = DefaultRpcModuleValidator::parse_selection("unknownmodule");
928        assert!(result.is_err());
929        assert!(result.unwrap_err().contains("Unknown RPC module: 'unknownmodule'"));
930
931        let result = DefaultRpcModuleValidator::parse_selection("eth,admin,xyz123");
932        assert!(result.is_err());
933        assert!(result.unwrap_err().contains("Unknown RPC module: 'xyz123'"));
934    }
935
936    #[test]
937    fn test_default_validator_all_selection() {
938        // Should accept "all" selection
939        let result = DefaultRpcModuleValidator::parse_selection("all");
940        assert!(result.is_ok());
941        assert_eq!(result.unwrap(), RpcModuleSelection::All);
942    }
943
944    #[test]
945    fn test_default_validator_none_selection() {
946        // Should accept "none" selection
947        let result = DefaultRpcModuleValidator::parse_selection("none");
948        assert!(result.is_ok());
949        assert_eq!(result.unwrap(), RpcModuleSelection::Selection(Default::default()));
950    }
951
952    #[test]
953    fn test_lenient_validator_accepts_unknown_modules() {
954        // Lenient validator should accept any module name without validation
955        let result = LenientRpcModuleValidator::parse_selection("eht,adimn,xyz123,customrpc");
956        assert!(result.is_ok());
957
958        let selection = result.unwrap();
959        if let RpcModuleSelection::Selection(modules) = selection {
960            assert!(modules.contains(&RethRpcModule::Other("eht".to_string())));
961            assert!(modules.contains(&RethRpcModule::Other("adimn".to_string())));
962            assert!(modules.contains(&RethRpcModule::Other("xyz123".to_string())));
963            assert!(modules.contains(&RethRpcModule::Other("customrpc".to_string())));
964        } else {
965            panic!("Expected Selection variant");
966        }
967    }
968
969    #[test]
970    fn test_default_validator_mixed_standard_and_custom() {
971        // Should reject mix of standard and custom modules
972        let result = DefaultRpcModuleValidator::parse_selection("eth,admin,mycustom,debug");
973        assert!(result.is_err());
974        assert!(result.unwrap_err().contains("Unknown RPC module: 'mycustom'"));
975    }
976}