From aba50423606ae3e0440a07d508fe5085de1835fb Mon Sep 17 00:00:00 2001 From: Colin Redmond Date: Fri, 8 Dec 2023 09:43:24 -0800 Subject: [PATCH] feat: Allow empty collections to be returned instead of errors. They will only be returned if a node has been defined, if the node doesnt exist it will still throw errors. https://github.com/gestalt-config/gestalt/issues/127 --- README.md | 3 +- .../gestalt/config/decoder/ArrayDecoder.java | 6 +- .../config/decoder/CollectionDecoder.java | 6 +- .../gestalt/config/decoder/ListDecoder.java | 2 +- .../gestalt/config/decoder/SetDecoder.java | 2 +- .../config/decoder/ArrayDecoderTest.java | 64 +++++++++++++++++-- .../config/decoder/ListDecoderTest.java | 44 ++++++++++++- .../config/decoder/SetDecoderTest.java | 44 ++++++++++++- .../config/integration/GestaltSample.java | 25 ++++++++ 9 files changed, 176 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 97b7e962a..4baab3b86 100644 --- a/README.md +++ b/README.md @@ -503,7 +503,8 @@ Using the extension functions you don't need to specify the type if the return t val hosts: List = gestalt.getConfig("db.hosts", emptyList()) ``` | Gestalt Version | Kotlin Version | -|------------------|----------------| +|------------------|----------------| +| 0.25.0 + | 1.9 | | 0.17.0 + | 1.8 | | 0.13.0 to 0.16.6 | 1.7 | | 0.10.0 to 0.12.0 | 1.6 | diff --git a/gestalt-core/src/main/java/org/github/gestalt/config/decoder/ArrayDecoder.java b/gestalt-core/src/main/java/org/github/gestalt/config/decoder/ArrayDecoder.java index b4da66baa..b5096a6bf 100644 --- a/gestalt-core/src/main/java/org/github/gestalt/config/decoder/ArrayDecoder.java +++ b/gestalt-core/src/main/java/org/github/gestalt/config/decoder/ArrayDecoder.java @@ -42,11 +42,7 @@ public boolean canDecode(String path, Tags tags, ConfigNode node, TypeCapture public ValidateOf decode(String path, Tags tags, ConfigNode node, TypeCapture type, DecoderContext decoderContext) { ValidateOf results; if (node instanceof ArrayNode) { - if (node.size() > 0) { - results = arrayDecode(path, tags, node, type, decoderContext); - } else { - results = ValidateOf.inValid(new ValidationError.DecodingArrayMissingValue(path, name())); - } + results = arrayDecode(path, tags, node, type, decoderContext); } else if (node instanceof LeafNode) { if (node.getValue().isPresent()) { String value = node.getValue().get(); diff --git a/gestalt-core/src/main/java/org/github/gestalt/config/decoder/CollectionDecoder.java b/gestalt-core/src/main/java/org/github/gestalt/config/decoder/CollectionDecoder.java index b4687198e..ae2282fcb 100644 --- a/gestalt-core/src/main/java/org/github/gestalt/config/decoder/CollectionDecoder.java +++ b/gestalt-core/src/main/java/org/github/gestalt/config/decoder/CollectionDecoder.java @@ -30,11 +30,7 @@ public Priority priority() { public ValidateOf decode(String path, Tags tags, ConfigNode node, TypeCapture type, DecoderContext decoderContext) { ValidateOf results; if (node instanceof ArrayNode) { - if (node.size() > 0) { - results = arrayDecode(path, tags, node, type, decoderContext); - } else { - results = ValidateOf.inValid(new ValidationError.DecodingArrayMissingValue(path, name())); - } + results = arrayDecode(path, tags, node, type, decoderContext); } else if (node instanceof LeafNode) { if (node.getValue().isPresent()) { String value = node.getValue().get(); diff --git a/gestalt-core/src/main/java/org/github/gestalt/config/decoder/ListDecoder.java b/gestalt-core/src/main/java/org/github/gestalt/config/decoder/ListDecoder.java index 26d355303..ff8b82a56 100644 --- a/gestalt-core/src/main/java/org/github/gestalt/config/decoder/ListDecoder.java +++ b/gestalt-core/src/main/java/org/github/gestalt/config/decoder/ListDecoder.java @@ -52,6 +52,6 @@ protected ValidateOf> arrayDecode(String path, Tags tags, ConfigNode nod } - return ValidateOf.validateOf(!results.isEmpty() ? results : null, errors); + return ValidateOf.validateOf(results, errors); } } diff --git a/gestalt-core/src/main/java/org/github/gestalt/config/decoder/SetDecoder.java b/gestalt-core/src/main/java/org/github/gestalt/config/decoder/SetDecoder.java index bdc43b3d1..1f5fcdec7 100644 --- a/gestalt-core/src/main/java/org/github/gestalt/config/decoder/SetDecoder.java +++ b/gestalt-core/src/main/java/org/github/gestalt/config/decoder/SetDecoder.java @@ -52,6 +52,6 @@ protected ValidateOf> arrayDecode(String path, Tags tags, ConfigNode node } - return ValidateOf.validateOf(!results.isEmpty() ? results : null, errors); + return ValidateOf.validateOf(results, errors); } } diff --git a/gestalt-core/src/test/java/org/github/gestalt/config/decoder/ArrayDecoderTest.java b/gestalt-core/src/test/java/org/github/gestalt/config/decoder/ArrayDecoderTest.java index e2f76de78..9faae18c3 100644 --- a/gestalt-core/src/test/java/org/github/gestalt/config/decoder/ArrayDecoderTest.java +++ b/gestalt-core/src/test/java/org/github/gestalt/config/decoder/ArrayDecoderTest.java @@ -194,6 +194,49 @@ void arrayDecodeNullLeaf() { values.getErrors().get(0).description()); } + @Test + void arrayDecodeEmptyArrayNodeOk() { + ConfigNode nodes = new ArrayNode(List.of()); + ArrayDecoder decoder = new ArrayDecoder(); + + ValidateOf values = decoder.decode("db.hosts", Tags.of(), nodes, + TypeCapture.of(String[].class), new DecoderContext(decoderService, null)); + + Assertions.assertFalse(values.hasErrors()); + Assertions.assertTrue(values.hasResults()); + + String[] results = (String[]) values.results(); + Assertions.assertEquals(0, results.length); + } + + + @Test + void arrayDecodeNullArrayNodeOk() { + ConfigNode nodes = new ArrayNode(null); + ArrayDecoder decoder = new ArrayDecoder(); + + ValidateOf values = decoder.decode("db.hosts", Tags.of(), nodes, TypeCapture.of(String[].class), + new DecoderContext(decoderService, null)); + + Assertions.assertFalse(values.hasErrors()); + Assertions.assertTrue(values.hasResults()); + Assertions.assertEquals(0, values.results().length); + } + + @Test + void arrayDecodeEmptyLeafNodeOk() { + ConfigNode nodes = new LeafNode(""); + ArrayDecoder decoder = new ArrayDecoder(); + + ValidateOf values = decoder.decode("db.hosts", Tags.of(), nodes, TypeCapture.of(String[].class), + new DecoderContext(decoderService, null)); + + Assertions.assertFalse(values.hasErrors()); + Assertions.assertTrue(values.hasResults()); + Assertions.assertEquals(1, values.results().length); + Assertions.assertEquals("", values.results()[0]); + } + @Test void arrayDecodeWrongTypeDoubles() { @@ -282,6 +325,19 @@ void arrayDecodeMapNodeNullInside() { values.getErrors().get(0).description()); } + @Test + void arrayDecodeListNodeEmpty() { + ArrayDecoder decoder = new ArrayDecoder<>(); + + ValidateOf values = decoder.decode("db.hosts", Tags.of(), new ArrayNode(List.of()), + TypeCapture.of(Double[].class), new DecoderContext(decoderService, null)); + + Assertions.assertFalse(values.hasErrors()); + Assertions.assertTrue(values.hasResults()); + + Assertions.assertEquals(0, values.results().length); + } + @Test void arrayDecodeListNodeNullInside() { ArrayDecoder decoder = new ArrayDecoder<>(); @@ -289,12 +345,10 @@ void arrayDecodeListNodeNullInside() { ValidateOf values = decoder.decode("db.hosts", Tags.of(), new ArrayNode(null), TypeCapture.of(Double[].class), new DecoderContext(decoderService, null)); - Assertions.assertTrue(values.hasErrors()); - Assertions.assertFalse(values.hasResults()); + Assertions.assertFalse(values.hasErrors()); + Assertions.assertTrue(values.hasResults()); - Assertions.assertEquals(1, values.getErrors().size()); - Assertions.assertEquals("Array on path: db.hosts, has no value attempting to decode Array", - values.getErrors().get(0).description()); + Assertions.assertEquals(0, values.results().length); } @Test diff --git a/gestalt-core/src/test/java/org/github/gestalt/config/decoder/ListDecoderTest.java b/gestalt-core/src/test/java/org/github/gestalt/config/decoder/ListDecoderTest.java index 9b36cc7cd..1f2823423 100644 --- a/gestalt-core/src/test/java/org/github/gestalt/config/decoder/ListDecoderTest.java +++ b/gestalt-core/src/test/java/org/github/gestalt/config/decoder/ListDecoderTest.java @@ -198,6 +198,47 @@ void arrayDecodeNullNode() { values.getErrors().get(0).description()); } + @Test + void arrayDecodeEmptyArrayNodeOk() { + ConfigNode nodes = new ArrayNode(List.of()); + ListDecoder decoder = new ListDecoder(); + + ValidateOf> values = decoder.decode("db.hosts", Tags.of(), nodes, new TypeCapture>() { + }, new DecoderContext(decoderService, null)); + + Assertions.assertFalse(values.hasErrors()); + Assertions.assertTrue(values.hasResults()); + Assertions.assertEquals(0, values.results().size()); + } + + @Test + void arrayDecodeNullArrayNodeOk() { + ConfigNode nodes = new ArrayNode(null); + ListDecoder decoder = new ListDecoder(); + + ValidateOf> values = decoder.decode("db.hosts", Tags.of(), nodes, new TypeCapture>() { + }, new DecoderContext(decoderService, null)); + + Assertions.assertFalse(values.hasErrors()); + Assertions.assertTrue(values.hasResults()); + Assertions.assertEquals(0, values.results().size()); + } + + @Test + void arrayDecodeEmptyLeafNodeOk() { + ConfigNode nodes = new LeafNode(""); + ListDecoder decoder = new ListDecoder(); + + ValidateOf> values = decoder.decode("db.hosts", Tags.of(), nodes, new TypeCapture>() { + }, new DecoderContext(decoderService, null)); + + Assertions.assertFalse(values.hasErrors()); + Assertions.assertTrue(values.hasResults()); + Assertions.assertEquals(1, values.results().size()); + Assertions.assertEquals("", values.results().get(0)); + } + + @Test void arrayDecodeWrongTypeDoubles() { @@ -213,7 +254,8 @@ void arrayDecodeWrongTypeDoubles() { }, new DecoderContext(decoderService, null)); Assertions.assertTrue(values.hasErrors()); - Assertions.assertFalse(values.hasResults()); + Assertions.assertTrue(values.hasResults()); + Assertions.assertEquals(0, values.results().size()); Assertions.assertEquals(3, values.getErrors().size()); Assertions.assertEquals("Unable to parse a number on Path: db.hosts[0], from node: LeafNode{value='John'} " + diff --git a/gestalt-core/src/test/java/org/github/gestalt/config/decoder/SetDecoderTest.java b/gestalt-core/src/test/java/org/github/gestalt/config/decoder/SetDecoderTest.java index a8b069b68..1c26cf035 100644 --- a/gestalt-core/src/test/java/org/github/gestalt/config/decoder/SetDecoderTest.java +++ b/gestalt-core/src/test/java/org/github/gestalt/config/decoder/SetDecoderTest.java @@ -215,6 +215,47 @@ void arrayDecodeNullNode() { values.getErrors().get(0).description()); } + + @Test + void arrayDecodeEmptyArrayNodeOk() { + ConfigNode nodes = new ArrayNode(List.of()); + SetDecoder decoder = new SetDecoder(); + + ValidateOf> values = decoder.decode("db.hosts", Tags.of(), nodes, new TypeCapture>() { + }, new DecoderContext(decoderService, null)); + + Assertions.assertFalse(values.hasErrors()); + Assertions.assertTrue(values.hasResults()); + Assertions.assertEquals(0, values.results().size()); + } + + @Test + void arrayDecodeNullArrayNodeOk() { + ConfigNode nodes = new ArrayNode(null); + SetDecoder decoder = new SetDecoder(); + + ValidateOf> values = decoder.decode("db.hosts", Tags.of(), nodes, new TypeCapture>() { + }, new DecoderContext(decoderService, null)); + + Assertions.assertFalse(values.hasErrors()); + Assertions.assertTrue(values.hasResults()); + Assertions.assertEquals(0, values.results().size()); + } + + @Test + void arrayDecodeEmptyLeafNodeOk() { + ConfigNode nodes = new LeafNode(""); + SetDecoder decoder = new SetDecoder(); + + ValidateOf> values = decoder.decode("db.hosts", Tags.of(), nodes, new TypeCapture>() { + }, new DecoderContext(decoderService, null)); + + Assertions.assertFalse(values.hasErrors()); + Assertions.assertTrue(values.hasResults()); + Assertions.assertEquals(1, values.results().size()); + Assertions.assertTrue(values.results().contains("")); + } + @Test void arrayDecodeWrongTypeDoubles() { @@ -230,7 +271,8 @@ void arrayDecodeWrongTypeDoubles() { }, new DecoderContext(decoderService, null)); Assertions.assertTrue(values.hasErrors()); - Assertions.assertFalse(values.hasResults()); + Assertions.assertTrue(values.hasResults()); + Assertions.assertEquals(0, values.results().size()); Assertions.assertEquals(3, values.getErrors().size()); Assertions.assertEquals("Unable to parse a number on Path: db.hosts[0], from node: LeafNode{value='John'} " + diff --git a/gestalt-examples/gestalt-sample/src/test/java/org/github/gestalt/config/integration/GestaltSample.java b/gestalt-examples/gestalt-sample/src/test/java/org/github/gestalt/config/integration/GestaltSample.java index 68cbe0333..fa877c305 100644 --- a/gestalt-examples/gestalt-sample/src/test/java/org/github/gestalt/config/integration/GestaltSample.java +++ b/gestalt-examples/gestalt-sample/src/test/java/org/github/gestalt/config/integration/GestaltSample.java @@ -276,6 +276,31 @@ public void integrationTestDefaultTags() throws GestaltException { validateResults(gestalt); } + + @Test + public void testDontTreatEmptyCollectionAsErrors() throws GestaltException { + + String hoconStr = "database: {\n" + + " global: {\n" + + " volumes: []\n" + + " }\n" + + "}\n"; + + GestaltBuilder builder = new GestaltBuilder(); + Gestalt gestalt = builder + .addSource(StringConfigSourceBuilder.builder().setConfig(hoconStr).setFormat("conf").build()) + .build(); + + gestalt.loadConfigs(); + + try { + List admins = gestalt.getConfig("database.global.volumes", new TypeCapture<>() {}); + Assertions.assertEquals(0, admins.size()); + } catch (GestaltException e) { + Assertions.fail("Should not reach here"); + } + } + @Test public void integrationTestProxyPassThrough() throws GestaltException { // Create a map of configurations we wish to inject.