diff --git a/src/main/java/com/lmax/simpledsl/api/DslArg.java b/src/main/java/com/lmax/simpledsl/api/DslArg.java index a3e7d25..6f2e8b0 100644 --- a/src/main/java/com/lmax/simpledsl/api/DslArg.java +++ b/src/main/java/com/lmax/simpledsl/api/DslArg.java @@ -37,7 +37,7 @@ public interface DslArg /** * Get a default value for this argument. - * + * <p> * If the argument is required, this method will throw an {@link IllegalArgumentException}. * * @return the default value for the argument diff --git a/src/main/java/com/lmax/simpledsl/api/DslValues.java b/src/main/java/com/lmax/simpledsl/api/DslValues.java index 9f09c1d..a8469da 100644 --- a/src/main/java/com/lmax/simpledsl/api/DslValues.java +++ b/src/main/java/com/lmax/simpledsl/api/DslValues.java @@ -22,6 +22,9 @@ import java.util.Arrays; import java.util.List; import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -276,6 +279,34 @@ default String valueAsParam(final String name) return value != null ? name + ": " + value : null; } + /** + * Retrieve the value supplied for a parameter formatted as a parameter with the given name. + * For example, if the parameter {@literal user} was given the value {@literal jenny}, then + * {@code valueAsParamNamed("user", "person")} would return {@code person: jenny}. + * <p> + * This is useful when reusing DSL methods to build higher level functions. e.g. + * + * <pre>{@code + * public void createUserAndLogin(String... args) { + * DslParams params = new DslParams(args, + * new RequiredParam("user"), + * new RequiredParam("accountType")); + * generateRandomUser(params.valueAsParamNamed("user", "rememberUserAs")); + * login(params.valueAsParam("user"), "password: password"); + * } + * }</pre> + * + * @param oldParamName the name of the parameter. + * @param newParamName the new name of the parameter. + * @return the value supplied for that parameter, formatted as a parameter ready to pass on to another method that uses Simple-DSL. + * @throws IllegalArgumentException if {@code name} does not match the name of a supported parameter or if the parameter supports multiple values. + */ + default String valueAsParamNamed(final String oldParamName, final String newParamName) + { + final String value = value(oldParamName); + return value != null ? newParamName + ": " + value : null; + } + /** * Retrieve the values supplied for a parameter as a {@link List}. Returns an empty list if the parameter is optional and a value has not been supplied. * @@ -305,7 +336,7 @@ default <T> List<T> valuesAsListOf(final String name, final Function<String, T> /** * Retrieve the values supplied for a parameter as an {@link Optional} {@link List}. * <p> - * Returns an empty Optional if the parameter is optional and a value has not been supplied. + * Returns an {@link Optional#empty() empty Optional} if the parameter is optional and a value has not been supplied. * <p> * In most cases {@link #valuesAsList} is the more suitable method. * This variant is useful if there is an important difference between a parameter being set to an empty list vs not being supplied. @@ -331,6 +362,48 @@ default Optional<List<String>> valuesAsOptional(final String name) .filter(list -> !list.isEmpty()); } + /** + * Retrieve the values supplied for a parameter as an {@link OptionalInt}. + * <p> + * Returns an {@link OptionalInt#empty() empty Optional} if the parameter is optional and a value has not been supplied. + * + * @param name the name of the parameter. + * @return the value of the parameter, or empty + * @throws IllegalArgumentException if {@code name} does not match the name of a supported parameter. + */ + default OptionalInt valuesAsOptionalInt(final String name) + { + return hasValue(name) ? OptionalInt.of(valueAsInt(name)) : OptionalInt.empty(); + } + + /** + * Retrieve the values supplied for a parameter as an {@link OptionalLong}. + * <p> + * Returns an {@link OptionalLong#empty() empty Optional} if the parameter is optional and a value has not been supplied. + * + * @param name the name of the parameter. + * @return the value of the parameter, or empty + * @throws IllegalArgumentException if {@code name} does not match the name of a supported parameter. + */ + default OptionalLong valuesAsOptionalLong(final String name) + { + return hasValue(name) ? OptionalLong.of(valueAsLong(name)) : OptionalLong.empty(); + } + + /** + * Retrieve the values supplied for a parameter as an {@link OptionalDouble}. + * <p> + * Returns an {@link OptionalDouble#empty() empty Optional} if the parameter is optional and a value has not been supplied. + * + * @param name the name of the parameter. + * @return the value of the parameter, or empty + * @throws IllegalArgumentException if {@code name} does not match the name of a supported parameter. + */ + default OptionalDouble valuesAsOptionalDouble(final String name) + { + return hasValue(name) ? OptionalDouble.of(valueAsInt(name)) : OptionalDouble.empty(); + } + /** * Retrieve the values supplied for a parameter as an {@code int} array. * <p> diff --git a/src/main/java/com/lmax/simpledsl/api/SimpleDslArg.java b/src/main/java/com/lmax/simpledsl/api/SimpleDslArg.java index d953cea..feb19fc 100644 --- a/src/main/java/com/lmax/simpledsl/api/SimpleDslArg.java +++ b/src/main/java/com/lmax/simpledsl/api/SimpleDslArg.java @@ -16,6 +16,8 @@ package com.lmax.simpledsl.api; +import static java.util.Arrays.stream; + /** * The root type for all simple args. */ @@ -96,7 +98,8 @@ public SimpleDslArg setDefault(final String defaultValue) /** * Restrict the allowed values for this argument to the specified set. - * Specifying a value outside of this set will result in an exception being thrown when parsing the arguments. + * <p> + * Specifying a value outside this set will result in an exception being thrown when parsing the arguments. * * @param allowedValues the allowable values for this argument. * @return this argument @@ -107,6 +110,35 @@ public SimpleDslArg setAllowedValues(final String... allowedValues) return this; } + /** + * Restrict the allowed values for this argument to the specified set. + * <p> + * Specifying a value outside this set will result in an exception being thrown when parsing the arguments. + * + * @param <T> the type + * @param clazz the {@link Class} that provides the allowed values. + * @return this argument + * @throws IllegalArgumentException if allowed values cannot be determined from the provided class + */ + public <T> SimpleDslArg setAllowedValues(final Class<T> clazz) + { + if (Boolean.class.isAssignableFrom(clazz)) + { + return setAllowedValues("true", "false"); + } + else if (Enum.class.isAssignableFrom(clazz)) + { + return setAllowedValues( + stream(clazz.getEnumConstants()) + .map(constant -> (Enum<?>) constant) + .map(Enum::name) + .toArray(String[]::new) + ); + } + + throw new IllegalArgumentException("Cannot assign allowed values from class " + clazz.getName()); + } + /** * Allow multiple values to be specified for this argument, either as separate arguments or using comma (,) as a delimiter. * <p> diff --git a/src/test/java/com/lmax/simpledsl/internal/DslParamsImplTest.java b/src/test/java/com/lmax/simpledsl/internal/DslParamsImplTest.java index 3722ac4..02dbe74 100644 --- a/src/test/java/com/lmax/simpledsl/internal/DslParamsImplTest.java +++ b/src/test/java/com/lmax/simpledsl/internal/DslParamsImplTest.java @@ -24,6 +24,9 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; @@ -74,7 +77,7 @@ public void shouldReturnMultipleValues() final DslParams params = new DslParamsImpl(new DslArg[0], Collections.singletonMap("a", aParam)); - assertArrayEquals(new String[] {"Hello World", "Goodbye, Cruel World"}, params.values("a")); + assertArrayEquals(new String[]{"Hello World", "Goodbye, Cruel World"}, params.values("a")); } @Test @@ -85,7 +88,7 @@ public void shouldReturnMultipleValuesWhenUsingAMappingFunctionToGenericArray() final DslParams params = new DslParamsImpl(new DslArg[0], Collections.singletonMap("a", aParam)); assertArrayEquals( - new Object[] {TestValues.VALUE_1, TestValues.VALUE_2}, + new Object[]{TestValues.VALUE_1, TestValues.VALUE_2}, params.valuesAs("a", TestValues::valueOf) ); } @@ -98,7 +101,7 @@ public void shouldReturnMultipleValuesWhenUsingAMappingFunctionToArrayOfSpecific final DslParams params = new DslParamsImpl(new DslArg[0], Collections.singletonMap("a", aParam)); assertArrayEquals( - new TestValues[] {TestValues.VALUE_1, TestValues.VALUE_2}, + new TestValues[]{TestValues.VALUE_1, TestValues.VALUE_2}, params.valuesAs("a", TestValues.class, TestValues::valueOf) ); } @@ -111,7 +114,7 @@ public void shouldReturnMultipleValuesAsAnEnum() final DslParams params = new DslParamsImpl(new DslArg[0], Collections.singletonMap("a", aParam)); assertArrayEquals( - new TestValues[] {TestValues.VALUE_1, TestValues.VALUE_2}, + new TestValues[]{TestValues.VALUE_1, TestValues.VALUE_2}, params.valuesAs("a", TestValues.class) ); } @@ -244,6 +247,26 @@ public void shouldReturnNullValueAsParamWhenSimpleDslParamNotSpecified() assertNull(params.valueAsParam("a")); } + @Test + public void shouldReturnValueAsNamedParamForOptionalValueThatWasSpecified() + { + final SimpleDslParam aParam = new SimpleDslParam("a", singletonList("value")); + + final DslParams params = new DslParamsImpl(new DslArg[0], Collections.singletonMap("a", aParam)); + + assertEquals("b: value", params.valueAsParamNamed("a", "b")); + } + + @Test + public void shouldReturnNullValueAsNamedParamWhenSimpleDslParamNotSpecified() + { + final SimpleDslParam aParam = new SimpleDslParam("a", emptyList()); + + final DslParams params = new DslParamsImpl(new DslArg[0], Collections.singletonMap("a", aParam)); + + assertNull(params.valueAsParamNamed("a", "b")); + } + @Test public void shouldReturnValuesAsIntArray() { @@ -447,6 +470,36 @@ public void shouldReturnEmptyOptionalListWhenNoValuesAreSupplied() assertEquals(Optional.empty(), params.valuesAsOptional("a")); } + @Test + public void shouldReturnEmptyOptionalIntWhenNoValuesAreSupplied() + { + final SimpleDslParam aParam = new SimpleDslParam("a", emptyList()); + + final DslParams params = new DslParamsImpl(new DslArg[0], Collections.singletonMap("a", aParam)); + + assertEquals(OptionalInt.empty(), params.valuesAsOptionalInt("a")); + } + + @Test + public void shouldReturnEmptyOptionalLongWhenNoValuesAreSupplied() + { + final SimpleDslParam aParam = new SimpleDslParam("a", emptyList()); + + final DslParams params = new DslParamsImpl(new DslArg[0], Collections.singletonMap("a", aParam)); + + assertEquals(OptionalLong.empty(), params.valuesAsOptionalLong("a")); + } + + @Test + public void shouldReturnEmptyOptionalDoubleWhenNoValuesAreSupplied() + { + final SimpleDslParam aParam = new SimpleDslParam("a", emptyList()); + + final DslParams params = new DslParamsImpl(new DslArg[0], Collections.singletonMap("a", aParam)); + + assertEquals(OptionalDouble.empty(), params.valuesAsOptionalDouble("a")); + } + @Test public void shouldReturnOptionalListWhenMultipleParameterValueIsSupplied() { diff --git a/src/test/java/com/lmax/simpledsl/internal/DslParamsParserTest.java b/src/test/java/com/lmax/simpledsl/internal/DslParamsParserTest.java index 72fbac7..4353656 100644 --- a/src/test/java/com/lmax/simpledsl/internal/DslParamsParserTest.java +++ b/src/test/java/com/lmax/simpledsl/internal/DslParamsParserTest.java @@ -626,7 +626,79 @@ public void shouldMatchAllowedValuesCaseInsensitivelyAndReturnValuesUsingTheCase } @Test - public void shouldThrowAnExceptionIfRequiredArgeterMissingFromGroup() + public void shouldMatchAllowedValuesSpecifiedViaABoolean() + { + final String[] args = { + "thisWorks: true", + }; + final DslArg[] parameters = { + new RequiredArg("thisWorks").setAllowedValues(Boolean.class), + }; + + final DslParamsParser parser = new DslParamsParser(); + + final DslParams params = parser.parse(args, parameters); + + assertTrue(params.valueAsBoolean("thisWorks")); + } + + @Test + public void shouldThrowAnExceptionIfValuesDoesNotMatchAllowedValuesSpecifiedViaABoolean() + { + final String[] args = { + "thisWorks: NO", + }; + final DslArg[] parameters = { + new RequiredArg("thisWorks").setAllowedValues(Boolean.class), + }; + + final DslParamsParser parser = new DslParamsParser(); + + final IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, + () -> parser.parse(args, parameters)); + + assertEquals("thisWorks parameter value 'NO' must be one of: [true, false]", exception.getMessage()); + } + + @Test + public void shouldMatchAllowedValuesSpecifiedViaAnEnum() + { + final String[] args = { + "pet: DRAGON", + }; + final DslArg[] parameters = { + new RequiredArg("pet").setAllowedValues(PossiblePets.class), + }; + + final DslParamsParser parser = new DslParamsParser(); + + final DslParams params = parser.parse(args, parameters); + + assertEquals("DRAGON", params.value("pet")); + } + + @Test + public void shouldThrowAnExceptionIfValuesDoesNotMatchAllowedValuesSpecifiedViaAnEnum() + { + final String[] args = { + "pet: UNICORN", + }; + final DslArg[] parameters = { + new RequiredArg("pet").setAllowedValues(PossiblePets.class), + }; + + final DslParamsParser parser = new DslParamsParser(); + + final IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, + () -> parser.parse(args, parameters)); + + assertEquals("pet parameter value 'UNICORN' must be one of: [COW, SHEEP, GOAT, DRAGON]", exception.getMessage()); + } + + @Test + public void shouldThrowAnExceptionIfRequiredParameterMissingFromGroup() { final String[] args = {"a: value", "myGroup: Joe", "myGroup: Jenny", "myValue: 2"}; final DslArg[] params = { @@ -805,4 +877,12 @@ public void maybeShouldThrowExceptionIfRequiredParametersArePassedOutOfOrder() assertEquals("Unexpected ambiguous argument 1", exception.getMessage()); } + + private enum PossiblePets + { + COW, + SHEEP, + GOAT, + DRAGON + } } \ No newline at end of file