Skip to content

Commit

Permalink
Initial work on improved error handling (#53)
Browse files Browse the repository at this point in the history
Adds a new ParserErrorHandler interface and incorporates it into the
ParserMetadata.  Doesn't yet utilise it properly
  • Loading branch information
rvesse committed Nov 25, 2016
1 parent 9578f89 commit 3d024a2
Show file tree
Hide file tree
Showing 10 changed files with 326 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
import com.github.rvesse.airline.SingleCommand;
import com.github.rvesse.airline.TypeConverter;
import com.github.rvesse.airline.model.ParserMetadata;
import com.github.rvesse.airline.parser.errors.handlers.FailFast;
import com.github.rvesse.airline.parser.errors.handlers.ParserErrorHandler;
import com.github.rvesse.airline.parser.options.OptionParser;

/**
Expand Down Expand Up @@ -109,7 +111,7 @@
*
* @return Command aliases
*/
Alias[]aliases() default {};
Alias[] aliases() default {};

/**
* Defines the name of a file from which user defined command aliases should
Expand All @@ -134,7 +136,7 @@
*
* @return
*/
String[]userAliasesSearchLocation() default "";
String[] userAliasesSearchLocation() default "";

/**
* Sets the prefix used for properties that define aliases
Expand Down Expand Up @@ -172,20 +174,28 @@
* @return Option parser classes
*/
@SuppressWarnings("rawtypes")
Class<? extends OptionParser>[]optionParsers() default {};
Class<? extends OptionParser>[] optionParsers() default {};

/**
* Sets the command factory class to use
*
* @return Command factory class
*/
@SuppressWarnings("rawtypes")
Class<? extends CommandFactory>commandFactory() default DefaultCommandFactory.class;
Class<? extends CommandFactory> commandFactory() default DefaultCommandFactory.class;

/**
* Sets the type converter class to use
*
* @return Type converter class
*/
Class<? extends TypeConverter>typeConverter() default DefaultTypeConverter.class;
Class<? extends TypeConverter> typeConverter() default DefaultTypeConverter.class;

/**
* Sets the error handler to use, defaults to {@code FailFast} which throws
* errors as soon as they are encountered
*
* @return Error handler to use
*/
Class<? extends ParserErrorHandler> errorHandler() default FailFast.class;
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import com.github.rvesse.airline.model.AliasMetadata;
import com.github.rvesse.airline.model.ParserMetadata;
import com.github.rvesse.airline.parser.aliases.UserAliasesSource;
import com.github.rvesse.airline.parser.errors.handlers.ParserErrorHandler;
import com.github.rvesse.airline.parser.options.ClassicGetOptParser;
import com.github.rvesse.airline.parser.options.LongGetOptParser;
import com.github.rvesse.airline.parser.options.OptionParser;
Expand All @@ -47,6 +48,7 @@ public class ParserBuilder<C> extends AbstractBuilder<ParserMetadata<C>> {
protected final List<OptionParser<C>> optionParsers = new ArrayList<>();
protected String argsSeparator;
protected UserAliasesSource<C> userAliases;
protected ParserErrorHandler errorHandler;

public static <T> ParserMetadata<T> defaultConfiguration() {
return new ParserBuilder<T>().build();
Expand Down Expand Up @@ -217,6 +219,16 @@ public ParserBuilder<C> withDefaultTypeConverter() {
return this;
}

public ParserBuilder<C> withErrorHandler(ParserErrorHandler errorHandler) {
this.errorHandler = errorHandler;
return this;
}

public ParserBuilder<C> withDefaultErrorHandler() {
this.errorHandler = null;
return this;
}

/**
* Configures the CLI to use the given option parser
* <p>
Expand Down Expand Up @@ -344,7 +356,8 @@ public ParserMetadata<C> build() {
aliasData = new ArrayList<>();
}

return new ParserMetadata<C>(commandFactory, optionParsers, typeConverter, allowAbbreviatedCommands,
allowAbbreviatedOptions, aliasData, userAliases, aliasesOverrideBuiltIns, aliasesMayChain, argsSeparator);
return new ParserMetadata<C>(commandFactory, optionParsers, typeConverter, errorHandler,
allowAbbreviatedCommands, allowAbbreviatedOptions, aliasData, userAliases, aliasesOverrideBuiltIns,
aliasesMayChain, argsSeparator);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.github.rvesse.airline.help.sections.factories.HelpSectionRegistry;
import com.github.rvesse.airline.help.suggester.Suggester;
import com.github.rvesse.airline.parser.ParserUtil;
import com.github.rvesse.airline.parser.errors.handlers.FailFast;
import com.github.rvesse.airline.parser.options.OptionParser;
import com.github.rvesse.airline.restrictions.ArgumentsRestriction;
import com.github.rvesse.airline.restrictions.GlobalRestriction;
Expand Down Expand Up @@ -88,6 +89,11 @@ private static <C> ParserMetadata<C> loadParser(Parser parserConfig) {
} else {
builder = builder.withDefaultCommandFactory();
}
if (!parserConfig.errorHandler().equals(FailFast.class)) {
builder = builder.withErrorHandler(ParserUtil.createInstance(parserConfig.errorHandler()));
} else {
builder = builder.withDefaultErrorHandler();
}

// Abbreviation options
if (parserConfig.allowCommandAbbreviation()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import com.github.rvesse.airline.TypeConverter;
import com.github.rvesse.airline.DefaultTypeConverter;
import com.github.rvesse.airline.parser.aliases.UserAliasesSource;
import com.github.rvesse.airline.parser.errors.handlers.FailFast;
import com.github.rvesse.airline.parser.errors.handlers.ParserErrorHandler;
import com.github.rvesse.airline.parser.options.OptionParser;
import com.github.rvesse.airline.utils.AirlineUtils;

Expand All @@ -44,16 +46,20 @@ public class ParserMetadata<T> {
private final TypeConverter typeConverter;
private final CommandFactory<T> commandFactory;
private final String argsSeparator;
private final ParserErrorHandler errorHandler;

public ParserMetadata(CommandFactory<T> commandFactory, List<OptionParser<T>> optionParsers,
TypeConverter typeConverter, boolean allowAbbreviateCommands, boolean allowAbbreviatedOptions,
List<AliasMetadata> aliases, UserAliasesSource<T> userAliases, boolean aliasesOverrideBuiltIns,
boolean aliasesMayChain, String argumentsSeparator) {
TypeConverter typeConverter, ParserErrorHandler errorHandler, boolean allowAbbreviateCommands,
boolean allowAbbreviatedOptions, List<AliasMetadata> aliases, UserAliasesSource<T> userAliases,
boolean aliasesOverrideBuiltIns, boolean aliasesMayChain, String argumentsSeparator) {
if (optionParsers == null)
throw new NullPointerException("optionParsers cannot be null");
if (aliases == null)
throw new NullPointerException("aliases cannot be null");

// Error handling
this.errorHandler = errorHandler != null ? errorHandler : new FailFast();

// Command parsing
this.commandFactory = commandFactory != null ? commandFactory : new DefaultCommandFactory<T>();
this.allowAbbreviatedCommands = allowAbbreviateCommands;
Expand Down Expand Up @@ -97,6 +103,15 @@ public TypeConverter getTypeConverter() {
return typeConverter;
}

/**
* Gets the error handler to use
*
* @return Error handler
*/
public ParserErrorHandler getErrorHandler() {
return errorHandler;
}

/**
* Gets the defined command aliases
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* Copyright (C) 2010-16 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.github.rvesse.airline.parser;

import java.util.Collection;
import java.util.Collections;

import com.github.rvesse.airline.parser.errors.ParseException;

/**
* Represents parsing results
*
* @author rvesse
*
* @param <T>
* Command type
*/
public class ParseResult<T> {
private final ParseState<T> state;
private final Collection<ParseException> errors;

public ParseResult(ParseState<T> state, Collection<ParseException> errors) {
if (state == null)
throw new NullPointerException("state cannot be null");
this.state = state;
this.errors = errors != null ? Collections.<ParseException> unmodifiableCollection(errors)
: Collections.<ParseException> emptyList();
}

/**
* Indicates whether parsing was successful
*
* @return True if successful, false if any errors occurred
*/
public boolean wasSuccessful() {
return this.errors.size() == 0;
}

/**
* Gets the final parser state
*
* @return Parser state
*/
public ParseState<T> getState() {
return this.state;
}

/**
* Gets the collection of errors that occurred, may be empty if parsing was
* successful
*
* @return Errors
*/
public Collection<ParseException> getErrors() {
return this.errors;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Copyright (C) 2010-16 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.rvesse.airline.parser.errors.handlers;

import java.util.ArrayList;
import java.util.List;

import com.github.rvesse.airline.parser.errors.ParseException;

public abstract class AbstractCollectingHandler implements ParserErrorHandler {

protected List<ParseException> errors = new ArrayList<>();

public AbstractCollectingHandler() {
super();
}

@Override
public void handleError(ParseException e) {
this.errors.add(e);
}

protected List<ParseException> getCollection() {
return this.errors;
}

protected void resetCollection() {
this.errors = new ArrayList<>();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Copyright (C) 2010-16 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.rvesse.airline.parser.errors.handlers;

import com.github.rvesse.airline.parser.ParseResult;
import com.github.rvesse.airline.parser.ParseState;

/**
* Error handler which collects all the errors for later inspection
*
* @author rvesse
*
*/
public class CollectAll extends AbstractCollectingHandler {

@Override
public <T> ParseResult<T> finished(ParseState<T> state) {
ParseResult<T> result = new ParseResult<>(state, getCollection());
resetCollection();
return result;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* Copyright (C) 2010-16 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.rvesse.airline.parser.errors.handlers;

import java.util.Collection;

import com.github.rvesse.airline.parser.ParseResult;
import com.github.rvesse.airline.parser.ParseState;
import com.github.rvesse.airline.parser.errors.ParseException;

public class FailAll extends AbstractCollectingHandler {

@Override
public <T> ParseResult<T> finished(ParseState<T> state) {
Collection<ParseException> errors = getCollection();
if (errors.size() == 1) {
throw errors.iterator().next();
} else if (errors.size() > 1) {
ParseException aggEx = new ParseException("Parsing encountered %d error(s)", errors.size());
for (ParseException e : errors) {
aggEx.addSuppressed(e);
}
throw aggEx;
} else {
return new ParseResult<>(state, null);
}
}

}
Loading

0 comments on commit 3d024a2

Please sign in to comment.