Skip to content

Error handling and helper

Jonathan edited this page Oct 25, 2020 · 1 revision

KWCommands never throws an exception, unless something went really wrong (because we don't put throw in KWCommands code). To threat KWCommands failures, you must check the value returned by parseAndDispatch and similar functions used to parse and dispatch commands.

ParseFail

This is the failure object returned when something goes wrong in KWCommands, from parsing failure to dispatch failure, but exceptions thrown by command and arguments handlers will not be caught.

Handling ParseFail

HelpInfoHandler

You could simply switch every ParseFail case and treat them accordingly, but if you only want to show a help information to user, you could use HelpInfoHandler, lets see an exemple:

public class KwDocsListArgTypes {

    public static void main(String[] args) {
        HelpInfoHandler helpInfoHandler = new CommonHelpInfoHandler();
        CommandProcessor processor = Processors.createCommonProcessor();
        Command command = Command.builder()
                .name("play")
                .addAlias("request")
                .description(Text.of("Request a song"))
                .staticArguments()
                .addArgument(Argument.builder()
                        .name("song")
                        .argumentType(
                                new ListArgumentType<>(
                                        CommonArgTypesKt.enumArgumentType(Music.class),
                                        TypeInfo.builderOf(List.class).of(Music.class).buildGeneric()
                                )
                        )
                        .build()
                )
                .build()
                .handler((commandContainer, informationProviders, resultHandler) -> {
                    List<String> songs = commandContainer.<List<Music>>getArgumentValue("song").stream()
                            .map(it -> it.name().toLowerCase())
                            .collect(Collectors.toList());

                    System.out.printf("...Requested songs: %s ...%n", String.join(", ", songs));
                    throw new RuntimeException();
                })
                .build();


        processor.getCommandManager().registerCommand(command, new KwDocsListArgTypes());

        processor.parseAndDispatch("play this is beautifuxl", null, InformationProvidersVoid.INSTANCE).ifLeft(parseFail -> {
            helpInfoHandler.handleFail(parseFail, Printers.INSTANCE.getSysOut());
        });

    }

}

This will print:

-------- Commands --------

--------   Label  --------
 [ ]  = Required argument
 < >  = Optional argument
  !   = Dynamic argument (depends on others arguments)
  ->  = Main command
 -'>  = Sub command
  -   = Description
 - x: = Description of argument x
  *   = Information Requirement
  **  = Requirement
--------   Label  --------

Command with name 'beautifuxl' was not found.
Processed commands: 'play ListInput(input=[SingleInput(input='this', source='play this is beautifuxl', start='5', end='8', content='this'), SingleInput(input='is', source='play this is beautifuxl', start='10', end='11', content='is')], source='play this is beautifuxl', start='5', end='11', content='this is')'
Available Commands:
-> play [song: List<Music>]       - Request a song
       - Aliases: request


-------- Commands --------

Also, you could handle CommandResult too, for example, for when an requirement is not met, or an Information is not present:

public class KwDocsListArgTypes {

    public static void main(String[] args) {
        HelpInfoHandler helpInfoHandler = new CommonHelpInfoHandler();
        CommandProcessor processor = Processors.createCommonProcessor();
        Command command = Command.builder()
                .name("play")
                .addAlias("request")
                .description(Text.of("Request a song"))
                .staticArguments()
                .addArgument(Argument.builder()
                        .name("song")
                        .argumentType(
                                new ListArgumentType<>(
                                        CommonArgTypesKt.enumArgumentType(Music.class),
                                        TypeInfo.builderOf(List.class).of(Music.class).buildGeneric()
                                )
                        )
                        .build()
                )
                .build()
                .addRequiredInfo(new RequiredInformation(new Information.Id<>(TypeInfo.of(String.class), new String[]{"a"})))
                .handler((commandContainer, informationProviders, resultHandler) -> {
                    List<String> songs = commandContainer.<List<Music>>getArgumentValue("song").stream()
                            .map(it -> it.name().toLowerCase())
                            .collect(Collectors.toList());

                    System.out.printf("...Requested songs: %s ...%n", String.join(", ", songs));
                    throw new RuntimeException();
                })
                .build();


        processor.getCommandManager().registerCommand(command, new KwDocsListArgTypes());

        Either<ParseFail, List<CommandResult>> result = processor.parseAndDispatch("play this is beautiful", null, InformationProvidersVoid.INSTANCE);
        result.ifRight(commandResults -> {
            helpInfoHandler.handleResults(commandResults, Printers.INSTANCE.getSysOut());
        });
        result.ifLeft(parseFail -> {
            helpInfoHandler.handleFail(parseFail, Printers.INSTANCE.getSysOut());
        });

    }

}

Will print:

-------- Commands --------

--------   Label  --------
 [ ]  = Required argument
 < >  = Optional argument
  !   = Dynamic argument (depends on others arguments)
  ->  = Main command
 -'>  = Sub command
  -   = Description
 - x: = Description of argument x
  *   = Information Requirement
  **  = Requirement
--------   Label  --------

Missing information of 'command play':
   Type: String. Tags: a. Include provided: True.

-------- Commands --------

Also, if you provide an invalid argument (and it is in a context which KWCommands could not skip the invalid argument), the Helper will provide more information about the error, take a look at this scenario:

public class KwDocsErrorHandling {

    public static void main(String[] args) {
        HelpInfoHandler helpInfoHandler = new CommonHelpInfoHandler();
        CommandProcessor processor = Processors.createCommonProcessor();
        Command command = Command.builder()
                .name("play")
                .addAlias("request")
                .description(Text.of("Request a song"))
                .staticArguments()
                .addArgument(Argument.builder()
                        .name("song")
                        .argumentType(
                                CommonArgTypesKt.enumArgumentType(Music.class)
                        )
                        .build()
                )
                .build()
                .addRequiredInfo(new RequiredInformation(new Information.Id<>(TypeInfo.of(String.class), new String[]{"a"})))
                .handler((commandContainer, informationProviders, resultHandler) -> {
                    String song = commandContainer.<Music>getArgumentValue("song").name().toLowerCase();

                    System.out.printf("...Requested song: %s ...%n", song);
                    throw new RuntimeException();
                })
                .build();


        processor.getCommandManager().registerCommand(command, new KwDocsErrorHandling());

        Either<ParseFail, List<CommandResult>> result = processor.parseAndDispatch("play beautifulad", null, InformationProvidersVoid.INSTANCE);
        result.ifRight(commandResults -> {
            helpInfoHandler.handleResults(commandResults, Printers.INSTANCE.getSysOut());
        });
        result.ifLeft(parseFail -> {
            helpInfoHandler.handleFail(parseFail, Printers.INSTANCE.getSysOut());
        });

    }

}

The Helper will print:

-------- Commands --------

--------   Label  --------
 [ ]  = Required argument
 < >  = Optional argument
  !   = Dynamic argument (depends on others arguments)
  ->  = Main command
 -'>  = Sub command
  -   = Description
 - x: = Description of argument x
  *   = Information Requirement
  **  = Requirement
--------   Label  --------

Invalid input value 'beautifulad' provided for argument 'song' of command 'play'.

Invalid inputs:

 | [5..15]: play beautifulad | Input: beautifulad
 |               ^_________^
 | Type of inserted value: Single value
 | Argument type: Music
 | Valid input types: Single value
 | Parser: parser.enumparser

Argument type: Music
  - Argument possibilities: 
  - THIS
  - IS
  - A
  - BEAUTIFUL
  - MUSIC

Command specification: 
-> play [song: Music]       - Request a song
       - Aliases: request
   Requirements:
    Command:
     * Requires information 'Id(type=String, tags=[a])'.


-------- Commands --------

Helper is a very easy way of handling errors when giving a feedback is only what you need, you could also remove headers display and write your own Printer (which currently I've made in this tutorial because LocaleLoader is not working in Windows. KWCommands provides language-based messages, so you could customize them by creating your own language file.

Helper is also able to provide errors about malformed inputs, for example:

Trying to construct a map missing the declaration character, such as = or ::

processor.parseAndDispatch("play {this>master, is=in, beautiful=cover}", null, InformationProvidersVoid.INSTANCE);

// Prints:

Malformed input for type 'Map of values'

 | [5..19]: play {this>master, is=in, beautiful=cover}
 |               ^_____________^
 | Valid input types: Map of values
 | Expected tokens: ':', '='.
 | Found token: 'i'.

Not closing the map:

processor.parseAndDispatch("play {this=master, is=in, beautiful=cover", null, InformationProvidersVoid.INSTANCE);

// Prints:

Malformed input for type 'Map of values'

 | [5..40]: play {this=master, is=in, beautiful=cover
 |               ^__________________________________^
 | Valid input types: Map of values
 | Expected tokens: ',', '}'.
 | Found token: ''.
 | Successfully parsed map entries: {this=master, is=in, beautiful=cover}.

Yes, even if it can correctly parse the map entries, it will not allow you to keep the map unclosed.

If you put a comma without a pair:

processor.parseAndDispatch("play {this=master, is=in, , beautiful=cover}", null, InformationProvidersVoid.INSTANCE);

// Prints:

Malformed input for type 'Map of values'

 | [5..28]: play {this=master, is=in, , beautiful=cover}
 |               ^______________________^
 | Valid input types: Map of values
 | Expected tokens: ':', '='.
 | Found token: 'b'.
 | Successfully parsed map entries: {this=master, is=in}.

Error messages still being improved, but because of the flebixility of parsing, there is needed some tricks to achieve the error messages we want to, for example:

processor.parseAndDispatch("play {this=master, is=in, ,=c, beautiful=cover}", null, InformationProvidersVoid.INSTANCE);

Is correctly parsed into: [SingleInput(play), MapInput((this = master), (is = in), (, = c), (beautiful = cover))] because parser allows you to use special chars in keys and values, after you signaled that you is specifying the next pair (by adding a comma after in), it will take next chars as entry key until it finds either = or :, this gives some liberty to the developer, but makes it harder to show error messages.

There is also some corner cases that are not already covered, such as #24 for example, but we are working to make KWCommands always return maningful error types and messages instead of meaningless exceptions.

Handling it yourself

You could handle errors yourself instead of relying on HelpInfoHandler, see a simple example below:

Either<ParseFail, List<CommandResult>> result = processor.parseAndDispatch("play [this, is, beautiful", null, InformationProvidersVoid.INSTANCE);

if (result.isLeft()) {
    When<ParseFail, String> when = When.When(result.getLeft(),
            When.InstanceOf(InputedParseFail.class, fail -> String.format("Failure parsing CLS: %s%n", fail.getInput())),
            When.InstanceOf(SourcedParseFail.class, fail -> String.format("Failure in post-parsing: %s%n", fail.getSource()))
    );

    OptObject<String> evaluate = when.evaluate();

    System.out.println(evaluate.toOptional());
}

You should also remember to handle CommandResult too, because they provide dispatch failure information (when Requirement is not met or Required Information is missing).

Clone this wiki locally