Skip to content

Commit

Permalink
feat: introduce Either as a parseable type (#647)
Browse files Browse the repository at this point in the history
* feat: introduce `Either` as a parseable type

* Add null checks in either factories

* Move either parser factory down

* Rename annotations test

---------

Co-authored-by: Jason Penilla <[email protected]>
  • Loading branch information
Citymonstret and jpenilla authored Jan 20, 2024
1 parent e35de97 commit 6cd028e
Show file tree
Hide file tree
Showing 9 changed files with 580 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,13 @@
import cloud.commandframework.arguments.ComponentPreprocessor;
import cloud.commandframework.arguments.DefaultValue;
import cloud.commandframework.arguments.parser.ArgumentParser;
import cloud.commandframework.arguments.parser.EitherParser;
import cloud.commandframework.arguments.parser.ParserDescriptor;
import cloud.commandframework.arguments.parser.ParserParameters;
import cloud.commandframework.arguments.suggestion.Suggestion;
import cloud.commandframework.arguments.suggestion.SuggestionProvider;
import cloud.commandframework.types.Either;
import io.leangen.geantyref.GenericTypeReflector;
import io.leangen.geantyref.TypeToken;
import java.lang.annotation.Annotation;
import java.lang.reflect.Parameter;
Expand Down Expand Up @@ -74,7 +78,41 @@ public ArgumentAssemblerImpl(final @NonNull AnnotationParser<C> annotationParser
.parseAnnotations(token, annotations);
/* Create the argument parser */
final ArgumentParser<C, ?> parser;
if (descriptor.parserName() == null) {
if (GenericTypeReflector.isSuperType(Either.class, token.getType())) {
final TypeToken<?> primaryType = TypeToken.get(GenericTypeReflector.getTypeParameter(
parameter.getParameterizedType(),
Either.class.getTypeParameters()[0]
));
final TypeToken<?> fallbackType = TypeToken.get(GenericTypeReflector.getTypeParameter(
parameter.getParameterizedType(),
Either.class.getTypeParameters()[1]
));
final ParserDescriptor<C, ?> primary = this.annotationParser.manager()
.parserRegistry()
.createParser(primaryType, parameters)
.map(primaryParser -> ParserDescriptor.of(primaryParser, (TypeToken) primaryType))
.orElseThrow(() -> new IllegalArgumentException(
String.format(
"Parameter '%s' "
+ "has parser 'Either<%s, ?>' but no parser exists "
+ "for that type",
parameter.getName(),
token.getType().getTypeName()
)));
final ParserDescriptor<C, ?> fallback = this.annotationParser.manager()
.parserRegistry()
.createParser(fallbackType, parameters)
.map(fallbackParser -> ParserDescriptor.of(fallbackParser, (TypeToken) fallbackType))
.orElseThrow(() -> new IllegalArgumentException(
String.format(
"Parameter '%s' "
+ "has parser 'Either<?, %s>' but no parser exists "
+ "for that type",
parameter.getName(),
token.getType().getTypeName()
)));
parser = EitherParser.eitherParser(primary, fallback).parser();
} else if (descriptor.parserName() == null) {
parser = this.annotationParser.manager().parserRegistry()
.createParser(token, parameters)
.orElseThrow(() -> new IllegalArgumentException(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//
// MIT License
//
// Copyright (c) 2024 Incendo
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
package cloud.commandframework.annotations.feature;

import cloud.commandframework.CommandComponent;
import cloud.commandframework.CommandManager;
import cloud.commandframework.annotations.AnnotationParser;
import cloud.commandframework.annotations.Command;
import cloud.commandframework.annotations.TestCommandManager;
import cloud.commandframework.annotations.TestCommandSender;
import cloud.commandframework.arguments.parser.EitherParser;
import cloud.commandframework.arguments.standard.BooleanParser;
import cloud.commandframework.arguments.standard.IntegerParser;
import cloud.commandframework.types.Either;
import java.util.Collection;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static com.google.common.truth.Truth.assertThat;

class EitherParserAnnotationsTest {

private CommandManager<TestCommandSender> commandManager;
private AnnotationParser<TestCommandSender> annotationParser;

@BeforeEach
void setup() {
this.commandManager = new TestCommandManager();
this.annotationParser = new AnnotationParser<>(
this.commandManager,
TestCommandSender.class
);
}

@Test
@SuppressWarnings("unchecked")
void test() {
// Act
final Collection<cloud.commandframework.Command<TestCommandSender>> result = this.annotationParser.parse(
new EitherParserAnnotationsTest()
);

// Assert
final cloud.commandframework.Command<TestCommandSender> command = result.stream().findFirst().get();
final CommandComponent<TestCommandSender> eitherComponent = command.components().get(1);
assertThat(eitherComponent.parser()).isInstanceOf(EitherParser.class);
final EitherParser<?, Integer, Boolean> eitherParser = (EitherParser<?, Integer, Boolean>) eitherComponent.parser();
assertThat(eitherParser.primary().parser()).isInstanceOf(IntegerParser.class);
assertThat(eitherParser.fallback().parser()).isInstanceOf(BooleanParser.class);
}

@Command("command <arg>")
public void command(@NonNull Either<Integer, Boolean> arg) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import cloud.commandframework.arguments.suggestion.SuggestionProviderHolder;
import cloud.commandframework.context.CommandContext;
import cloud.commandframework.context.CommandInput;
import cloud.commandframework.types.Either;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import org.apiguardian.api.API;
Expand Down Expand Up @@ -175,6 +176,24 @@ public interface ArgumentParser<C, T> extends SuggestionProviderHolder<C> {
return SuggestionProvider.noSuggestions();
}

/**
* Creates a new parser which attempts to use the {@code primary} parser and falls back on
* the {@code fallback} parser if that fails.
*
* @param <C> command sender type
* @param <U> primary value type
* @param <V> fallback value type
* @param primary primary parser
* @param fallback fallback parser which gets invoked if the primary parser fails to parse the input
* @return the descriptor of the parser
*/
static <C, U, V> @NonNull ParserDescriptor<C, Either<U, V>> firstOf(
final @NonNull ParserDescriptor<C, U> primary,
final @NonNull ParserDescriptor<C, V> fallback
) {
return EitherParser.eitherParser(primary, fallback);
}


/**
* Utility interface extending {@link ArgumentParser} to make it easier to implement
Expand Down
Loading

0 comments on commit 6cd028e

Please sign in to comment.