Improve error message when spring.config.import fails to resolve

Update `StandardConfigDataLocationResolver` to give a better error
message when a location cannot be resolved. Prior to this commit, a
location with a misspelling in the prefix would only give an error
about the file extension being not known.

Fixes gh-36243
This commit is contained in:
Phillip Webb 2024-06-25 17:06:10 -07:00
parent 85f6641a7e
commit 8bcdb4b06b
3 changed files with 29 additions and 9 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2023 the original author or authors. * Copyright 2012-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -69,17 +69,17 @@ class ConfigDataLocationResolvers {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
private List<ConfigDataLocationResolver<?>> reorder(List<ConfigDataLocationResolver> resolvers) { private List<ConfigDataLocationResolver<?>> reorder(List<ConfigDataLocationResolver> resolvers) {
List<ConfigDataLocationResolver<?>> reordered = new ArrayList<>(resolvers.size()); List<ConfigDataLocationResolver<?>> reordered = new ArrayList<>(resolvers.size());
StandardConfigDataLocationResolver resourceResolver = null; ConfigDataLocationResolver<?> standardConfigDataLocationResolver = null;
for (ConfigDataLocationResolver<?> resolver : resolvers) { for (ConfigDataLocationResolver<?> resolver : resolvers) {
if (resolver instanceof StandardConfigDataLocationResolver configDataLocationResolver) { if (resolver instanceof StandardConfigDataLocationResolver) {
resourceResolver = configDataLocationResolver; standardConfigDataLocationResolver = resolver;
} }
else { else {
reordered.add(resolver); reordered.add(resolver);
} }
} }
if (resourceResolver != null) { if (standardConfigDataLocationResolver != null) {
reordered.add(resourceResolver); reordered.add(standardConfigDataLocationResolver);
} }
return Collections.unmodifiableList(reordered); return Collections.unmodifiableList(reordered);
} }

View File

@ -45,6 +45,7 @@ import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.log.LogMessage; import org.springframework.core.log.LogMessage;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
@ -231,8 +232,17 @@ public class StandardConfigDataLocationResolver
if (configDataLocation.isOptional()) { if (configDataLocation.isOptional()) {
return Collections.emptySet(); return Collections.emptySet();
} }
throw new IllegalStateException("File extension is not known to any PropertySourceLoader. " if (configDataLocation.hasPrefix(PREFIX) || configDataLocation.hasPrefix(ResourceUtils.FILE_URL_PREFIX)
+ "If the location is meant to reference a directory, it must end in '/' or File.separator"); || configDataLocation.hasPrefix(ResourceUtils.CLASSPATH_URL_PREFIX)
|| configDataLocation.toString().indexOf(':') == -1) {
throw new IllegalStateException("File extension is not known to any PropertySourceLoader. "
+ "If the location is meant to reference a directory, it must end in '/' or File.separator");
}
throw new IllegalStateException(
"Incorrect ConfigDataLocationResolver chosen or file extension is not known to any PropertySourceLoader. "
+ "If the location is meant to reference a directory, it must end in '/' or File.separator. "
+ "The location is being resolved using the StandardConfigDataLocationResolver, "
+ "check the location prefix if a different resolver is expected");
} }
private String getLoadableFileExtension(PropertySourceLoader loader, String file) { private String getLoadableFileExtension(PropertySourceLoader loader, String file) {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2023 the original author or authors. * Copyright 2012-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -99,6 +99,16 @@ class StandardConfigDataLocationResolverTests {
.satisfies((ex) -> assertThat(ex.getCause()).hasMessageStartingWith("File extension is not known")); .satisfies((ex) -> assertThat(ex.getCause()).hasMessageStartingWith("File extension is not known"));
} }
@Test
void resolveWhenLocationHasUnknownPrefixAndNoMatchingLoaderThrowsException() {
ConfigDataLocation location = ConfigDataLocation
.of("typo:src/test/resources/configdata/properties/application.unknown");
assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location))
.withMessageStartingWith("Unable to load config data from")
.satisfies((ex) -> assertThat(ex.getCause()).hasMessageStartingWith(
"Incorrect ConfigDataLocationResolver chosen or file extension is not known to any PropertySourceLoader"));
}
@Test @Test
void resolveWhenLocationWildcardIsSpecifiedForClasspathLocationThrowsException() { void resolveWhenLocationWildcardIsSpecifiedForClasspathLocationThrowsException() {
ConfigDataLocation location = ConfigDataLocation.of("classpath*:application.properties"); ConfigDataLocation location = ConfigDataLocation.of("classpath*:application.properties");