Skip to content

Commit 45d32b0

Browse files
committed
introduce @values annotation
1 parent 770ed60 commit 45d32b0

File tree

12 files changed

+215
-5
lines changed

12 files changed

+215
-5
lines changed

bukkit/src/main/java/revxrsal/commands/bukkit/exception/BukkitExceptionHandler.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,9 @@ public void onNumberNotInRange(@NotNull NumberNotInRangeException e, @NotNull Bu
135135
@Override public void onUnknownCommand(@NotNull UnknownCommandException e, @NotNull BukkitCommandActor actor) {
136136
actor.error(legacyColorize("&cUnknown command: &e" + e.input() + "&c."));
137137
}
138+
139+
@Override public void onValueNotAllowed(@NotNull ValueNotAllowedException e, @NotNull BukkitCommandActor actor) {
140+
String allowedValues = String.join("&c, &e", e.allowedValues());
141+
actor.error(legacyColorize("Received an invalid value: &e" + e.input() + "&c. Allowed values: &e" + allowedValues + "&c."));
142+
}
138143
}

bungee/src/main/java/revxrsal/commands/bungee/exception/BungeeExceptionHandler.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,9 @@ public void onNumberNotInRange(@NotNull NumberNotInRangeException e, @NotNull Bu
100100
@Override public void onUnknownCommand(@NotNull UnknownCommandException e, @NotNull BungeeCommandActor actor) {
101101
actor.error(legacyColorize("&cUnknown command: &e" + e.input() + "&c."));
102102
}
103+
104+
@Override public void onValueNotAllowed(@NotNull ValueNotAllowedException e, @NotNull BungeeCommandActor actor) {
105+
String allowedValues = String.join("&c, &e", e.allowedValues());
106+
actor.error(legacyColorize("Received an invalid value: &e" + e.input() + "&c. Allowed values: &e" + allowedValues + "&c."));
107+
}
103108
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package revxrsal.commands.annotation;
2+
3+
import org.jetbrains.annotations.NotNull;
4+
5+
/**
6+
* A utility annotation that marks a field as <em>only</em> accepting certain
7+
* values. This will take care of validation, tab-completion and errors.
8+
* <p>
9+
* While Lamp encourages using standard Java {@code enum}s for closed-type
10+
* values, this annotation makes it easier when a quick and dirty solution is needed.
11+
* <p>
12+
* It is also usable for numerical types, which cannot be done using enums.
13+
* <p>
14+
* Values can be of any type, including strings, integers, enums, etc.
15+
*/
16+
public @interface Values {
17+
18+
/**
19+
* The allowed values.
20+
*
21+
* @return The allowed values
22+
*/
23+
@NotNull String[] value();
24+
25+
/**
26+
* Should checks be case-sensitive?
27+
*
28+
* @return if checks should be case-sensitive.
29+
*/
30+
boolean caseSensitive() default true;
31+
32+
}

common/src/main/java/revxrsal/commands/exception/DefaultExceptionHandler.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,12 @@ public void onCooldown(@NotNull CooldownException e, @NotNull A actor) {
145145
actor.error("You must wait " + formatTimeFancy(e.getTimeLeftMillis()) + " before using this command again.");
146146
}
147147

148+
@HandleException
149+
public void onValueNotAllowed(@NotNull ValueNotAllowedException e, @NotNull A actor) {
150+
String allowedValues = String.join(", ", e.allowedValues());
151+
actor.error("Received an invalid value: " + e.input() + ". Allowed values: " + allowedValues);
152+
}
153+
148154
@HandleException
149155
public void onSendable(@NotNull SendableException e, @NotNull A actor) {
150156
e.sendTo(actor);
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package revxrsal.commands.exception;
2+
3+
import org.jetbrains.annotations.NotNull;
4+
import org.jetbrains.annotations.Unmodifiable;
5+
import revxrsal.commands.annotation.Values;
6+
7+
import java.util.List;
8+
9+
/**
10+
* Thrown when an invalid value is given for a parameter annotated with
11+
* {@link revxrsal.commands.annotation.Values @Values}.
12+
*/
13+
public class ValueNotAllowedException extends InvalidValueException {
14+
15+
private final @Unmodifiable List<String> allowedValues;
16+
private final boolean caseSensitive;
17+
18+
public ValueNotAllowedException(@NotNull String input, @NotNull @Unmodifiable List<String> allowedValues, boolean caseSensitive) {
19+
super(input);
20+
this.allowedValues = allowedValues;
21+
this.caseSensitive = caseSensitive;
22+
}
23+
24+
/**
25+
* Tests whether the parameter is case-sensitive or not
26+
*
27+
* @return If the parameter is case-sensitive or not
28+
* @see Values#caseSensitive()
29+
*/
30+
public boolean caseSensitive() {
31+
return caseSensitive;
32+
}
33+
34+
/**
35+
* Returns an immutable list of the allowed values
36+
*
37+
* @return The allowed values
38+
* @see Values#value()
39+
*/
40+
public @Unmodifiable @NotNull List<String> allowedValues() {
41+
return allowedValues;
42+
}
43+
44+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* This file is part of lamp, licensed under the MIT License.
3+
*
4+
* Copyright (c) Revxrsal <reflxction.github@gmail.com>
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
package revxrsal.commands.parameter.builtins;
25+
26+
import org.jetbrains.annotations.ApiStatus;
27+
import org.jetbrains.annotations.NotNull;
28+
import revxrsal.commands.Lamp;
29+
import revxrsal.commands.annotation.Values;
30+
import revxrsal.commands.annotation.list.AnnotationList;
31+
import revxrsal.commands.autocomplete.SuggestionProvider;
32+
import revxrsal.commands.command.CommandActor;
33+
import revxrsal.commands.exception.EnumNotFoundException;
34+
import revxrsal.commands.exception.ValueNotAllowedException;
35+
import revxrsal.commands.node.ExecutionContext;
36+
import revxrsal.commands.parameter.ParameterType;
37+
import revxrsal.commands.parameter.PrioritySpec;
38+
import revxrsal.commands.stream.MutableStringStream;
39+
40+
import java.lang.reflect.Type;
41+
import java.util.*;
42+
43+
import static revxrsal.commands.util.Classes.getRawType;
44+
import static revxrsal.commands.util.Collections.map;
45+
46+
@ApiStatus.Internal
47+
public enum ValuesParameterTypeFactory implements ParameterType.Factory<CommandActor> {
48+
INSTANCE;
49+
50+
@Override
51+
public <T> ParameterType<CommandActor, T> create(@NotNull Type parameterType, @NotNull AnnotationList annotations, @NotNull Lamp<CommandActor> lamp) {
52+
Values values = annotations.get(Values.class);
53+
if (values == null)
54+
return null;
55+
ParameterType<CommandActor, Object> delegate = lamp.findNextResolver(parameterType, annotations, this)
56+
.requireParameterType();
57+
List<String> allowed = values.caseSensitive() ? Arrays.asList(values.value()) : map(values.value(), String::toUpperCase);
58+
if (allowed.isEmpty())
59+
throw new IllegalArgumentException("@Values() must contain at least 1 value!");
60+
return new ParameterType<CommandActor, T>() {
61+
@Override
62+
public T parse(@NotNull MutableStringStream input, @NotNull ExecutionContext<@NotNull CommandActor> context) {
63+
int start = input.position();
64+
@SuppressWarnings("unchecked")
65+
T value = (T) delegate.parse(input, context);
66+
int end = input.position();
67+
String consumed = input.peek(end - start);
68+
69+
if ((values.caseSensitive() && allowed.contains(consumed))
70+
|| (!values.caseSensitive() && allowed.contains(consumed.toUpperCase())))
71+
return value;
72+
throw new ValueNotAllowedException(
73+
consumed,
74+
Arrays.asList(values.value()),
75+
values.caseSensitive()
76+
);
77+
}
78+
79+
@Override public @NotNull SuggestionProvider<@NotNull CommandActor> defaultSuggestions() {
80+
return SuggestionProvider.of(values.value());
81+
}
82+
};
83+
}
84+
}

common/src/main/java/revxrsal/commands/util/Collections.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,16 @@ public static <T> Object[] insertAtBeginning(
149149
return list;
150150
}
151151

152+
@Contract(pure = true)
153+
@CheckReturnValue
154+
public static @NotNull <U, T> List<T> map(@NotNull U[] iterator, @NotNull Function<U, T> fn) {
155+
List<T> list = new ArrayList<>();
156+
for (U u : iterator) {
157+
list.add(fn.apply(u));
158+
}
159+
return list;
160+
}
161+
152162
public static <T> boolean any(@NotNull Iterable<T> iterator, Predicate<T> predicate) {
153163
for (T t : iterator) {
154164
if (predicate.test(t))

fabric/src/main/java/revxrsal/commands/fabric/exception/FabricExceptionHandler.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,9 @@ public void onNumberNotInRange(@NotNull NumberNotInRangeException e, @NotNull Fa
110110
@Override public void onUnknownCommand(@NotNull UnknownCommandException e, @NotNull FabricCommandActor actor) {
111111
actor.error(legacyColorize("&cUnknown command: &e" + e.input() + "&c."));
112112
}
113+
114+
@Override public void onValueNotAllowed(@NotNull ValueNotAllowedException e, @NotNull FabricCommandActor actor) {
115+
String allowedValues = String.join("&c, &e", e.allowedValues());
116+
actor.error(legacyColorize("Received an invalid value: &e" + e.input() + "&c. Allowed values: &e" + allowedValues + "&c."));
117+
}
113118
}

jda/src/main/java/revxrsal/commands/jda/exception/SlashJDAExceptionHandler.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,15 @@
2323
*/
2424
package revxrsal.commands.jda.exception;
2525

26+
import net.dv8tion.jda.api.utils.MarkdownUtil;
2627
import org.jetbrains.annotations.NotNull;
27-
import revxrsal.commands.exception.CommandInvocationException;
28-
import revxrsal.commands.exception.DefaultExceptionHandler;
29-
import revxrsal.commands.exception.InvalidHelpPageException;
30-
import revxrsal.commands.exception.NoPermissionException;
28+
import revxrsal.commands.exception.*;
3129
import revxrsal.commands.jda.actor.SlashCommandActor;
3230

3331
import java.util.Locale;
3432

3533
import static revxrsal.commands.util.BuiltInNamingStrategies.separateCamelCase;
34+
import static revxrsal.commands.util.Collections.map;
3635

3736
public class SlashJDAExceptionHandler<A extends SlashCommandActor> extends DefaultExceptionHandler<A> {
3837

@@ -65,10 +64,15 @@ public void onGuildOnlyCommand(GuildOnlyCommandException e, SlashCommandActor ac
6564
}
6665

6766
@HandleException
68-
public void onInvalidCategory(InvalidCategoryException e, SlashCommandActor actor) {
67+
public void onInvalidCategory(InvalidCategoryException e, A actor) {
6968
actor.error("**Invalid role:** " + e.input());
7069
}
7170

71+
@Override public void onValueNotAllowed(@NotNull ValueNotAllowedException e, @NotNull A actor) {
72+
String allowedValues = String.join(", ", map(e.allowedValues(), MarkdownUtil::bold));
73+
actor.error("🛑 Received an invalid value: " + MarkdownUtil.bold(e.input()) + ". Allowed values: " + allowedValues + ".");
74+
}
75+
7276
@Override
7377
public void onInvalidHelpPage(@NotNull InvalidHelpPageException e, @NotNull A actor) {
7478
if (e.numberOfPages() == 1)

minestom/src/main/java/revxrsal/commands/minestom/exception/MinestomExceptionHandler.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ public void onCommandInvocation(@NotNull CommandInvocationException e, @NotNull
119119
e.cause().printStackTrace();
120120
}
121121

122+
@Override public void onValueNotAllowed(@NotNull ValueNotAllowedException e, @NotNull MinestomCommandActor actor) {
123+
String allowedValues = String.join("&c, &e", e.allowedValues());
124+
actor.error(legacyColorize("Received an invalid value: &e" + e.input() + "&c. Allowed values: &e" + allowedValues + "&c."));
125+
}
126+
122127
@Override public void onUnknownCommand(@NotNull UnknownCommandException e, @NotNull MinestomCommandActor actor) {
123128
actor.error(legacyColorize("&cUnknown command: &e" + e.input() + "&c."));
124129
}

0 commit comments

Comments
 (0)