Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: load app with only testnet config #16

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

pauldelucia
Copy link
Member

@pauldelucia pauldelucia commented Oct 22, 2024

Allow users to load the app with configs for testnet only (and no configs for mainnet)

Summary by CodeRabbit

  • New Features

    • Enhanced error handling for configuration loading, improving robustness.
    • Updated network chooser logic to handle optional application contexts.
  • Bug Fixes

    • Improved handling of missing network configurations to prevent application crashes.
  • Documentation

    • Added error messages for configuration failures to assist users in troubleshooting.
  • Refactor

    • Refined application state management to allow optional contexts, simplifying error handling.

Copy link
Contributor

coderabbitai bot commented Oct 22, 2024

Walkthrough

The changes in this pull request involve significant modifications to the application state management and error handling mechanisms across multiple files. The AppState structure now allows for an optional mainnet_app_context, necessitating updates to initialization logic and context handling. The Config struct has been enhanced with error reporting capabilities, while the AppContext struct has been updated to handle configuration loading errors gracefully. Additionally, the NetworkChooserScreen has been adjusted to accommodate the new optional context types, improving overall robustness and error handling.

Changes

File Change Summary
src/app.rs Updated AppState struct to have mainnet_app_context as Option<Arc<AppContext>>. Refactored initialization logic to handle optional contexts. Updated current_app_context method for error handling.
src/config.rs Modified load method to return Result<Self, ConfigError>. Introduced ConfigError enum for error handling during configuration loading.
src/context.rs Updated AppContext::new method to handle errors from Config::load() using a match statement, ensuring the application exits on configuration errors.
src/ui/network_chooser_screen.rs Changed mainnet_app_context to Option<Arc<AppContext>>. Updated constructor and methods to handle optional contexts, improving error handling and control flow.

Possibly related PRs

  • feat: better error message for having no env file #15: The changes in src/config.rs regarding the error handling mechanism and the load method are related to the main PR's updates in src/app.rs, as both involve handling optional contexts and improving error reporting during configuration loading.
  • feat: dpns subscreens #18: The modifications in src/app.rs that enhance the initialization logic of AppState and the handling of contexts are directly related to the changes made in the main PR, specifically regarding the initialization of mainnet_app_context.

Suggested reviewers

  • QuantumExplorer
  • ogabrielides

Poem

In the burrow where changes bloom,
AppState's context finds more room.
Optional paths now softly tread,
Error handling, no more dread.
With screens that choose the way to go,
Our app hops high, with a joyful glow! 🐇✨


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments.

Actionable comments posted: 6

🧹 Outside diff range and nitpick comments (3)
src/context.rs (1)

38-44: Improved error handling, but consider enhancing user feedback

The addition of error handling for Config::load() is a good improvement. It prevents the application from running with an invalid configuration, which aligns with the PR objective of loading the app with only testnet config.

However, consider the following enhancements:

  1. Use eprintln! instead of println! for error messages, as errors should typically be written to stderr.
  2. Provide more detailed error information to help users troubleshoot configuration issues.
  3. Consider a more graceful error handling approach, especially if this code might be used in a library context.

Here's a suggested improvement:

let config = match Config::load() {
    Ok(config) => config,
    Err(e) => {
        eprintln!("Failed to load config: {}", e);
        eprintln!("Please check your configuration file and ensure it's correctly formatted.");
        eprintln!("If the issue persists, contact support or check the documentation.");
        std::process::exit(1);
    }
};

This change provides more context and guidance to the user while still preventing the application from running with an invalid configuration.

src/config.rs (1)

63-65: Consider changing the error log to a warning when .env file is missing

Since the application can proceed using environment variables even if the .env file is missing, logging an error might not be appropriate. Consider logging a warning instead to indicate that while the .env file was not loaded, the application will continue.

Apply this diff to make the change:

