Cache guide does not properly print cache-stack values (#31943)
* Cache guide does not properly print cache-stack values Ability to choose expected values strict Fixes #31941 Signed-off-by: Martin Bartoš <mabartos@redhat.com> * Add Javadoc Signed-off-by: Martin Bartoš <mabartos@redhat.com> * Reflect non-strict values in docs Signed-off-by: Martin Bartoš <mabartos@redhat.com> * Use 'or any' in docs for non-strict expected values Signed-off-by: Martin Bartoš <mabartos@redhat.com> * Edit approved files for HelpCommandDistTest Signed-off-by: Martin Bartoš <mabartos@redhat.com> --------- Signed-off-by: Martin Bartoš <mabartos@redhat.com>
This commit is contained in:
parent
653f67768e
commit
f0162db56f
13 changed files with 119 additions and 31 deletions
|
@ -1,7 +1,8 @@
|
|||
<#macro expectedValues option>
|
||||
<#list ctx.options.getOption(option).expectedValues as expectedValue>
|
||||
* ${expectedValue}
|
||||
</#list>
|
||||
<#assign optionObj = ctx.options.getOption(option) />
|
||||
<#list optionObj.expectedValues as expectedValue>
|
||||
* ${expectedValue} <#if optionObj.defaultValue?has_content && expectedValue == optionObj.defaultValue> (default)</#if>
|
||||
</#list>
|
||||
</#macro>
|
||||
|
||||
<#macro list options buildIcon=true anchor=true>
|
||||
|
@ -39,7 +40,7 @@ ${option.deprecated.note!}<#if option.deprecated.newOptionsKeys?has_content><#if
|
|||
</#if>
|
||||
|
||||
|<#if option.expectedValues?has_content>
|
||||
<#list option.expectedValues as value>`+${value!}+`<#if option.defaultValue?has_content && value = option.defaultValue> (default)</#if><#if value?has_next>, </#if></#list>
|
||||
<#list option.expectedValues as value>`+${value!}+`<#if option.defaultValue?has_content && value = option.defaultValue> (default)</#if><#if value?has_next>, </#if></#list><#if !option.strictExpectedValues>, or any</#if>
|
||||
<#else>
|
||||
<#if option.defaultValue?has_content>[.options-default]#`+${option.defaultValue!}+`# (default)</#if><#if option.type?has_content && option.defaultValue?has_content> or </#if><#if option.type?has_content && !option.expectedValues?has_content>any `+${option.type!}+`</#if>
|
||||
</#if>
|
||||
|
@ -47,4 +48,4 @@ ${option.deprecated.note!}<#if option.deprecated.newOptionsKeys?has_content><#if
|
|||
</#list>
|
||||
|
||||
|===
|
||||
</#macro>
|
||||
</#macro>
|
||||
|
|
|
@ -50,6 +50,7 @@ public class Options {
|
|||
m.getDescription(),
|
||||
m.getDefaultValue().map(Object::toString).orElse(null),
|
||||
m.getExpectedValues(),
|
||||
m.isStrictExpectedValues(),
|
||||
m.getEnabledWhen().orElse(""),
|
||||
m.getDeprecatedMetadata().orElse(null)))
|
||||
.forEach(o -> options.computeIfAbsent(o.category, k -> new TreeSet<>(Comparator.comparing(Option::getKey))).add(o));
|
||||
|
@ -76,6 +77,7 @@ public class Options {
|
|||
m.getHelpText(),
|
||||
m.getDefaultValue() == null ? null : m.getDefaultValue().toString(),
|
||||
m.getOptions() == null ? Collections.emptyList() : m.getOptions(),
|
||||
true,
|
||||
"",
|
||||
null))
|
||||
.sorted(Comparator.comparing(Option::getKey)).collect(Collectors.toList());
|
||||
|
@ -169,6 +171,9 @@ public class Options {
|
|||
private String description;
|
||||
private final String defaultValue;
|
||||
private List<String> expectedValues;
|
||||
|
||||
private final boolean strictExpectedValues;
|
||||
|
||||
private final String enabledWhen;
|
||||
private final DeprecatedMetadata deprecated;
|
||||
|
||||
|
@ -179,6 +184,7 @@ public class Options {
|
|||
String description,
|
||||
String defaultValue,
|
||||
Iterable<String> expectedValues,
|
||||
boolean strictExpectedValues,
|
||||
String enabledWhen,
|
||||
DeprecatedMetadata deprecatedMetadata) {
|
||||
this.key = key;
|
||||
|
@ -188,6 +194,7 @@ public class Options {
|
|||
this.description = description;
|
||||
this.defaultValue = defaultValue;
|
||||
this.expectedValues = StreamSupport.stream(expectedValues.spliterator(), false).collect(Collectors.toList());
|
||||
this.strictExpectedValues = strictExpectedValues;
|
||||
this.enabledWhen = enabledWhen;
|
||||
this.deprecated = deprecatedMetadata;
|
||||
}
|
||||
|
@ -239,6 +246,10 @@ public class Options {
|
|||
return expectedValues;
|
||||
}
|
||||
|
||||
public boolean isStrictExpectedValues() {
|
||||
return strictExpectedValues;
|
||||
}
|
||||
|
||||
public String getEnabledWhen() {
|
||||
if (StringUtil.isBlank(enabledWhen)) return null;
|
||||
return enabledWhen;
|
||||
|
|
|
@ -50,9 +50,8 @@ public class CachingOptions {
|
|||
|
||||
public static final Option<Stack> CACHE_STACK = new OptionBuilder<>("cache-stack", Stack.class)
|
||||
.category(OptionCategory.CACHE)
|
||||
.expectedValues(List.of())
|
||||
.description("Define the default stack to use for cluster communication and node discovery. This option only takes effect "
|
||||
+ "if 'cache' is set to 'ispn'. Default: udp. Built-in values include: " + Stream.of(Stack.values()).map(Stack::name).collect(Collectors.joining(", ")))
|
||||
.expectedValues(false)
|
||||
.description("Define the default stack to use for cluster communication and node discovery. This option only takes effect if 'cache' is set to 'ispn'. Default: udp.")
|
||||
.build();
|
||||
|
||||
public static final Option<File> CACHE_CONFIG_FILE = new OptionBuilder<>(CACHE_CONFIG_FILE_PROPERTY, File.class)
|
||||
|
|
|
@ -14,9 +14,10 @@ public class Option<T> {
|
|||
private final String description;
|
||||
private final Optional<T> defaultValue;
|
||||
private final List<String> expectedValues;
|
||||
private final boolean strictExpectedValues;
|
||||
private final DeprecatedMetadata deprecatedMetadata;
|
||||
|
||||
public Option(Class<T> type, String key, OptionCategory category, boolean hidden, boolean buildTime, String description, Optional<T> defaultValue, List<String> expectedValues, DeprecatedMetadata deprecatedMetadata) {
|
||||
public Option(Class<T> type, String key, OptionCategory category, boolean hidden, boolean buildTime, String description, Optional<T> defaultValue, List<String> expectedValues, boolean strictExpectedValues, DeprecatedMetadata deprecatedMetadata) {
|
||||
this.type = type;
|
||||
this.key = key;
|
||||
this.category = category;
|
||||
|
@ -25,6 +26,7 @@ public class Option<T> {
|
|||
this.description = getDescriptionByCategorySupportLevel(description, category);
|
||||
this.defaultValue = defaultValue;
|
||||
this.expectedValues = expectedValues;
|
||||
this.strictExpectedValues = strictExpectedValues;
|
||||
this.deprecatedMetadata = deprecatedMetadata;
|
||||
}
|
||||
|
||||
|
@ -52,10 +54,24 @@ public class Option<T> {
|
|||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* If {@link #isStrictExpectedValues()} is false, custom values can be provided
|
||||
* Otherwise, only specified expected values can be used
|
||||
*
|
||||
* @return expected values
|
||||
*/
|
||||
public List<String> getExpectedValues() {
|
||||
return expectedValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Denotes whether a custom value can be provided among the expected values
|
||||
* If strict, application fails when some custom value is provided
|
||||
*/
|
||||
public boolean isStrictExpectedValues() {
|
||||
return strictExpectedValues;
|
||||
}
|
||||
|
||||
public Optional<DeprecatedMetadata> getDeprecatedMetadata() {
|
||||
return Optional.ofNullable(deprecatedMetadata);
|
||||
}
|
||||
|
@ -70,6 +86,7 @@ public class Option<T> {
|
|||
this.description,
|
||||
Optional.ofNullable(defaultValue),
|
||||
this.expectedValues,
|
||||
this.strictExpectedValues,
|
||||
this.deprecatedMetadata
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,17 +1,21 @@
|
|||
package org.keycloak.config;
|
||||
|
||||
import org.keycloak.common.util.CollectionUtil;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class OptionBuilder<T> {
|
||||
|
||||
private static final List<String> BOOLEAN_TYPE_VALUES = List.of(Boolean.TRUE.toString(), Boolean.FALSE.toString());
|
||||
|
||||
private final Class<T> type;
|
||||
private final Class<?> auxiliaryType;
|
||||
private final String key;
|
||||
private OptionCategory category;
|
||||
private boolean hidden;
|
||||
|
@ -19,6 +23,8 @@ public class OptionBuilder<T> {
|
|||
private String description;
|
||||
private Optional<T> defaultValue;
|
||||
private List<String> expectedValues = List.of();
|
||||
// Denotes whether a custom value can be provided among the expected values
|
||||
private boolean strictExpectedValues;
|
||||
private DeprecatedMetadata deprecatedMetadata;
|
||||
|
||||
public static <A> OptionBuilder<List<A>> listOptionBuilder(String key, Class<A> type) {
|
||||
|
@ -31,6 +37,7 @@ public class OptionBuilder<T> {
|
|||
|
||||
private OptionBuilder(String key, Class<T> type, Class<?> auxiliaryType) {
|
||||
this.type = type;
|
||||
this.auxiliaryType = auxiliaryType;
|
||||
if (type.isArray() || ((Collection.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type)) && type != java.util.List.class)) {
|
||||
throw new IllegalArgumentException("Non-List multi-valued options are not yet supported");
|
||||
}
|
||||
|
@ -39,17 +46,8 @@ public class OptionBuilder<T> {
|
|||
hidden = false;
|
||||
build = false;
|
||||
description = null;
|
||||
Class<?> expected = type;
|
||||
if (auxiliaryType != null) {
|
||||
expected = auxiliaryType;
|
||||
}
|
||||
defaultValue = Boolean.class.equals(expected) ? Optional.of((T) Boolean.FALSE) : Optional.empty();
|
||||
if (Boolean.class.equals(expected)) {
|
||||
expectedValues(BOOLEAN_TYPE_VALUES);
|
||||
}
|
||||
if (Enum.class.isAssignableFrom(expected)) {
|
||||
expectedValues((Class<? extends Enum>) expected);
|
||||
}
|
||||
defaultValue = Optional.empty();
|
||||
strictExpectedValues = true;
|
||||
}
|
||||
|
||||
public OptionBuilder<T> category(OptionCategory category) {
|
||||
|
@ -83,17 +81,40 @@ public class OptionBuilder<T> {
|
|||
}
|
||||
|
||||
public OptionBuilder<T> expectedValues(List<String> expected) {
|
||||
return expectedValues(true, expected);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param strict if only expected values are allowed, or some other custom value can be specified
|
||||
* @param expected expected values
|
||||
*/
|
||||
public OptionBuilder<T> expectedValues(boolean strict, List<String> expected) {
|
||||
this.strictExpectedValues = strict;
|
||||
this.expectedValues = expected;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OptionBuilder<T> expectedValues(Class<? extends Enum> expected) {
|
||||
this.expectedValues = List.of(expected.getEnumConstants()).stream().map(Object::toString).collect(Collectors.toList());
|
||||
return expectedValues(true, expected);
|
||||
}
|
||||
|
||||
public OptionBuilder<T> expectedValues(boolean strict, Class<? extends Enum> expected) {
|
||||
this.strictExpectedValues = strict;
|
||||
this.expectedValues = Stream.of(expected.getEnumConstants()).map(Object::toString).collect(Collectors.toList());
|
||||
return this;
|
||||
}
|
||||
|
||||
public OptionBuilder<T> expectedValues(T ... expected) {
|
||||
this.expectedValues = List.of(expected).stream().map(v -> v.toString()).collect(Collectors.toList());
|
||||
return expectedValues(true, expected);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param strict if only expected values are allowed, or some other custom value can be specified
|
||||
* @param expected expected values - if empty and the {@link #type} or {@link #auxiliaryType} is enum, values are inferred
|
||||
*/
|
||||
public OptionBuilder<T> expectedValues(boolean strict, T... expected) {
|
||||
this.strictExpectedValues = strict;
|
||||
this.expectedValues = Stream.of(expected).map(Object::toString).collect(Collectors.toList());
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -128,7 +149,26 @@ public class OptionBuilder<T> {
|
|||
deprecated();
|
||||
}
|
||||
|
||||
return new Option<T>(type, key, category, hidden, build, description, defaultValue, expectedValues, deprecatedMetadata);
|
||||
Class<?> expected = type;
|
||||
if (auxiliaryType != null) {
|
||||
expected = auxiliaryType;
|
||||
}
|
||||
|
||||
if (CollectionUtil.isEmpty(expectedValues)) {
|
||||
if (Boolean.class.equals(expected)) {
|
||||
expectedValues(strictExpectedValues, BOOLEAN_TYPE_VALUES);
|
||||
}
|
||||
|
||||
if (Enum.class.isAssignableFrom(expected)) {
|
||||
expectedValues(strictExpectedValues, (Class<? extends Enum>) expected);
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultValue.isEmpty() && Boolean.class.equals(expected)) {
|
||||
defaultValue = Optional.of((T) Boolean.FALSE);
|
||||
}
|
||||
|
||||
return new Option<T>(type, key, category, hidden, build, description, defaultValue, expectedValues, strictExpectedValues, deprecatedMetadata);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -831,7 +831,11 @@ public final class Picocli {
|
|||
}
|
||||
return value;
|
||||
}).toList();
|
||||
transformedDesc.append(" Possible values are: " + String.join(", ", decoratedExpectedValues) + ".");
|
||||
|
||||
var isStrictExpectedValues = mapper.getOption().isStrictExpectedValues();
|
||||
var printableValues = String.join(", ", decoratedExpectedValues) + (!isStrictExpectedValues ? ", or a custom one" : "");
|
||||
|
||||
transformedDesc.append(String.format(" Possible values are: %s.", printableValues));
|
||||
}
|
||||
|
||||
mapper.getDefaultValue()
|
||||
|
|
|
@ -193,10 +193,20 @@ public class PropertyMapper<T> {
|
|||
return this.option.getDescription();
|
||||
}
|
||||
|
||||
/**
|
||||
* If {@link #isStrictExpectedValues()} is false, custom values can be provided
|
||||
* Otherwise, only specified expected values can be used.
|
||||
*
|
||||
* @return expected values
|
||||
*/
|
||||
public List<String> getExpectedValues() {
|
||||
return this.option.getExpectedValues();
|
||||
}
|
||||
|
||||
public boolean isStrictExpectedValues() {
|
||||
return this.option.isStrictExpectedValues();
|
||||
}
|
||||
|
||||
public Optional<T> getDefaultValue() { return this.option.getDefaultValue(); }
|
||||
|
||||
public OptionCategory getCategory() {
|
||||
|
@ -378,7 +388,7 @@ public class PropertyMapper<T> {
|
|||
|
||||
void validateSingleValue(ConfigValue configValue, String v) {
|
||||
List<String> expectedValues = getExpectedValues();
|
||||
if (!expectedValues.isEmpty() && !expectedValues.contains(v)) {
|
||||
if (!expectedValues.isEmpty() && !expectedValues.contains(v) && getOption().isStrictExpectedValues()) {
|
||||
throw new PropertyException(
|
||||
String.format("Invalid value for option %s: %s.%s", getOptionAndSourceMessage(configValue), v,
|
||||
PropertyMapperParameterConsumer.getExpectedValuesMessage(expectedValues, expectedValues)));
|
||||
|
|
|
@ -48,7 +48,8 @@ Cache:
|
|||
--cache-stack <stack>
|
||||
Define the default stack to use for cluster communication and node discovery.
|
||||
This option only takes effect if 'cache' is set to 'ispn'. Default: udp.
|
||||
Built-in values include: tcp, udp, kubernetes, ec2, azure, google
|
||||
Possible values are: tcp, udp, kubernetes, ec2, azure, google, or a custom
|
||||
one.
|
||||
|
||||
Config:
|
||||
|
||||
|
|
|
@ -74,7 +74,8 @@ Cache:
|
|||
--cache-stack <stack>
|
||||
Define the default stack to use for cluster communication and node discovery.
|
||||
This option only takes effect if 'cache' is set to 'ispn'. Default: udp.
|
||||
Built-in values include: tcp, udp, kubernetes, ec2, azure, google
|
||||
Possible values are: tcp, udp, kubernetes, ec2, azure, google, or a custom
|
||||
one.
|
||||
|
||||
Config:
|
||||
|
||||
|
|
|
@ -49,7 +49,8 @@ Cache:
|
|||
--cache-stack <stack>
|
||||
Define the default stack to use for cluster communication and node discovery.
|
||||
This option only takes effect if 'cache' is set to 'ispn'. Default: udp.
|
||||
Built-in values include: tcp, udp, kubernetes, ec2, azure, google
|
||||
Possible values are: tcp, udp, kubernetes, ec2, azure, google, or a custom
|
||||
one.
|
||||
|
||||
Config:
|
||||
|
||||
|
|
|
@ -75,7 +75,8 @@ Cache:
|
|||
--cache-stack <stack>
|
||||
Define the default stack to use for cluster communication and node discovery.
|
||||
This option only takes effect if 'cache' is set to 'ispn'. Default: udp.
|
||||
Built-in values include: tcp, udp, kubernetes, ec2, azure, google
|
||||
Possible values are: tcp, udp, kubernetes, ec2, azure, google, or a custom
|
||||
one.
|
||||
|
||||
Config:
|
||||
|
||||
|
|
|
@ -49,7 +49,8 @@ Cache:
|
|||
--cache-stack <stack>
|
||||
Define the default stack to use for cluster communication and node discovery.
|
||||
This option only takes effect if 'cache' is set to 'ispn'. Default: udp.
|
||||
Built-in values include: tcp, udp, kubernetes, ec2, azure, google
|
||||
Possible values are: tcp, udp, kubernetes, ec2, azure, google, or a custom
|
||||
one.
|
||||
|
||||
Config:
|
||||
|
||||
|
|
|
@ -75,7 +75,8 @@ Cache:
|
|||
--cache-stack <stack>
|
||||
Define the default stack to use for cluster communication and node discovery.
|
||||
This option only takes effect if 'cache' is set to 'ispn'. Default: udp.
|
||||
Built-in values include: tcp, udp, kubernetes, ec2, azure, google
|
||||
Possible values are: tcp, udp, kubernetes, ec2, azure, google, or a custom
|
||||
one.
|
||||
|
||||
Config:
|
||||
|
||||
|
|
Loading…
Reference in a new issue