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

Option to put generated context selectors into a submodule #188

Closed
LPGhatguy opened this issue Oct 22, 2019 · 3 comments · Fixed by #295
Closed

Option to put generated context selectors into a submodule #188

LPGhatguy opened this issue Oct 22, 2019 · 3 comments · Fixed by #295
Labels
enhancement New feature or request feedback requested User feedback desired on a design decision good first issue Good for newcomers help wanted Extra attention is needed

Comments

@LPGhatguy
Copy link

First, thanks for making SNAFU, I'm really enjoying its ergonomics!

I started a new project and tried to create a couple nested errors, but hit a name collision between the selectors that SNAFU was generating and one of my existing structs.

My inner module looks like this:

pub struct Config {
    // ...
}

#[derive(Debug, Snafu)]
pub enum ConfigError {
    // ... 
}

impl Config {
    pub fn load() -> Result<Self, ConfigError> {
        // ...
    }
}

My outer module imports a couple things from this struct and exports a new error type that wraps ConfigError:

use crate::{Config, ConfigError};

#[derive(Debug, Snafu)]
pub enum SyncError {
    Config { // oh no, a name collision!
        source: ConfigError,
    }
}

pub fn sync() -> Result<(), SyncError> {
    let config = Config::load().context(Config)?; // Collision here, too!

    // ...
}

The Snafu derive macro on SyncError tries to define a struct named Config as the context selector, which ends up colliding with the struct Config that I imported before!

One solution for this particular case I thought of was to put my SNAFU error into its own mod block to scope everything the macro defines. Combined with #[snafu(visibility)] and re-exporting the error type, this seems to work:

use crate::Config;

mod error {
    use crate::config::ConfigError;
    use snafu::Snafu;

    #[derive(Debug, Snafu)]
    #[snafu(visibility = "pub(super)")]
    pub enum SyncError {
        Config {
            source: ConfigError,
        },
    }
}

pub use error::SyncError;

pub fn sync() -> Result<(), SyncError> {
    let config = Config::load().context(error::Config)?;

    // ...
}

Another way we could solve this is by adding a configuration option like #[snafu(mod = "error")] to instruct SNAFU to wrap all of its context selectors into a module with the given name.

Does this feature make sense? Is there a better solution to this particular problem that I'm not seeing?

Thanks!

@shepmaster shepmaster added enhancement New feature or request good first issue Good for newcomers help wanted Extra attention is needed feedback requested User feedback desired on a design decision labels Nov 6, 2019
@shepmaster
Copy link
Owner

shepmaster commented Nov 6, 2019

Yes, I think such an option does make sense. I expect that most people who have run into the problem have done as you suggest: put it in a module "by hand" and perform the relevant exports.

I think that your general sketch of the resulting code also is the good default when this is opted-in-to: create a module (name chosen by user), put all the context selectors in that module, then mark them as pub(super). The error itself stays where it is.

There will undoubtedly be some nuances that are run into during development, but this seems like it's in a good state to start work on.

Would you be interested in taking a crack at it?

@HeapUnderfl0w
Copy link

i actually wanted to suggest the same thing, main difference i would prefer is it to be named something like namespace (because you are basically "namespacing" the variants)

@LPGhatguy
Copy link
Author

LPGhatguy commented Dec 19, 2019

@shepmaster Sorry I haven't gotten back sooner! I'm interested in helping build this, but after using SNAFU in more projects I wonder if it's the right solution to the name-collision problem.

Does it make sense for SNAFU to generate the context selectors with a name that doesn't match the enum variants directly? Could we introduce an enum-variant attribute to change the generated selector name?

SNAFU could automatically name your selectors in a way that isn't your enum variant verbatim (which would obviously be a breaking change), SNAFU could name the selector FooContext or FooCtx.

For manual names, it could be reasonable to let the user specify:

use crate::{Config, ConfigError};

#[derive(Debug, Snafu)]
pub enum SyncError {
    #[snafu(selector = ConfigContext)]
    Config {
        source: ConfigError,
    }
}

pub fn sync() -> Result<(), SyncError> {
    let config = Config::load().context(ConfigContext)?; // All good now!

    // ...
}

This is a smaller hammer than namespacing all the context selectors. Alternatively, we could implement this feature as proposed, and then encourage users to then re-export their selectors. That would look like this:

use crate::{Config, ConfigError};

#[derive(Debug, Snafu)]
#[snafu(mod = "error")]
pub enum SyncError {
    Config {
        source: ConfigError,
    }
}

// We can't glob-import the context selectors if we need to rename any of them.
use error::Config as ConfigContext;

pub fn sync() -> Result<(), SyncError> {
    let config = Config::load().context(ConfigContext)?; // All good now!

    // ...
}

SamWilsn added a commit to SamWilsn/snafu that referenced this issue Jul 6, 2021
The new attribute `module` makes the derive macro put all the context
selectors for structs and enums in a module.
SamWilsn added a commit to SamWilsn/snafu that referenced this issue Jul 6, 2021
The new attribute `module` makes the derive macro put all the context
selectors for structs and enums in a module.
SamWilsn added a commit to SamWilsn/snafu that referenced this issue Jul 7, 2021
The new attribute `module` makes the derive macro put all the context
selectors for structs and enums in a module.
shepmaster pushed a commit to SamWilsn/snafu that referenced this issue Sep 15, 2021
The new attribute `module` makes the derive macro put all the context
selectors for structs and enums in a module.

Closes shepmaster#188
@shepmaster shepmaster linked a pull request Sep 15, 2021 that will close this issue
shepmaster pushed a commit to SamWilsn/snafu that referenced this issue Sep 29, 2021
The new attribute `module` makes the derive macro put all the context
selectors for structs and enums in a module.

Closes shepmaster#188
shepmaster pushed a commit to SamWilsn/snafu that referenced this issue Oct 23, 2021
The new attribute `module` makes the derive macro put all the context
selectors for structs and enums in a module.

Closes shepmaster#188
shepmaster pushed a commit to SamWilsn/snafu that referenced this issue Nov 13, 2021
The new attribute `module` makes the derive macro put all the context
selectors for structs and enums in a module.

Closes shepmaster#188
shepmaster pushed a commit to SamWilsn/snafu that referenced this issue Nov 13, 2021
The new attribute `module` makes the derive macro put all the context
selectors for structs and enums in a module.

Closes shepmaster#188
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request feedback requested User feedback desired on a design decision good first issue Good for newcomers help wanted Extra attention is needed
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants