Allow multiple default values to contain an escaped comma (#4657)

* Allow multiple default values to contain an escaped comma

An comma character can be a part of the value when escaped with a backslash.
The backslash will be removed, i.e. `\,` -> `,`

To have an actual backslash and comma, add an extra backslash, i.e. `\\,` -> `\,`

Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au>
This commit is contained in:
jimtng
2025-04-03 21:34:27 +02:00
committed by GitHub
parent 2aa19c73d0
commit 089961ac43
3 changed files with 59 additions and 39 deletions
@@ -23,6 +23,7 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNull;
@@ -51,7 +52,7 @@ import org.slf4j.LoggerFactory;
@NonNullByDefault
public class ConfigUtil {
private static final String DEFAULT_LIST_DELIMITER = ",";
private static final Pattern DEFAULT_LIST_SPLITTER = Pattern.compile("(?<!\\\\),");
/**
* Maps the provided (default) value of the given {@link ConfigDescriptionParameter} to the corresponding Java type.
@@ -61,10 +62,29 @@ public class ConfigUtil {
*
* @param parameter the {@link ConfigDescriptionParameter} which default value should be normalized (must not be
* null)
* @return the given value as the corresponding Java type or <code>null</code> if the value could not be converted
* @return the default value as the corresponding Java type, or
* a <code>List</code> of the corresponding Java type if the parameter contains multiple values.
* Returns <code>null</code> if the value could not be converted.
*/
public static @Nullable Object getDefaultValueAsCorrectType(ConfigDescriptionParameter parameter) {
return getDefaultValueAsCorrectType(parameter.getName(), parameter.getType(), parameter.getDefault());
if (parameter.isMultiple()) {
List<Object> defaultValues = Stream.of(DEFAULT_LIST_SPLITTER.split(parameter.getDefault())) //
.map(value -> value.trim().replace("\\,", ",")) //
.filter(not(String::isEmpty)) //
.map(value -> getDefaultValueAsCorrectType(parameter.getName(), parameter.getType(), value)) //
.filter(Objects::nonNull) //
.toList();
Integer multipleLimit = parameter.getMultipleLimit();
if (multipleLimit != null && defaultValues.size() > multipleLimit.intValue()) {
LoggerFactory.getLogger(ConfigUtil.class).warn(
"Number of default values ({}) for parameter '{}' is greater than multiple limit ({})",
defaultValues.size(), parameter.getName(), multipleLimit);
}
return defaultValues;
} else {
return getDefaultValueAsCorrectType(parameter.getName(), parameter.getType(), parameter.getDefault());
}
}
static @Nullable Object getDefaultValueAsCorrectType(String parameterName, Type parameterType,
@@ -117,33 +137,9 @@ public class ConfigUtil {
for (ConfigDescriptionParameter parameter : configDescription.getParameters()) {
String defaultValue = parameter.getDefault();
if (defaultValue != null && configuration.get(parameter.getName()) == null) {
if (parameter.isMultiple()) {
if (defaultValue.contains(DEFAULT_LIST_DELIMITER)) {
List<Object> values = (List<Object>) Stream.of(defaultValue.split(DEFAULT_LIST_DELIMITER))
.map(String::trim) //
.filter(not(String::isEmpty)) //
.map(value -> ConfigUtil.getDefaultValueAsCorrectType(parameter.getName(),
parameter.getType(), value)) //
.filter(Objects::nonNull) //
.toList();
Integer multipleLimit = parameter.getMultipleLimit();
if (multipleLimit != null && values.size() > multipleLimit.intValue()) {
LoggerFactory.getLogger(ConfigUtil.class).warn(
"Number of default values ({}) for parameter '{}' is greater than multiple limit ({})",
values.size(), parameter.getName(), multipleLimit);
}
configuration.put(parameter.getName(), values);
} else {
Object value = ConfigUtil.getDefaultValueAsCorrectType(parameter);
if (value != null) {
configuration.put(parameter.getName(), List.of(value));
}
}
} else {
Object value = ConfigUtil.getDefaultValueAsCorrectType(parameter);
if (value != null) {
configuration.put(parameter.getName(), value);
}
Object value = ConfigUtil.getDefaultValueAsCorrectType(parameter);
if (value != null) {
configuration.put(parameter.getName(), value);
}
}
}
@@ -15,10 +15,11 @@ package org.openhab.core.io.rest.core.config;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;
import org.openhab.core.config.core.ConfigDescriptionParameter;
import org.openhab.core.config.core.ConfigDescriptionParameter.Type;
import org.openhab.core.config.core.ConfigDescriptionParameterBuilder;
import org.openhab.core.config.core.ConfigUtil;
import org.openhab.core.config.core.dto.ConfigDescriptionParameterDTO;
import org.openhab.core.config.core.dto.FilterCriteriaDTO;
import org.openhab.core.config.core.dto.ParameterOptionDTO;
@@ -26,15 +27,18 @@ import org.openhab.core.config.core.dto.ParameterOptionDTO;
/**
* This is an enriched data transfer object that is used to serialize config descriptions parameters with a list of
* default values if a configuration description defines <code>multiple="true"</code>.
*
* The default values are split by a comma. Individual values that contain a comma
* must be escaped with a backslash character. The backslash character will be
* removed from the value.
*
* @author Christoph Weitkamp - Initial contribution
*/
public class EnrichedConfigDescriptionParameterDTO extends ConfigDescriptionParameterDTO {
private static final String DEFAULT_LIST_DELIMITER = ",";
public Collection<String> defaultValues;
@SuppressWarnings("unchecked")
public EnrichedConfigDescriptionParameterDTO(String name, Type type, BigDecimal minimum, BigDecimal maximum,
BigDecimal stepsize, String pattern, Boolean required, Boolean readOnly, Boolean multiple, String context,
String defaultValue, String label, String description, List<ParameterOptionDTO> options,
@@ -45,11 +49,10 @@ public class EnrichedConfigDescriptionParameterDTO extends ConfigDescriptionPara
unitLabel, verify);
if (multiple && defaultValue != null) {
if (defaultValue.contains(DEFAULT_LIST_DELIMITER)) {
defaultValues = Stream.of(defaultValue.split(DEFAULT_LIST_DELIMITER)).map(String::trim)
.filter(v -> !v.isEmpty()).toList();
} else {
defaultValues = Set.of(defaultValue);
ConfigDescriptionParameter parameter = ConfigDescriptionParameterBuilder.create(name, type)
.withMultiple(multiple).withDefault(defaultValue).withMultipleLimit(multipleLimit).build();
if (ConfigUtil.getDefaultValueAsCorrectType(parameter) instanceof List defaultValues) {
this.defaultValues = (Collection<String>) defaultValues;
}
}
}
@@ -92,4 +92,25 @@ public class EnrichedConfigDescriptionDTOMapperTest {
assertThat(ecdpdto.defaultValues, hasSize(3));
assertThat(ecdpdto.defaultValues, is(equalTo(List.of("first value", "second value", "third value"))));
}
@Test
public void testThatDefaultValuesDontSplitEscapedCommas() {
final String CONFIG_PARAMETER_DEFAULT_VALUE = "Me\\, myself\\, and I,You \\\\,";
ConfigDescriptionParameter configDescriptionParameter = ConfigDescriptionParameterBuilder
.create(CONFIG_PARAMETER_NAME, Type.TEXT).withDefault(CONFIG_PARAMETER_DEFAULT_VALUE).withMultiple(true)
.build();
ConfigDescription configDescription = ConfigDescriptionBuilder.create(CONFIG_URI)
.withParameter(configDescriptionParameter).build();
ConfigDescriptionDTO cddto = EnrichedConfigDescriptionDTOMapper.map(configDescription);
assertThat(cddto.parameters, hasSize(1));
ConfigDescriptionParameterDTO cdpdto = cddto.parameters.getFirst();
assertThat(cdpdto, instanceOf(EnrichedConfigDescriptionParameterDTO.class));
assertThat(cdpdto.defaultValue, is(CONFIG_PARAMETER_DEFAULT_VALUE));
EnrichedConfigDescriptionParameterDTO ecdpdto = (EnrichedConfigDescriptionParameterDTO) cdpdto;
assertThat(ecdpdto.defaultValues, is(notNullValue()));
assertThat(ecdpdto.defaultValues, hasSize(2));
assertThat(ecdpdto.defaultValues, is(equalTo(List.of("Me, myself, and I", "You \\,"))));
}
}