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:
Martin Bartoš 2024-08-13 09:35:40 +01:00 committed by GitHub
parent 653f67768e
commit f0162db56f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 119 additions and 31 deletions

View file

@ -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>

View file

@ -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;

View file

@ -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)

View file

@ -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
);
}

View file

@ -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);
}
}

View file

@ -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()

View file

@ -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)));

View file

@ -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:

View file

@ -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:

View file

@ -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:

View file

@ -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:

View file

@ -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:

View file

@ -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: