Skip to content

Commit

Permalink
Map known exceptions to CommandOutcomes #25
Browse files Browse the repository at this point in the history
* Handling duplicate command condition
  • Loading branch information
andrus committed Apr 29, 2017
1 parent 830f4c7 commit 8234fd1
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 11 deletions.
7 changes: 5 additions & 2 deletions bootique/src/main/java/io/bootique/BQCoreModule.java
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import io.bootique.cli.Cli; import io.bootique.cli.Cli;
import io.bootique.command.Command; import io.bootique.command.Command;
import io.bootique.command.CommandManager; import io.bootique.command.CommandManager;
import io.bootique.command.CommandOutcome;
import io.bootique.command.DefaultCommandManager; import io.bootique.command.DefaultCommandManager;
import io.bootique.config.CliConfigurationSource; import io.bootique.config.CliConfigurationSource;
import io.bootique.config.ConfigurationFactory; import io.bootique.config.ConfigurationFactory;
Expand Down Expand Up @@ -312,8 +313,10 @@ CommandManager provideCommandManager(Set<Command> commands,
if (existing != null && existing != c) { if (existing != null && existing != c) {
String c1 = existing.getClass().getName(); String c1 = existing.getClass().getName();
String c2 = c.getClass().getName(); String c2 = c.getClass().getName();
throw new RuntimeException(
String.format("Duplicate command for name %s (provided by: %s and %s) ", name, c1, c2)); String message = String.format("More than one DI command named '%s'. Conflicting types: %s, %s.",
name, c1, c2);
throw new BootiqueException(CommandOutcome.failed(1, message));
} }
} }
}); });
Expand Down
20 changes: 13 additions & 7 deletions bootique/src/main/java/io/bootique/jopt/JoptCliProvider.java
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
import com.google.inject.Provider; import com.google.inject.Provider;
import io.bootique.BootiqueException; import io.bootique.BootiqueException;
import io.bootique.annotation.Args; import io.bootique.annotation.Args;
import io.bootique.command.CommandOutcome;
import io.bootique.meta.application.ApplicationMetadata;
import io.bootique.meta.application.OptionMetadata;
import io.bootique.cli.Cli; import io.bootique.cli.Cli;
import io.bootique.command.Command; import io.bootique.command.Command;
import io.bootique.command.CommandManager; import io.bootique.command.CommandManager;
import io.bootique.command.CommandOutcome;
import io.bootique.log.BootLogger; import io.bootique.log.BootLogger;
import io.bootique.meta.application.ApplicationMetadata;
import io.bootique.meta.application.OptionMetadata;
import joptsimple.OptionException; import joptsimple.OptionException;
import joptsimple.OptionParser; import joptsimple.OptionParser;
import joptsimple.OptionSet; import joptsimple.OptionSet;
Expand All @@ -29,15 +29,21 @@ public class JoptCliProvider implements Provider<Cli> {
private String[] args; private String[] args;
private BootLogger bootLogger; private BootLogger bootLogger;
private ApplicationMetadata application; private ApplicationMetadata application;
private CommandManager commandManager; private Provider<CommandManager> commandManagerProvider;



@Inject @Inject
public JoptCliProvider(BootLogger bootLogger, public JoptCliProvider(BootLogger bootLogger,
CommandManager commandManager, Provider<CommandManager> commandManagerProvider,
ApplicationMetadata application, ApplicationMetadata application,
@Args String[] args) { @Args String[] args) {


this.commandManager = commandManager; // injecting CommandManager via provider for an obscure reason - it is injected here and also in
// ApplicationMetadata provider (and this class depends on ApplicationMetadata). So when there's an error
// during CommandManager construction, it is thrown twice, causing ProvisionException to lose its "cause",
// complicating exception analysis.

this.commandManagerProvider = commandManagerProvider;
this.application = application; this.application = application;
this.args = args; this.args = args;
this.bootLogger = bootLogger; this.bootLogger = bootLogger;
Expand Down Expand Up @@ -103,7 +109,7 @@ protected void addOption(OptionParser parser, OptionMetadata option) {
protected String commandName(OptionSet optionSet) { protected String commandName(OptionSet optionSet) {


Map<String, Command> matches = new HashMap<>(3); Map<String, Command> matches = new HashMap<>(3);
commandManager.getCommands().forEach((name, c) -> { commandManagerProvider.get().getCommands().forEach((name, c) -> {
if (optionSet.has(name) && !optionSet.hasArgument(name)) { if (optionSet.has(name) && !optionSet.hasArgument(name)) {
matches.put(name, c); matches.put(name, c);
} }
Expand Down
Original file line number Original file line Diff line number Diff line change
@@ -1,8 +1,12 @@
package io.bootique; package io.bootique;


import com.google.inject.Binder;
import com.google.inject.CreationException; import com.google.inject.CreationException;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Provides;
import com.google.inject.ProvisionException; import com.google.inject.ProvisionException;
import com.google.inject.Singleton;
import io.bootique.cli.Cli; import io.bootique.cli.Cli;
import io.bootique.command.Command; import io.bootique.command.Command;
import io.bootique.command.CommandOutcome; import io.bootique.command.CommandOutcome;
Expand Down Expand Up @@ -95,10 +99,87 @@ public void testConfig_BadUrlProtocol() {
assertEquals("Invalid config resource url: no_such_protocol://myconfig", out.getMessage()); assertEquals("Invalid config resource url: no_such_protocol://myconfig", out.getMessage());
} }


@Test
public void testDI_ProviderMethodException() {
CommandOutcome out = Bootique.app("-m")
.module(new ModuleWithProviderMethodException())
.exec();

assertEquals(1, out.getExitCode());
assertTrue(out.getException() instanceof ProvisionException);
assertEquals("test provider exception", out.getMessage());
}

@Test
public void testDI_TwoCommandsSameName() {
CommandOutcome out = Bootique.app("-x")
.module(b -> BQCoreModule.extend(b)
.addCommand(new Command() {

@Override
public CommandMetadata getMetadata() {
return CommandMetadata.builder("xcommand").build();
}

@Override
public CommandOutcome run(Cli cli) {
return CommandOutcome.succeeded();
}
})
.addCommand(new Command() {

@Override
public CommandMetadata getMetadata() {
return CommandMetadata.builder("xcommand").build();
}

@Override
public CommandOutcome run(Cli cli) {
return CommandOutcome.succeeded();
}
})
)
.exec();

assertEquals(1, out.getExitCode());
assertTrue(out.getException() instanceof ProvisionException);
assertEquals("More than one DI command named 'xcommand'. Conflicting types: " +
"io.bootique.BootiqueExceptionsHandlerIT$4, io.bootique.BootiqueExceptionsHandlerIT$3.",
out.getMessage());
}

public static class ConfigDependent { public static class ConfigDependent {


@Inject @Inject
public ConfigDependent(ConfigurationFactory factory) { public ConfigDependent(ConfigurationFactory factory) {
} }
} }

public static class ModuleWithProviderMethodException implements Module {

public static class MyCommand implements Command {

@Override
public CommandMetadata getMetadata() {
return CommandMetadata.builder(MyCommand.class).build();
}

@Override
public CommandOutcome run(Cli cli) {
return null;
}
}

@Override
public void configure(Binder binder) {
BQCoreModule.extend(binder).addCommand(MyCommand.class);
}

@Provides
@Singleton
public MyCommand provideCommand() {
throw new BootiqueException(CommandOutcome.failed(1, "test provider exception"));
}
}

} }
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ private Cli createCli(String args) {
ApplicationMetadata.Builder appBuilder = ApplicationMetadata.builder(); ApplicationMetadata.Builder appBuilder = ApplicationMetadata.builder();
commands.values().forEach(c -> appBuilder.addCommand(c.getMetadata())); commands.values().forEach(c -> appBuilder.addCommand(c.getMetadata()));


return new JoptCliProvider(mockBootLogger, commandManager, appBuilder.build(), argsArray).get(); return new JoptCliProvider(mockBootLogger, () -> commandManager, appBuilder.build(), argsArray).get();
} }


} }
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -120,6 +120,6 @@ private Cli createCli(String args) {
ApplicationMetadata.Builder appBuilder = ApplicationMetadata.builder(); ApplicationMetadata.Builder appBuilder = ApplicationMetadata.builder();
commands.values().forEach(c -> appBuilder.addCommand(c.getMetadata())); commands.values().forEach(c -> appBuilder.addCommand(c.getMetadata()));


return new JoptCliProvider(mockBootLogger, commandManager, appBuilder.build(), argsArray).get(); return new JoptCliProvider(mockBootLogger, () -> commandManager, appBuilder.build(), argsArray).get();
} }
} }

0 comments on commit 8234fd1

Please sign in to comment.