Skip to content

Commit 7827c6e

Browse files
committed
Wrap match result in optional to avoid nullable in API
1 parent 47ebc10 commit 7827c6e

File tree

4 files changed

+48
-41
lines changed

4 files changed

+48
-41
lines changed

java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package io.cucumber.cucumberexpressions;
22

33
import org.apiguardian.api.API;
4-
import org.jspecify.annotations.Nullable;
54

65
import java.lang.reflect.Type;
76
import java.util.ArrayList;
87
import java.util.List;
8+
import java.util.Optional;
99
import java.util.function.Function;
1010
import java.util.regex.Pattern;
1111

@@ -132,10 +132,10 @@ private void assertNoNodeOfType(Node.Type nodeType, Node node,
132132

133133

134134
@Override
135-
public @Nullable List<Argument<?>> match(String text, Type... typeHints) {
135+
public Optional<List<Argument<?>>> match(String text, Type... typeHints) {
136136
final Group group = treeRegexp.match(text);
137137
if (group == null) {
138-
return null;
138+
return Optional.empty();
139139
}
140140

141141
List<ParameterType<?>> parameterTypes = new ArrayList<>(this.parameterTypes);
@@ -148,7 +148,7 @@ private void assertNoNodeOfType(Node.Type nodeType, Node node,
148148
}
149149
}
150150

151-
return Argument.build(group, parameterTypes);
151+
return Optional.of(Argument.build(group, parameterTypes));
152152
}
153153

154154
@Override

java/src/main/java/io/cucumber/cucumberexpressions/Expression.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
package io.cucumber.cucumberexpressions;
22

33
import org.apiguardian.api.API;
4-
import org.jspecify.annotations.Nullable;
54

65
import java.lang.reflect.Type;
76
import java.util.List;
7+
import java.util.Optional;
88
import java.util.regex.Pattern;
99

1010
@API(status = API.Status.STABLE)
1111
public interface Expression {
12-
13-
@Nullable List<Argument<?>> match(String text, Type... typeHints);
12+
13+
/**
14+
* Matches a string to an expression. Empty if no match.
15+
*/
16+
Optional<List<Argument<?>>> match(String text, Type... typeHints);
1417

1518
Pattern getRegexp();
1619

java/src/main/java/io/cucumber/cucumberexpressions/RegularExpression.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package io.cucumber.cucumberexpressions;
22

33
import org.apiguardian.api.API;
4-
import org.jspecify.annotations.Nullable;
54

65
import java.lang.reflect.Type;
76
import java.util.ArrayList;
87
import java.util.List;
8+
import java.util.Optional;
99
import java.util.regex.Pattern;
1010

1111
import static io.cucumber.cucumberexpressions.ParameterType.createAnonymousParameterType;
@@ -31,10 +31,10 @@ public final class RegularExpression implements Expression {
3131
}
3232

3333
@Override
34-
public @Nullable List<Argument<?>> match(String text, Type... typeHints) {
34+
public Optional<List<Argument<?>>> match(String text, Type... typeHints) {
3535
final Group group = treeRegexp.match(text);
3636
if (group == null) {
37-
return null;
37+
return Optional.empty();
3838
}
3939

4040
final ParameterByTypeTransformer defaultTransformer = parameterTypeRegistry.getDefaultParameterTransformer();
@@ -70,7 +70,7 @@ public final class RegularExpression implements Expression {
7070
parameterTypes.add(parameterType);
7171
}
7272

73-
return Argument.build(group, parameterTypes);
73+
return Optional.of(Argument.build(group, parameterTypes));
7474
}
7575

7676
@Override

java/src/test/java/io/cucumber/cucumberexpressions/CustomParameterTypeTest.java

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package io.cucumber.cucumberexpressions;
22

3+
import org.assertj.core.api.AbstractObjectAssert;
4+
import org.assertj.core.api.InstanceOfAssertFactories;
35
import org.jspecify.annotations.Nullable;
46
import org.junit.jupiter.api.BeforeEach;
57
import org.junit.jupiter.api.Test;
68
import org.junit.jupiter.api.function.Executable;
79

810
import java.util.List;
911
import java.util.Locale;
12+
import java.util.Optional;
1013
import java.util.regex.Pattern;
1114

1215
import static java.lang.Integer.parseInt;
@@ -54,10 +57,7 @@ void throws_exception_for_illegal_character_in_parameter_name() {
5457
void matches_CucumberExpression_parameters_with_custom_parameter_type() {
5558
var expression = new CucumberExpression("I have a {color} ball", parameterTypeRegistry);
5659
var arguments = expression.match("I have a red ball");
57-
58-
assertThat(arguments).singleElement()
59-
.extracting(Argument::getValue)
60-
.isEqualTo(new Color("red"));
60+
asserThatSingleArgumentValue(arguments).isEqualTo(new Color("red"));
6161
}
6262

6363
@Test
@@ -75,8 +75,10 @@ void matches_CucumberExpression_parameters_with_multiple_capture_groups() {
7575
var arguments = expression.match("A 5 thick line from 10,20,30 to 40,50,60");
7676

7777
assertThat(arguments)
78+
.get()
79+
.asInstanceOf(InstanceOfAssertFactories.LIST)
80+
.map(Argument.class::cast)
7881
.extracting(Argument::getValue)
79-
.map(Object.class::cast)
8082
.containsExactly(
8183
5,
8284
new Coordinate(10, 20, 30),
@@ -100,10 +102,8 @@ void warns_when_CucumberExpression_parameters_with_multiple_capture_groups_has_a
100102
var expression = new CucumberExpression("A {int} thick line from {coordinate} to {coordinate}", parameterTypeRegistry);
101103
var arguments = expression.match("A 5 thick line from 10,20,30 to 40,50,60");
102104

103-
assertDoesNotThrow(() -> {
104-
requireNonNull(arguments).get(0).getValue();
105-
});
106-
var exception = assertThrows(CucumberExpressionException.class, () -> requireNonNull(arguments).get(1).getValue());
105+
assertDoesNotThrow(() -> getArgumentValue(arguments, 0));
106+
var exception = assertThrows(CucumberExpressionException.class, () -> getArgumentValue(arguments, 1));
107107
assertThat(exception).hasMessage(
108108
"ParameterType {coordinate} was registered with a Transformer but has multiple capture groups [(\\d+),\\s*(\\d+),\\s*(\\d+)]. " +
109109
"Did you mean to use a CaptureGroupTransformer?"
@@ -114,15 +114,13 @@ void warns_when_CucumberExpression_parameters_with_multiple_capture_groups_has_a
114114
void warns_when_anonymous_parameter_has_multiple_capture_groups() {
115115
parameterTypeRegistry = new ParameterTypeRegistry(Locale.ENGLISH);
116116
Expression expression = new RegularExpression(Pattern.compile("^A (\\d+) thick line from ((\\d+),\\s*(\\d+),\\s*(\\d+)) to ((\\d+),\\s*(\\d+),\\s*(\\d+))$"), parameterTypeRegistry);
117-
List<Argument<?>> arguments = expression.match("A 5 thick line from 10,20,30 to 40,50,60",
117+
var arguments = expression.match("A 5 thick line from 10,20,30 to 40,50,60",
118118
Integer.class, Coordinate.class, Coordinate.class);
119119

120120
assertNotNull(arguments);
121-
assertDoesNotThrow(() -> {
122-
arguments.get(0).getValue();
123-
});
121+
assertDoesNotThrow(() -> getArgumentValue(arguments, 0));
124122

125-
var exception = assertThrows(CucumberExpressionException.class, () -> arguments.get(1).getValue());
123+
var exception = assertThrows(CucumberExpressionException.class, () -> getArgumentValue(arguments, 1));
126124
assertThat(exception).hasMessage(
127125
"Anonymous ParameterType has multiple capture groups [(\\d+),\\s*(\\d+),\\s*(\\d+)]. " +
128126
"You can only use a single capture group in an anonymous ParameterType."
@@ -143,9 +141,7 @@ void matches_CucumberExpression_parameters_with_custom_parameter_type_using_opti
143141
var expression = new CucumberExpression("I have a {color} ball", parameterTypeRegistry);
144142
var match = expression.match("I have a dark red ball");
145143

146-
assertThat(match).singleElement()
147-
.extracting(Argument::getValue)
148-
.isEqualTo(new Color("dark red"));
144+
asserThatSingleArgumentValue(match).isEqualTo(new Color("dark red"));
149145
}
150146

151147
@Test
@@ -163,7 +159,7 @@ void defers_transformation_until_queried_from_argument() {
163159
var expression = new CucumberExpression("I have a {throwing} parameter", parameterTypeRegistry);
164160
var arguments = expression.match("I have a bad parameter");
165161

166-
var exception = assertThrows(RuntimeException.class, () -> requireNonNull(arguments).get(0).getValue());
162+
var exception = assertThrows(RuntimeException.class, () -> getArgumentValue(arguments, 0));
167163
assertThat(exception).hasMessage("ParameterType {throwing} failed to transform [bad] to " + CssColor.class, exception.getMessage());
168164
}
169165

@@ -205,15 +201,11 @@ void conflicting_parameter_type_is_not_detected_for_regexp() {
205201
false
206202
));
207203

208-
var cssColorMatch = new CucumberExpression("I have a {css-color} ball", parameterTypeRegistry).match("I have a blue ball");
209-
assertThat(cssColorMatch).singleElement()
210-
.extracting(Argument::getValue)
211-
.isEqualTo(new CssColor("blue"));
204+
var cssColorArguments = new CucumberExpression("I have a {css-color} ball", parameterTypeRegistry).match("I have a blue ball");
205+
asserThatSingleArgumentValue(cssColorArguments).isEqualTo(new CssColor("blue"));
212206

213-
var colorMatch = new CucumberExpression("I have a {color} ball", parameterTypeRegistry).match("I have a blue ball");
214-
assertThat(colorMatch).singleElement()
215-
.extracting(Argument::getValue)
216-
.isEqualTo(new Color("blue"));
207+
var colorArguments = new CucumberExpression("I have a {color} ball", parameterTypeRegistry).match("I have a blue ball");
208+
asserThatSingleArgumentValue(colorArguments).isEqualTo(new Color("blue"));
217209

218210
}
219211

@@ -230,10 +222,22 @@ void matches_RegularExpression_arguments_with_custom_parameter_type_without_name
230222
));
231223

232224
var expression = new RegularExpression(compile("I have a (red|blue|yellow) ball"), parameterTypeRegistry);
233-
var match = expression.match("I have a red ball");
234-
assertThat(match).singleElement()
235-
.extracting(Argument::getValue)
236-
.isEqualTo(new Color("red"));
225+
var arguments = expression.match("I have a red ball");
226+
asserThatSingleArgumentValue(arguments).isEqualTo(new Color("red"));
227+
}
228+
229+
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
230+
private static void getArgumentValue(Optional<List<Argument<?>>> match, int index) {
231+
match.ifPresent(arguments -> arguments.get(index).getValue());
232+
}
233+
234+
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
235+
private static AbstractObjectAssert<?, Object> asserThatSingleArgumentValue(Optional<List<Argument<?>>> match) {
236+
return assertThat(match).get()
237+
.asInstanceOf(InstanceOfAssertFactories.LIST)
238+
.map(Argument.class::cast)
239+
.singleElement()
240+
.extracting(Argument::getValue);
237241
}
238242

239243
private record Coordinate(int x, int y, int z) {

0 commit comments

Comments
 (0)