- tracing::error!(
+ tracing::warn!(
    ?err,
    "Failed to load .env file. Continuing with environment variables."
);
src/ui/network_chooser_screen.rs (1)

54-54: Consider handling unexpected Network variants explicitly

Using unreachable!() assumes that all possible Network variants have been covered. If new variants are added to the Network enum in the future, this could lead to unexpected panics.

Consider handling the default case explicitly or returning an error indicating an unsupported network.

🛑 Comments failed to post (6)
src/config.rs (1)

94-102: ⚠️ Potential issue

Correct the conditional logic to ensure all missing configurations are logged

The current use of else if statements means that only one warning will be logged, even if one of the configurations is missing. To log warnings for each missing configuration, replace the else if statements with separate if statements.

Apply this diff to fix the issue:

if mainnet_config.is_none() && testnet_config.is_none() {
    return Err(ConfigError::NoValidConfigs);
}
- else if mainnet_config.is_none() {
+ if mainnet_config.is_none() {
    tracing::warn!("Failed to load mainnet configuration");
}
- else if testnet_config.is_none() {
+ if testnet_config.is_none() {
    tracing::warn!(
        "Failed to load testnet configuration, but successfully loaded mainnet config"
    );
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        if mainnet_config.is_none() && testnet_config.is_none() {
            return Err(ConfigError::NoValidConfigs);
        }
        if mainnet_config.is_none() {
            tracing::warn!("Failed to load mainnet configuration");
        }
        if testnet_config.is_none() {
            tracing::warn!(
                "Failed to load testnet configuration, but successfully loaded mainnet config"
            );
        }
src/ui/network_chooser_screen.rs (2)

48-50: ⚠️ Potential issue

Avoid unwrapping Option without handling None cases

In the context_for_network method, using unwrap() on self.mainnet_app_context without handling the None case can lead to a panic if mainnet_app_context is None. Even though there's a check with is_some(), it's safer to handle the None case explicitly to prevent potential panics.

Apply this diff to handle the Option safely:

- Network::Dash if self.mainnet_app_context.is_some() => {
-     self.mainnet_app_context.as_ref().unwrap()
- }
+ Network::Dash => {
+     match &self.mainnet_app_context {
+         Some(context) => context,
+         None => {
+             // Handle the None case appropriately, e.g., return an error or provide alternative behavior
+         }
+     }
+ }

Committable suggestion was skipped due to low confidence.


145-145: ⚠️ Potential issue

Avoid unwrapping Option without handling None cases

When adding a new wallet, unwrapping self.mainnet_app_context without checking if it is Some can result in a panic if mainnet_app_context is None. It's important to handle the None case to ensure the application remains stable.

Apply this diff to safely handle the Option:

- &self.mainnet_app_context.as_ref().unwrap()
+ match &self.mainnet_app_context {
+     Some(context) => context,
+     None => {
+         // Handle the None case appropriately, e.g., display an error message or disable the action
+     }
+ }

Committable suggestion was skipped due to low confidence.

src/app.rs (3)

111-115: ⚠️ Potential issue

Prevent Panics When Both App Contexts Are None

In the initialization logic, mainnet_app_context and testnet_app_context are unwrapped without checking if they contain None. If both contexts are None, calling .unwrap() will cause a panic. Consider handling the scenario where neither context is available.

Apply this diff to safely handle the absence of both contexts:

 let app_context = if testnet_app_context.is_some() && mainnet_app_context.is_none() {
-    testnet_app_context.clone().unwrap()
+    testnet_app_context.clone().expect("Testnet app context is not available")
 } else {
-    mainnet_app_context.clone().unwrap()
+    mainnet_app_context.clone().expect("Mainnet app context is not available")
 };

Alternatively, you can refactor the logic to handle the case when both contexts might be None:

let app_context = mainnet_app_context.clone()
    .or(testnet_app_context.clone())
    .expect("No available app context (neither mainnet nor testnet)");

186-186: ⚠️ Potential issue

Handle Potential None Values in current_app_context Method

The method current_app_context uses .expect("expected mainnet") and .expect("expected testnet"), which can cause panics if the contexts are None. It's advisable to handle these Option values more gracefully to prevent unintended crashes.

Apply this diff to avoid unintentional panics:

 match self.chosen_network {
     Network::Dash => {
-        self.mainnet_app_context.as_ref().expect("expected mainnet")
+        self.mainnet_app_context.as_ref().unwrap_or_else(|| {
+            panic!("Mainnet app context is not available")
+        })
     },
     Network::Testnet => {
-        self.testnet_app_context.as_ref().expect("expected testnet")
+        self.testnet_app_context.as_ref().unwrap_or_else(|| {
+            panic!("Testnet app context is not available")
+        })
     },
     Network::Devnet => todo!(),
     Network::Regtest => todo!(),
     _ => todo!(),
 }

Consider changing the return type of current_app_context to Option<&Arc<AppContext>> and handling the None case wherever this method is called to enhance robustness.

Committable suggestion was skipped due to low confidence.


43-44: 💡 Codebase verification

⚠️ Potential issue

Potential Panics Due to .unwrap() and .expect() on Optional AppContext Fields

Instances of .unwrap() and .expect() found on mainnet_app_context and testnet_app_context can lead to runtime panics if these options are None. It's recommended to handle these cases gracefully to ensure application stability.

  • src/app.rs:43-44
  • src/ui/network_chooser_screen.rs: [Multiple Lines]
🔗 Analysis chain

Ensure Proper Handling of Optional AppContext

The fields mainnet_app_context and testnet_app_context have been changed to Option<Arc<AppContext>>. It's crucial to handle these optional fields correctly throughout the code to prevent potential None dereferences that could cause panics.

Run the following script to identify any instances where .unwrap() or .expect() are called on these optional fields without prior checks:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all occurrences of `.unwrap()` or `.expect()` on optional `AppContext` fields.

rg 'self\.(mainnet|testnet)_app_context\.as_ref\(\)\.(unwrap|expect)\('

Length of output: 650

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (2)
src/app.rs (2)

Line range hint 169-173: Consider more graceful error handling in current_app_context().

The current implementation uses expect(), which will panic if the required context is missing. While this ensures that the application doesn't continue with an invalid state, it might lead to unexpected crashes.

Consider a more graceful error handling approach:

pub fn current_app_context(&self) -> Result<&Arc<AppContext>, &'static str> {
    match self.chosen_network {
        Network::Dash => self.mainnet_app_context.as_ref().ok_or("Mainnet context not available"),
        Network::Testnet => self.testnet_app_context.as_ref().ok_or("Testnet context not available"),
        Network::Devnet => Err("Devnet not implemented"),
        Network::Regtest => Err("Regtest not implemented"),
        _ => Err("Unknown network"),
    }
}

This approach:

  1. Returns a Result, allowing callers to handle missing contexts more flexibly.
  2. Provides more informative error messages.
  3. Explicitly handles unimplemented network types.

You'll need to update the callers of this method to handle the Result.


Line range hint 1-324: Summary of changes in src/app.rs

The modifications in this file successfully introduce the ability to load the app with only testnet configuration, aligning with the PR objective. Key changes include:

  1. Making mainnet_app_context optional in the AppState struct.
  2. Updating the initialization logic in AppState::new() to handle optional contexts.
  3. Adapting NetworkChooserScreen initialization for optional contexts.
  4. Adjusting the current_app_context() method to work with optional contexts.

While these changes achieve the main goal, there are opportunities to improve error handling and robustness, particularly in the AppState::new() and current_app_context() methods. Implementing the suggested improvements will make the application more resilient to potential configuration issues.

Consider implementing a more comprehensive error handling strategy throughout the application to gracefully manage scenarios where expected contexts are missing. This could involve creating custom error types and using the Result type more extensively to propagate errors up the call stack, allowing for more informed decision-making at higher levels of the application.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between c87c97d and aa51529.

📒 Files selected for processing (1)
  • src/app.rs (4 hunks)
🧰 Additional context used
🔇 Additional comments (2)
src/app.rs (2)

118-121: LGTM: NetworkChooserScreen initialization adapted for optional contexts.

The NetworkChooserScreen::new() call has been correctly updated to pass optional references to contexts, aligning with the changes in the AppState structure. This change supports the possibility of missing contexts, which is consistent with the PR objective.


158-158: LGTM: Chosen network initialization aligned with available context.

The chosen_network is now correctly initialized with app_context.network. This change ensures consistency between the chosen network and the available context, which is important for the proper functioning of the application.

src/app.rs Outdated Show resolved Hide resolved
src/app.rs Show resolved Hide resolved
@pauldelucia pauldelucia marked this pull request as draft October 23, 2024 08:49
@pauldelucia pauldelucia marked this pull request as ready for review October 29, 2024 06:38
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (4)
src/config.rs (4)

Line range hint 63-67: Enhance error logging for .env file loading.

Consider using structured logging fields for better error tracking and analysis.

-            tracing::error!(
-                ?err,
-                "Failed to load .env file. Continuing with environment variables."
-            );
+            tracing::error!(
+                error = ?err,
+                file = ".env",
+                action = "load",
+                "Failed to load configuration file. Continuing with environment variables."
+            );

Line range hint 70-94: Consider refactoring network config loading for better maintainability.

The current implementation duplicates the loading logic for mainnet and testnet configurations. Consider extracting this into a helper function to reduce code duplication and improve maintainability.

+    fn load_network_config(prefix: &str, network: &str) -> Option<NetworkConfig> {
+        match envy::prefixed(prefix).from_env::<NetworkConfig>() {
+            Ok(config) => {
+                tracing::info!(network, "Network configuration loaded successfully");
+                Some(config)
+            }
+            Err(err) => {
+                tracing::error!(
+                    error = ?err,
+                    network,
+                    "Failed to load network configuration"
+                );
+                None
+            }
+        }
+    }
+
     pub fn load() -> Result<Self, ConfigError> {
         if let Err(err) = dotenvy::from_path(".env") {
             // ... existing .env loading code ...
         }

-        let mainnet_config = match envy::prefixed("MAINNET_").from_env::<NetworkConfig>() {
-            Ok(config) => {
-                tracing::info!("Mainnet configuration loaded successfully");
-                Some(config)
-            }
-            Err(err) => {
-                tracing::error!(?err, "Failed to load mainnet configuration");
-                None
-            }
-        };
-
-        let testnet_config = match envy::prefixed("TESTNET_").from_env::<NetworkConfig>() {
-            Ok(config) => {
-                tracing::info!("Testnet configuration loaded successfully");
-                Some(config)
-            }
-            Err(err) => {
-                tracing::error!(?err, "Failed to load testnet configuration");
-                None
-            }
-        };
+        let mainnet_config = Self::load_network_config("MAINNET_", "mainnet");
+        let testnet_config = Self::load_network_config("TESTNET_", "testnet");

Line range hint 96-104: Add configuration validation before returning.

Consider validating the loaded configurations using the existing is_valid() method before returning success.

         if mainnet_config.is_none() && testnet_config.is_none() {
             return Err(ConfigError::NoValidConfigs);
         }

+        // Validate loaded configurations
+        if let Some(config) = &mainnet_config {
+            if !config.is_valid() {
+                tracing::error!(network = "mainnet", "Invalid configuration detected");
+                return Err(ConfigError::LoadError("Invalid mainnet configuration".into()));
+            }
+        }
+
+        if let Some(config) = &testnet_config {
+            if !config.is_valid() {
+                tracing::error!(network = "testnet", "Invalid configuration detected");
+                return Err(ConfigError::LoadError("Invalid testnet configuration".into()));
+            }
+        }
+
         Ok(Config {
             mainnet_config,
             testnet_config,
         })

Line range hint 59-104: Document the partial configuration loading behavior.

The load method now supports partial configuration loading, but this behavior isn't documented. Consider adding documentation to explain this functionality.

+    /// Loads the configuration for all networks from environment variables and `.env` file.
+    ///
+    /// # Partial Loading
+    /// The method supports partial configuration loading, meaning it can succeed with only
+    /// mainnet or only testnet configuration present. This allows for testnet-only or
+    /// mainnet-only deployments.
+    ///
+    /// # Errors
+    /// Returns `ConfigError::NoValidConfigs` if neither mainnet nor testnet configurations
+    /// are successfully loaded.
+    ///
+    /// Returns `ConfigError::LoadError` if a loaded configuration is invalid.
     pub fn load() -> Result<Self, ConfigError> {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between aa51529 and 21b8481.

📒 Files selected for processing (2)
  • src/app.rs (4 hunks)
  • src/config.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/app.rs
🔇 Additional comments (2)
src/config.rs (2)

Line range hint 22-27: Well-structured error handling implementation!

The ConfigError enum follows Rust best practices with clear error messages and appropriate variants for different failure scenarios.


Line range hint 59-60: Verify error handling in dependent code.

The load method now returns a Result instead of Self. Let's verify that all callers properly handle this change.

✅ Verification successful

All callers properly handle the Result return type

The verification shows that the only caller of Config::load() in src/context.rs already implements proper error handling through a match expression that:

  • Handles the success case by using the config
  • Handles the error case by printing the error and exiting the process
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for direct calls to Config::load to ensure proper error handling
rg -A 5 "Config::load\(\)"

Length of output: 329

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant