Allow more flexible Duration binding
Extend `BinderConversionService` to support `Duration` parsing of the more readable `10s` form (equivalent to 10 seconds). Standard ISO-8601 parsing also remains as an option. Fixes gh-11078pull/11066/merge
parent
2f6aca222e
commit
99afc4bc1f
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.context.properties.bind.convert;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link Converter} for {@link String} to {@link Duration}. Support
|
||||
* {@link Duration#parse(CharSequence)} as well a more readable {@code 10s} form.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class StringToDurationConverter implements Converter<String, Duration> {
|
||||
|
||||
private static Pattern ISO8601 = Pattern.compile("^[\\+\\-]?P.*$");
|
||||
|
||||
private static Pattern SIMPLE = Pattern.compile("^([\\+\\-]?\\d+)([a-zA-Z]{1,2})$");
|
||||
|
||||
private static final Map<String, ChronoUnit> UNITS;
|
||||
|
||||
static {
|
||||
Map<String, ChronoUnit> units = new LinkedHashMap<>();
|
||||
units.put("ns", ChronoUnit.NANOS);
|
||||
units.put("ms", ChronoUnit.MILLIS);
|
||||
units.put("s", ChronoUnit.SECONDS);
|
||||
units.put("m", ChronoUnit.MINUTES);
|
||||
units.put("h", ChronoUnit.HOURS);
|
||||
units.put("d", ChronoUnit.DAYS);
|
||||
UNITS = Collections.unmodifiableMap(units);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration convert(String source) {
|
||||
try {
|
||||
if (ISO8601.matcher(source).matches()) {
|
||||
return Duration.parse(source);
|
||||
}
|
||||
Matcher matcher = SIMPLE.matcher(source);
|
||||
Assert.state(matcher.matches(), "'" + source + "' is not a valid duration");
|
||||
long amount = Long.parseLong(matcher.group(1));
|
||||
ChronoUnit unit = getUnit(matcher.group(2));
|
||||
return Duration.of(amount, unit);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("'" + source + "' is not a valid duration",
|
||||
ex);
|
||||
}
|
||||
}
|
||||
|
||||
private ChronoUnit getUnit(String value) {
|
||||
ChronoUnit unit = UNITS.get(value.toLowerCase());
|
||||
Assert.state(unit != null, "Unknown unit '" + value + "'");
|
||||
return unit;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.context.properties.bind.convert;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link StringToDurationConverter}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class StringToDurationConverterTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private StringToDurationConverter converter = new StringToDurationConverter();
|
||||
|
||||
@Test
|
||||
public void convertWhenIso8601ShouldReturnDuration() throws Exception {
|
||||
assertThat(convert("PT20.345S")).isEqualTo(Duration.parse("PT20.345S"));
|
||||
assertThat(convert("PT15M")).isEqualTo(Duration.parse("PT15M"));
|
||||
assertThat(convert("+PT15M")).isEqualTo(Duration.parse("PT15M"));
|
||||
assertThat(convert("PT10H")).isEqualTo(Duration.parse("PT10H"));
|
||||
assertThat(convert("P2D")).isEqualTo(Duration.parse("P2D"));
|
||||
assertThat(convert("P2DT3H4M")).isEqualTo(Duration.parse("P2DT3H4M"));
|
||||
assertThat(convert("P2DT3H4M")).isEqualTo(Duration.parse("P2DT3H4M"));
|
||||
assertThat(convert("-PT6H3M")).isEqualTo(Duration.parse("-PT6H3M"));
|
||||
assertThat(convert("-PT-6H+3M")).isEqualTo(Duration.parse("-PT-6H+3M"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenSimpleNanosShouldReturnDuration() {
|
||||
assertThat(convert("10ns")).isEqualTo(Duration.ofNanos(10));
|
||||
assertThat(convert("10NS")).isEqualTo(Duration.ofNanos(10));
|
||||
assertThat(convert("+10ns")).isEqualTo(Duration.ofNanos(10));
|
||||
assertThat(convert("-10ns")).isEqualTo(Duration.ofNanos(-10));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenSimpleMillisShouldReturnDuration() {
|
||||
assertThat(convert("10ms")).isEqualTo(Duration.ofMillis(10));
|
||||
assertThat(convert("10MS")).isEqualTo(Duration.ofMillis(10));
|
||||
assertThat(convert("+10ms")).isEqualTo(Duration.ofMillis(10));
|
||||
assertThat(convert("-10ms")).isEqualTo(Duration.ofMillis(-10));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenSimpleSecondsShouldReturnDuration() {
|
||||
assertThat(convert("10s")).isEqualTo(Duration.ofSeconds(10));
|
||||
assertThat(convert("10S")).isEqualTo(Duration.ofSeconds(10));
|
||||
assertThat(convert("+10s")).isEqualTo(Duration.ofSeconds(10));
|
||||
assertThat(convert("-10s")).isEqualTo(Duration.ofSeconds(-10));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenSimpleMinutesShouldReturnDuration() {
|
||||
assertThat(convert("10m")).isEqualTo(Duration.ofMinutes(10));
|
||||
assertThat(convert("10M")).isEqualTo(Duration.ofMinutes(10));
|
||||
assertThat(convert("+10m")).isEqualTo(Duration.ofMinutes(10));
|
||||
assertThat(convert("-10m")).isEqualTo(Duration.ofMinutes(-10));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenSimpleHoursShouldReturnDuration() {
|
||||
assertThat(convert("10h")).isEqualTo(Duration.ofHours(10));
|
||||
assertThat(convert("10H")).isEqualTo(Duration.ofHours(10));
|
||||
assertThat(convert("+10h")).isEqualTo(Duration.ofHours(10));
|
||||
assertThat(convert("-10h")).isEqualTo(Duration.ofHours(-10));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenSimpleDaysShouldReturnDuration() {
|
||||
assertThat(convert("10d")).isEqualTo(Duration.ofDays(10));
|
||||
assertThat(convert("10D")).isEqualTo(Duration.ofDays(10));
|
||||
assertThat(convert("+10d")).isEqualTo(Duration.ofDays(10));
|
||||
assertThat(convert("-10d")).isEqualTo(Duration.ofDays(-10));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenBadFormatShouldThrowException() throws Exception {
|
||||
this.thrown.expect(IllegalStateException.class);
|
||||
this.thrown.expectMessage("'10foo' is not a valid duration");
|
||||
convert("10foo");
|
||||
}
|
||||
|
||||
private Duration convert(String source) {
|
||||
return this.converter.convert(source);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue