mirror of
https://github.com/lightbend/config.git
synced 2025-03-22 15:20:26 +08:00
Merge pull request #286 from typesafehub/get-is-null
Add Config.hasPathOrNull and Config.getIsNull
This commit is contained in:
commit
a3c255e50a
@ -336,6 +336,10 @@ options:
|
|||||||
`Config`; `ConfigObject` implements `java.util.Map<String,?>` and
|
`Config`; `ConfigObject` implements `java.util.Map<String,?>` and
|
||||||
the `get()` method on `Map` returns null for missing keys. See
|
the `get()` method on `Map` returns null for missing keys. See
|
||||||
the API docs for more detail on `Config` vs. `ConfigObject`.
|
the API docs for more detail on `Config` vs. `ConfigObject`.
|
||||||
|
6. Set the setting to `null` in `reference.conf`, then use
|
||||||
|
`Config.getIsNull` and `Config.hasPathOrNull` to handle `null`
|
||||||
|
in a special way while still throwing an exception if the setting
|
||||||
|
is entirely absent.
|
||||||
|
|
||||||
The *recommended* path (for most cases, in most apps) is that you
|
The *recommended* path (for most cases, in most apps) is that you
|
||||||
require all settings to be present in either `reference.conf` or
|
require all settings to be present in either `reference.conf` or
|
||||||
|
@ -416,6 +416,47 @@ public interface Config extends ConfigMergeable {
|
|||||||
*/
|
*/
|
||||||
boolean hasPath(String path);
|
boolean hasPath(String path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a value is present at the given path, even
|
||||||
|
* if the value is null. Most of the getters on
|
||||||
|
* <code>Config</code> will throw if you try to get a null
|
||||||
|
* value, so if you plan to call {@link #getValue(String)},
|
||||||
|
* {@link #getInt(String)}, or another getter you may want to
|
||||||
|
* use plain {@link #hasPath(String)} rather than this method.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* To handle all three cases (unset, null, and a non-null value)
|
||||||
|
* the code might look like:
|
||||||
|
* <pre><code>
|
||||||
|
* if (config.hasPathOrNull(path)) {
|
||||||
|
* if (config.getIsNull(path)) {
|
||||||
|
* // handle null setting
|
||||||
|
* } else {
|
||||||
|
* // get and use non-null setting
|
||||||
|
* }
|
||||||
|
* } else {
|
||||||
|
* // handle entirely unset path
|
||||||
|
* }
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* <p> However, the usual thing is to allow entirely unset
|
||||||
|
* paths to be a bug that throws an exception (because you set
|
||||||
|
* a default in your <code>reference.conf</code>), so in that
|
||||||
|
* case it's OK to call {@link #getIsNull(String)} without
|
||||||
|
* checking <code>hasPathOrNull</code> first.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Note that path expressions have a syntax and sometimes require quoting
|
||||||
|
* (see {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath}).
|
||||||
|
*
|
||||||
|
* @param path
|
||||||
|
* the path expression
|
||||||
|
* @return true if a value is present at the path, even if the value is null
|
||||||
|
* @throws ConfigException.BadPath
|
||||||
|
* if the path expression is invalid
|
||||||
|
*/
|
||||||
|
boolean hasPathOrNull(String path);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the {@code Config}'s root object contains no key-value
|
* Returns true if the {@code Config}'s root object contains no key-value
|
||||||
* pairs.
|
* pairs.
|
||||||
@ -448,6 +489,33 @@ public interface Config extends ConfigMergeable {
|
|||||||
*/
|
*/
|
||||||
Set<Map.Entry<String, ConfigValue>> entrySet();
|
Set<Map.Entry<String, ConfigValue>> entrySet();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a value is set to null at the given path,
|
||||||
|
* but throws an exception if the value is entirely
|
||||||
|
* unset. This method will not throw if {@link
|
||||||
|
* #hasPathOrNull(String)} returned true for the same path, so
|
||||||
|
* to avoid any possible exception check
|
||||||
|
* <code>hasPathOrNull()</code> first. However, an exception
|
||||||
|
* for unset paths will usually be the right thing (because a
|
||||||
|
* <code>reference.conf</code> should exist that has the path
|
||||||
|
* set, the path should never be unset unless something is
|
||||||
|
* broken).
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Note that path expressions have a syntax and sometimes require quoting
|
||||||
|
* (see {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath}).
|
||||||
|
*
|
||||||
|
* @param path
|
||||||
|
* the path expression
|
||||||
|
* @return true if the value exists and is null, false if it
|
||||||
|
* exists and is not null
|
||||||
|
* @throws ConfigException.BadPath
|
||||||
|
* if the path expression is invalid
|
||||||
|
* @throws ConfigException.Missing
|
||||||
|
* if value is not set at all
|
||||||
|
*/
|
||||||
|
boolean getIsNull(String path);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param path
|
* @param path
|
||||||
|
@ -79,8 +79,7 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
|||||||
return new SimpleConfig((AbstractConfigObject) resolved);
|
return new SimpleConfig((AbstractConfigObject) resolved);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private ConfigValue hasPathPeek(String pathExpression) {
|
||||||
public boolean hasPath(String pathExpression) {
|
|
||||||
Path path = Path.newPath(pathExpression);
|
Path path = Path.newPath(pathExpression);
|
||||||
ConfigValue peeked;
|
ConfigValue peeked;
|
||||||
try {
|
try {
|
||||||
@ -88,9 +87,21 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
|||||||
} catch (ConfigException.NotResolved e) {
|
} catch (ConfigException.NotResolved e) {
|
||||||
throw ConfigImpl.improveNotResolved(path, e);
|
throw ConfigImpl.improveNotResolved(path, e);
|
||||||
}
|
}
|
||||||
|
return peeked;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasPath(String pathExpression) {
|
||||||
|
ConfigValue peeked = hasPathPeek(pathExpression);
|
||||||
return peeked != null && peeked.valueType() != ConfigValueType.NULL;
|
return peeked != null && peeked.valueType() != ConfigValueType.NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasPathOrNull(String path) {
|
||||||
|
ConfigValue peeked = hasPathPeek(path);
|
||||||
|
return peeked != null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEmpty() {
|
public boolean isEmpty() {
|
||||||
return object.isEmpty();
|
return object.isEmpty();
|
||||||
@ -121,8 +132,21 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
|||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static private AbstractConfigValue throwIfNull(AbstractConfigValue v, ConfigValueType expected, Path originalPath) {
|
||||||
|
if (v.valueType() == ConfigValueType.NULL)
|
||||||
|
throw new ConfigException.Null(v.origin(), originalPath.render(),
|
||||||
|
expected != null ? expected.name() : null);
|
||||||
|
else
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
static private AbstractConfigValue findKey(AbstractConfigObject self, String key,
|
static private AbstractConfigValue findKey(AbstractConfigObject self, String key,
|
||||||
ConfigValueType expected, Path originalPath) {
|
ConfigValueType expected, Path originalPath) {
|
||||||
|
return throwIfNull(findKeyOrNull(self, key, expected, originalPath), expected, originalPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
static private AbstractConfigValue findKeyOrNull(AbstractConfigObject self, String key,
|
||||||
|
ConfigValueType expected, Path originalPath) {
|
||||||
AbstractConfigValue v = self.peekAssumingResolved(key, originalPath);
|
AbstractConfigValue v = self.peekAssumingResolved(key, originalPath);
|
||||||
if (v == null)
|
if (v == null)
|
||||||
throw new ConfigException.Missing(originalPath.render());
|
throw new ConfigException.Missing(originalPath.render());
|
||||||
@ -130,10 +154,7 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
|||||||
if (expected != null)
|
if (expected != null)
|
||||||
v = DefaultTransformer.transform(v, expected);
|
v = DefaultTransformer.transform(v, expected);
|
||||||
|
|
||||||
if (v.valueType() == ConfigValueType.NULL)
|
if (expected != null && (v.valueType() != expected && v.valueType() != ConfigValueType.NULL))
|
||||||
throw new ConfigException.Null(v.origin(), originalPath.render(),
|
|
||||||
expected != null ? expected.name() : null);
|
|
||||||
else if (expected != null && v.valueType() != expected)
|
|
||||||
throw new ConfigException.WrongType(v.origin(), originalPath.render(), expected.name(),
|
throw new ConfigException.WrongType(v.origin(), originalPath.render(), expected.name(),
|
||||||
v.valueType().name());
|
v.valueType().name());
|
||||||
else
|
else
|
||||||
@ -142,17 +163,22 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
|||||||
|
|
||||||
static private AbstractConfigValue find(AbstractConfigObject self, Path path,
|
static private AbstractConfigValue find(AbstractConfigObject self, Path path,
|
||||||
ConfigValueType expected, Path originalPath) {
|
ConfigValueType expected, Path originalPath) {
|
||||||
|
return throwIfNull(findOrNull(self, path, expected, originalPath), expected, originalPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
static private AbstractConfigValue findOrNull(AbstractConfigObject self, Path path,
|
||||||
|
ConfigValueType expected, Path originalPath) {
|
||||||
try {
|
try {
|
||||||
String key = path.first();
|
String key = path.first();
|
||||||
Path next = path.remainder();
|
Path next = path.remainder();
|
||||||
if (next == null) {
|
if (next == null) {
|
||||||
return findKey(self, key, expected, originalPath);
|
return findKeyOrNull(self, key, expected, originalPath);
|
||||||
} else {
|
} else {
|
||||||
AbstractConfigObject o = (AbstractConfigObject) findKey(self, key,
|
AbstractConfigObject o = (AbstractConfigObject) findKey(self, key,
|
||||||
ConfigValueType.OBJECT,
|
ConfigValueType.OBJECT,
|
||||||
originalPath.subPath(0, originalPath.length() - next.length()));
|
originalPath.subPath(0, originalPath.length() - next.length()));
|
||||||
assert (o != null); // missing was supposed to throw
|
assert (o != null); // missing was supposed to throw
|
||||||
return find(o, next, expected, originalPath);
|
return findOrNull(o, next, expected, originalPath);
|
||||||
}
|
}
|
||||||
} catch (ConfigException.NotResolved e) {
|
} catch (ConfigException.NotResolved e) {
|
||||||
throw ConfigImpl.improveNotResolved(path, e);
|
throw ConfigImpl.improveNotResolved(path, e);
|
||||||
@ -160,7 +186,7 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AbstractConfigValue find(Path pathExpression, ConfigValueType expected, Path originalPath) {
|
AbstractConfigValue find(Path pathExpression, ConfigValueType expected, Path originalPath) {
|
||||||
return find(object, pathExpression, expected, originalPath);
|
return throwIfNull(findOrNull(object, pathExpression, expected, originalPath), expected, originalPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
AbstractConfigValue find(String pathExpression, ConfigValueType expected) {
|
AbstractConfigValue find(String pathExpression, ConfigValueType expected) {
|
||||||
@ -168,11 +194,26 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
|||||||
return find(path, expected, path);
|
return find(path, expected, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private AbstractConfigValue findOrNull(Path pathExpression, ConfigValueType expected, Path originalPath) {
|
||||||
|
return findOrNull(object, pathExpression, expected, originalPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AbstractConfigValue findOrNull(String pathExpression, ConfigValueType expected) {
|
||||||
|
Path path = Path.newPath(pathExpression);
|
||||||
|
return findOrNull(path, expected, path);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AbstractConfigValue getValue(String path) {
|
public AbstractConfigValue getValue(String path) {
|
||||||
return find(path, null);
|
return find(path, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getIsNull(String path) {
|
||||||
|
AbstractConfigValue v = findOrNull(path, null);
|
||||||
|
return (v.valueType() == ConfigValueType.NULL);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean getBoolean(String path) {
|
public boolean getBoolean(String path) {
|
||||||
ConfigValue v = find(path, ConfigValueType.BOOLEAN);
|
ConfigValue v = find(path, ConfigValueType.BOOLEAN);
|
||||||
|
@ -1057,4 +1057,38 @@ include "onclasspath"
|
|||||||
assertFalse("did not get bar-file.conf", conf.hasPath("bar-file"))
|
assertFalse("did not get bar-file.conf", conf.hasPath("bar-file"))
|
||||||
assertFalse("did not get subdir/baz.conf", conf.hasPath("baz"))
|
assertFalse("did not get subdir/baz.conf", conf.hasPath("baz"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
def hasPathOrNullWorks(): Unit = {
|
||||||
|
val conf = ConfigFactory.parseString("x.a=null,x.b=42")
|
||||||
|
assertFalse("hasPath says false for null", conf.hasPath("x.a"))
|
||||||
|
assertTrue("hasPathOrNull says true for null", conf.hasPathOrNull("x.a"))
|
||||||
|
|
||||||
|
assertTrue("hasPath says true for non-null", conf.hasPath("x.b"))
|
||||||
|
assertTrue("hasPathOrNull says true for non-null", conf.hasPathOrNull("x.b"))
|
||||||
|
|
||||||
|
assertFalse("hasPath says false for missing", conf.hasPath("x.c"))
|
||||||
|
assertFalse("hasPathOrNull says false for missing", conf.hasPathOrNull("x.c"))
|
||||||
|
|
||||||
|
// this is to be sure we handle a null along the path correctly
|
||||||
|
assertFalse("hasPath says false for missing under null", conf.hasPath("x.a.y"))
|
||||||
|
assertFalse("hasPathOrNull says false for missing under null", conf.hasPathOrNull("x.a.y"))
|
||||||
|
|
||||||
|
// this is to be sure we handle missing along the path correctly
|
||||||
|
assertFalse("hasPath says false for missing under missing", conf.hasPath("x.c.y"))
|
||||||
|
assertFalse("hasPathOrNull says false for missing under missing", conf.hasPathOrNull("x.c.y"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
def getIsNullWorks(): Unit = {
|
||||||
|
val conf = ConfigFactory.parseString("x.a=null,x.b=42")
|
||||||
|
|
||||||
|
assertTrue("getIsNull says true for null", conf.getIsNull("x.a"))
|
||||||
|
assertFalse("getIsNull says false for non-null", conf.getIsNull("x.b"))
|
||||||
|
intercept[ConfigException.Missing] { conf.getIsNull("x.c") }
|
||||||
|
// missing underneath null
|
||||||
|
intercept[ConfigException.Missing] { conf.getIsNull("x.a.y") }
|
||||||
|
// missing underneath missing
|
||||||
|
intercept[ConfigException.Missing] { conf.getIsNull("x.c.y") }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user