Skip to content

Commit cceb905

Browse files
fix(core): correctly forward component preprocessing failures (#744)
Co-authored-by: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
1 parent f5041f4 commit cceb905

3 files changed

Lines changed: 200 additions & 9 deletions

File tree

cloud-core/src/main/java/org/incendo/cloud/CommandTree.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -524,10 +524,8 @@ private CommandTree(final @NonNull CommandManager<C> commandManager) {
524524
parseResult = CompletableFutures.failedFuture(this.argumentParseException(commandContext, child, argumentValue));
525525
}
526526
} else {
527-
parseResult =
528-
this.parseArgument(commandContext, child, commandInput, executor)
529-
.thenApply(ArgumentParseResult::parsedValue)
530-
.thenApply(optional -> optional.orElse(null));
527+
parseResult = this.parseArgument(commandContext, child, commandInput, executor)
528+
.thenApply(result -> result.parsedValue().orElse(null));
531529
}
532530

533531
return parseResult.thenComposeAsync(value -> {
@@ -578,6 +576,11 @@ private boolean matchesLiteral(final @NonNull List<@NonNull CommandNode<C>> chil
578576
if (preParseResult.failure().isPresent() || !preParseResult.parsedValue().orElse(false)) {
579577
parsingContext.markEnd();
580578
parsingContext.success(false);
579+
if (preParseResult.failure().isPresent()) {
580+
return CompletableFutures.failedFuture(
581+
this.argumentParseException(commandContext, node, preParseResult)
582+
);
583+
}
581584
return CompletableFuture.completedFuture(preParseResult);
582585
}
583586

@@ -593,17 +596,14 @@ private boolean matchesLiteral(final @NonNull List<@NonNull CommandNode<C>> chil
593596
parsingContext.markEnd();
594597
parsingContext.success(false);
595598

596-
final CompletableFuture<ArgumentParseResult<?>> resultFuture = new CompletableFuture<>();
597-
598599
if (result.failure().isPresent()) {
599600
commandInput.cursor(currentInput.cursor());
600-
resultFuture.completeExceptionally(
601+
return CompletableFutures.failedFuture(
601602
this.argumentParseException(commandContext, node, result)
602603
);
603604
} else {
604-
resultFuture.complete(result);
605+
return CompletableFuture.completedFuture(result);
605606
}
606-
return resultFuture;
607607
}, executor);
608608
}
609609

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
//
2+
// MIT License
3+
//
4+
// Copyright (c) 2024 Incendo
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 org.incendo.cloud.feature;
25+
26+
import java.util.concurrent.CompletionException;
27+
import org.checkerframework.checker.nullness.qual.NonNull;
28+
import org.incendo.cloud.CommandManager;
29+
import org.incendo.cloud.TestCommandSender;
30+
import org.incendo.cloud.component.CommandComponent;
31+
import org.incendo.cloud.component.preprocessor.ComponentPreprocessor;
32+
import org.incendo.cloud.context.CommandContext;
33+
import org.incendo.cloud.context.CommandInput;
34+
import org.incendo.cloud.exception.ArgumentParseException;
35+
import org.incendo.cloud.execution.CommandResult;
36+
import org.incendo.cloud.parser.ArgumentParseResult;
37+
import org.junit.jupiter.api.BeforeEach;
38+
import org.junit.jupiter.api.Test;
39+
40+
import static com.google.common.truth.Truth.assertThat;
41+
import static org.incendo.cloud.parser.standard.StringParser.stringParser;
42+
import static org.incendo.cloud.util.TestUtils.createManager;
43+
import static org.junit.jupiter.api.Assertions.assertThrows;
44+
45+
class ComponentPreprocessorTest {
46+
47+
private CommandManager<TestCommandSender> commandManager;
48+
49+
@BeforeEach void setup() {
50+
this.commandManager = createManager();
51+
}
52+
53+
@Test
54+
void Parse_HappyFlow_Success() throws Exception {
55+
// Arrange
56+
final ComponentPreprocessor<TestCommandSender> preprocessor = new TestPreprocessor(true);
57+
this.commandManager.command(
58+
this.commandManager.commandBuilder("test")
59+
.required(CommandComponent.<TestCommandSender, String>builder("arg",
60+
stringParser()).preprocessor(preprocessor))
61+
);
62+
63+
// Act
64+
final CommandResult<TestCommandSender> result =
65+
this.commandManager.commandExecutor().executeCommand(new TestCommandSender(), "test abc").join();
66+
67+
// Assert
68+
assertThat(result.commandContext().<String>get("arg")).isEqualTo("abc");
69+
}
70+
71+
@Test
72+
void Parse_Failing_ThrowsParseException() throws Exception {
73+
// Arrange
74+
final ComponentPreprocessor<TestCommandSender> preprocessor = new TestPreprocessor(false);
75+
this.commandManager.command(
76+
this.commandManager.commandBuilder("test")
77+
.required(CommandComponent.<TestCommandSender, String>builder("arg",
78+
stringParser()).preprocessor(preprocessor))
79+
);
80+
81+
// Act & Assert
82+
final CompletionException failure = assertThrows(CompletionException.class, () ->
83+
this.commandManager.commandExecutor().executeCommand(new TestCommandSender(), "test abc").join());
84+
assertThat(failure).hasCauseThat().isInstanceOf(ArgumentParseException.class);
85+
}
86+
87+
private static class TestPreprocessor implements ComponentPreprocessor<TestCommandSender> {
88+
89+
private final boolean result;
90+
91+
TestPreprocessor(final boolean result) {
92+
this.result = result;
93+
}
94+
95+
@Override
96+
public @NonNull ArgumentParseResult<Boolean> preprocess(
97+
final @NonNull CommandContext<TestCommandSender> context,
98+
final @NonNull CommandInput commandInput
99+
) {
100+
if (this.result) {
101+
return ArgumentParseResult.success(true);
102+
}
103+
return ArgumentParseResult.failure(new RuntimeException("no"));
104+
}
105+
}
106+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
//
2+
// MIT License
3+
//
4+
// Copyright (c) 2024 Incendo
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 org.incendo.cloud.feature;
25+
26+
import java.util.concurrent.CompletionException;
27+
import org.incendo.cloud.CommandManager;
28+
import org.incendo.cloud.TestCommandSender;
29+
import org.incendo.cloud.component.CommandComponent;
30+
import org.incendo.cloud.component.preprocessor.ComponentPreprocessor;
31+
import org.incendo.cloud.component.preprocessor.RegexPreprocessor;
32+
import org.incendo.cloud.exception.ArgumentParseException;
33+
import org.incendo.cloud.execution.CommandResult;
34+
import org.junit.jupiter.api.BeforeEach;
35+
import org.junit.jupiter.api.Test;
36+
37+
import static com.google.common.truth.Truth.assertThat;
38+
import static org.incendo.cloud.parser.standard.StringParser.stringParser;
39+
import static org.incendo.cloud.util.TestUtils.createManager;
40+
import static org.junit.jupiter.api.Assertions.assertThrows;
41+
42+
class RegexTest {
43+
44+
private CommandManager<TestCommandSender> commandManager;
45+
46+
@BeforeEach
47+
void setup() {
48+
this.commandManager = createManager();
49+
}
50+
51+
@Test
52+
void Parse_HappyFlow_Success() throws Exception {
53+
// Arrange
54+
final ComponentPreprocessor<TestCommandSender> preprocessor = RegexPreprocessor.of("[a-z]+");
55+
this.commandManager.command(
56+
this.commandManager.commandBuilder("test")
57+
.required(CommandComponent.<TestCommandSender, String>builder("arg",
58+
stringParser()).preprocessor(preprocessor))
59+
);
60+
61+
// Act
62+
final CommandResult<TestCommandSender> result =
63+
this.commandManager.commandExecutor().executeCommand(new TestCommandSender(), "test abc").join();
64+
65+
// Assert
66+
assertThat(result.commandContext().<String>get("arg")).isEqualTo("abc");
67+
}
68+
69+
@Test
70+
void Parse_Failing_ThrowsParseException() throws Exception {
71+
// Arrange
72+
final ComponentPreprocessor<TestCommandSender> preprocessor = RegexPreprocessor.of("[a-z]+");
73+
this.commandManager.command(
74+
this.commandManager.commandBuilder("test")
75+
.required(CommandComponent.<TestCommandSender, String>builder("arg",
76+
stringParser()).preprocessor(preprocessor))
77+
);
78+
79+
// Act & Assert
80+
final CompletionException failure = assertThrows(CompletionException.class, () ->
81+
this.commandManager.commandExecutor().executeCommand(new TestCommandSender(), "test ABC").join());
82+
assertThat(failure).hasCauseThat().isInstanceOf(ArgumentParseException.class);
83+
assertThat(failure).hasCauseThat().hasCauseThat().isInstanceOf(RegexPreprocessor.RegexValidationException.class);
84+
}
85+
}

0 commit comments

Comments
 (0)