mirror of
https://github.com/lightbend/config.git
synced 2025-03-17 04:40:41 +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
|
||||
the `get()` method on `Map` returns null for missing keys. See
|
||||
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
|
||||
require all settings to be present in either `reference.conf` or
|
||||
|
@ -416,6 +416,47 @@ public interface Config extends ConfigMergeable {
|
||||
*/
|
||||
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
|
||||
* pairs.
|
||||
@ -448,6 +489,33 @@ public interface Config extends ConfigMergeable {
|
||||
*/
|
||||
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
|
||||
|
@ -79,8 +79,7 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
||||
return new SimpleConfig((AbstractConfigObject) resolved);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPath(String pathExpression) {
|
||||
private ConfigValue hasPathPeek(String pathExpression) {
|
||||
Path path = Path.newPath(pathExpression);
|
||||
ConfigValue peeked;
|
||||
try {
|
||||
@ -88,9 +87,21 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
||||
} catch (ConfigException.NotResolved 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPathOrNull(String path) {
|
||||
ConfigValue peeked = hasPathPeek(path);
|
||||
return peeked != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return object.isEmpty();
|
||||
@ -121,8 +132,21 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
||||
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,
|
||||
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);
|
||||
if (v == null)
|
||||
throw new ConfigException.Missing(originalPath.render());
|
||||
@ -130,10 +154,7 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
||||
if (expected != null)
|
||||
v = DefaultTransformer.transform(v, expected);
|
||||
|
||||
if (v.valueType() == ConfigValueType.NULL)
|
||||
throw new ConfigException.Null(v.origin(), originalPath.render(),
|
||||
expected != null ? expected.name() : null);
|
||||
else if (expected != null && v.valueType() != expected)
|
||||
if (expected != null && (v.valueType() != expected && v.valueType() != ConfigValueType.NULL))
|
||||
throw new ConfigException.WrongType(v.origin(), originalPath.render(), expected.name(),
|
||||
v.valueType().name());
|
||||
else
|
||||
@ -142,17 +163,22 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
||||
|
||||
static private AbstractConfigValue find(AbstractConfigObject self, Path path,
|
||||
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 {
|
||||
String key = path.first();
|
||||
Path next = path.remainder();
|
||||
if (next == null) {
|
||||
return findKey(self, key, expected, originalPath);
|
||||
return findKeyOrNull(self, key, expected, originalPath);
|
||||
} else {
|
||||
AbstractConfigObject o = (AbstractConfigObject) findKey(self, key,
|
||||
ConfigValueType.OBJECT,
|
||||
originalPath.subPath(0, originalPath.length() - next.length()));
|
||||
assert (o != null); // missing was supposed to throw
|
||||
return find(o, next, expected, originalPath);
|
||||
return findOrNull(o, next, expected, originalPath);
|
||||
}
|
||||
} catch (ConfigException.NotResolved 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) {
|
||||
return find(object, pathExpression, expected, originalPath);
|
||||
return throwIfNull(findOrNull(object, pathExpression, expected, originalPath), expected, originalPath);
|
||||
}
|
||||
|
||||
AbstractConfigValue find(String pathExpression, ConfigValueType expected) {
|
||||
@ -168,11 +194,26 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
||||
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
|
||||
public AbstractConfigValue getValue(String path) {
|
||||
return find(path, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getIsNull(String path) {
|
||||
AbstractConfigValue v = findOrNull(path, null);
|
||||
return (v.valueType() == ConfigValueType.NULL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBoolean(String path) {
|
||||
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 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