Skip to content

Commit

Permalink
Merge pull request #164 from gestalt-config/feat/kotlin-consistency
Browse files Browse the repository at this point in the history
feat: update data class decoder to be more consistent with other complex object decoders.
  • Loading branch information
credmond-git authored Mar 13, 2024
2 parents 7a554b0 + 2402527 commit c09c02b
Show file tree
Hide file tree
Showing 26 changed files with 779 additions and 197 deletions.
40 changes: 39 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,10 @@ To retrieve a complex object, you need to pass in the class for Gestalt to retur

There are two configuration options that allow you to control when errors are thrown when decoding complex objects.

```java
HttpPool pool = gestalt.getConfig("http.pool", HttpPool.class);
```

### `treatMissingValuesAsErrors`

Treat missing field values in an object, proxy, record, or data object as errors. This will cause the API to either throw errors or return an empty optional.
Expand All @@ -270,9 +274,43 @@ Treat missing discretionary values (optional, fields with defaults, fields with
- If this is `true`, if a field is missing and would have had a default, it will fail and throw an exception.


#### Examples of required and discretionary fields.

Here are some examples of required and discretionary fields and which setting can control if they are treated as errors or allowed.

```java
HttpPool pool = gestalt.getConfig("http.pool", HttpPool.class);
public class DBInfo {
private Optional<Integer> port; // discretionary value controlled by treatMissingValuesAsErrors
private String uri = "my.sql.db"; // discretionary value controlled by treatMissingDiscretionaryValuesAsErrors
private String password; // required value controlled by treatMissingDiscretionaryValuesAsErrors
private @Config(defaultVal = "100") Integer connections; // discretionary value controlled by treatMissingDiscretionaryValuesAsErrors
}

public interface DBInfoInterface {
Optional<int> getPort(); // discretionary value controlled by treatMissingValuesAsErrors
default String getUri() { // discretionary value controlled by treatMissingValuesAsErrors
return "https://my.sql.db";
}
String getPassword(); // required value controlled by treatMissingDiscretionaryValuesAsErrors
@Config(defaultVal = "100")
Integer getConnections(); // discretionary value controlled by treatMissingDiscretionaryValuesAsErrors
}

public record DBInfoRecord(
int port, // required value controlled by treatMissingDiscretionaryValuesAsErrors
String uri, // required value controlled by treatMissingDiscretionaryValuesAsErrors
String password, // required value controlled by treatMissingDiscretionaryValuesAsErrors
@Config(defaultVal = "100") Integer connections // discretionary value controlled by treatMissingDiscretionaryValuesAsErrors
) {}
```

```kotlin
data class DBInfoDataDefault(
var port: Int?, // discretionary value controlled by treatMissingValuesAsErrors
var uri: String = "mysql:URI", // discretionary value controlled by treatMissingValuesAsErrors
var password: String, // required value controlled by treatMissingDiscretionaryValuesAsErrors
@Config(defaultVal = "100") var connections: Integer, // required value controlled by treatMissingDiscretionaryValuesAsErrors
)
```

### Retrieving Interfaces
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
plugins {
base

id("gestalt.dependancy-update-conventions")
id("gestalt.dependency-update-conventions")
id("gestalt.git-version-conventions")
idea
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -423,9 +423,6 @@ public OptionalMissingValueDecoding(String path, ConfigNode node, String decoder
public String description() {
StringBuilder description = new StringBuilder(62);
description.append("Missing Optional Value while decoding ").append(decoder).append(" on path: ").append(path);
if (node != null) {
description.append(", from node: ").append(node);
}
if (className != null) {
description.append(", with class: ").append(className);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,9 +202,8 @@ void decodeDefaultInheritedMissingValue() {

Assertions.assertEquals(1, result.getErrors().size());
Assertions.assertEquals(ValidationLevel.MISSING_OPTIONAL_VALUE, result.getErrors().get(0).level());
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.timeout, from node: " +
"MapNode{mapNode={password=LeafNode{value='pass'}, port=LeafNode{value='100'}, uri=LeafNode{value='mysql.com'}, " +
"user=LeafNode{value='Ted'}}}, with class: DBInfoExtendedDefault",
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.timeout, " +
"with class: DBInfoExtendedDefault",
result.getErrors().get(0).description());
}

Expand Down Expand Up @@ -263,8 +262,8 @@ void decodeDefaultValues() {

Assertions.assertEquals(1, result.getErrors().size());
Assertions.assertEquals(ValidationLevel.MISSING_OPTIONAL_VALUE, result.getErrors().get(0).level());
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.password, from node: " +
"MapNode{mapNode={port=LeafNode{value='100'}, uri=LeafNode{value='mysql.com'}}}, with class: DBInfoNoConstructor",
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.password, " +
"with class: DBInfoNoConstructor",
result.getErrors().get(0).description());

DBInfoNoConstructor results = (DBInfoNoConstructor) result.results();
Expand Down Expand Up @@ -310,9 +309,8 @@ void decodeDefaultGetterModifyValueNotNull() {

Assertions.assertEquals(1, result.getErrors().size());
Assertions.assertEquals(ValidationLevel.MISSING_OPTIONAL_VALUE, result.getErrors().get(0).level());
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.port, from node: " +
"MapNode{mapNode={password=LeafNode{value='pass'}, uri=LeafNode{value='mysql.com'}}}, " +
"with class: DBInfoIntegerPortNonNullGetter",
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.port, " +
"with class: DBInfoIntegerPortNonNullGetter",
result.getErrors().get(0).description());


Expand Down Expand Up @@ -340,8 +338,7 @@ void decodeDefaultGetterBooleanModifyValueNotNull() {

Assertions.assertEquals(1, result.getErrors().size());
Assertions.assertEquals(ValidationLevel.MISSING_OPTIONAL_VALUE, result.getErrors().get(0).level());
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.enabled, from node: " +
"MapNode{mapNode={password=LeafNode{value='pass'}, port=LeafNode{value='100'}, uri=LeafNode{value='mysql.com'}}}, " +
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.enabled, " +
"with class: DBInfoBooleanEnabledNonNullGetter",
result.getErrors().get(0).description());

Expand Down Expand Up @@ -404,9 +401,7 @@ void decodeDefaultBadNodeNotAnInt() {

Assertions.assertEquals(ValidationLevel.MISSING_OPTIONAL_VALUE, result.getErrors().get(1).level());
Assertions.assertEquals(
"Missing Optional Value while decoding Object on path: db.host.port, from node: " +
"MapNode{mapNode={password=LeafNode{value='pass'}, port=LeafNode{value='aaaa'}, uri=LeafNode{value='mysql.com'}}}, " +
"with class: DBInfoNoConstructor",
"Missing Optional Value while decoding Object on path: db.host.port, with class: DBInfoNoConstructor",
result.getErrors().get(1).description());

DBInfoNoConstructor results = (DBInfoNoConstructor) result.results();
Expand Down Expand Up @@ -436,7 +431,7 @@ void decodeBadNodeNotAnIntNullResult() {

Assertions.assertEquals(ValidationLevel.MISSING_VALUE, result.getErrors().get(1).level());
Assertions.assertEquals("Unable to find node matching path: db.host.port, for class: DBInfoIntegerPort, " +
"during object decoding", result.getErrors().get(1).description());
"during object decoding", result.getErrors().get(1).description());

DBInfoIntegerPort results = (DBInfoIntegerPort) result.results();
Assertions.assertNull(results.getPort());
Expand Down Expand Up @@ -501,18 +496,16 @@ void decodeDefaultOptionalMissingValues() {

Assertions.assertEquals(3, result.getErrors().size());
Assertions.assertEquals(ValidationLevel.MISSING_OPTIONAL_VALUE, result.getErrors().get(0).level());
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.port, from node: " +
"MapNode{mapNode={}}, with class: DBInfoOptional",
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.port, with class: DBInfoOptional",
result.getErrors().get(0).description());

Assertions.assertEquals(ValidationLevel.MISSING_OPTIONAL_VALUE, result.getErrors().get(1).level());
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.uri, from node: " +
"MapNode{mapNode={}}, with class: DBInfoOptional",
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.uri, with class: DBInfoOptional",
result.getErrors().get(1).description());

Assertions.assertEquals(ValidationLevel.MISSING_OPTIONAL_VALUE, result.getErrors().get(2).level());
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.password, from node: " +
"MapNode{mapNode={}}, with class: DBInfoOptional", result.getErrors().get(2).description());
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.password, " +
"with class: DBInfoOptional", result.getErrors().get(2).description());
}

@Test
Expand All @@ -534,13 +527,12 @@ void decodeDefaultOptionalPartialMissingValues() {

Assertions.assertEquals(2, result.getErrors().size());
Assertions.assertEquals(ValidationLevel.MISSING_OPTIONAL_VALUE, result.getErrors().get(0).level());
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.uri, from node: " +
"MapNode{mapNode={port=LeafNode{value='100'}}}, with class: DBInfoOptional",
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.uri, with class: DBInfoOptional",
result.getErrors().get(0).description());

Assertions.assertEquals(ValidationLevel.MISSING_OPTIONAL_VALUE, result.getErrors().get(1).level());
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.password, from node: " +
"MapNode{mapNode={port=LeafNode{value='100'}}}, with class: DBInfoOptional",
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.password, " +
"with class: DBInfoOptional",
result.getErrors().get(1).description());
}

Expand Down Expand Up @@ -613,17 +605,17 @@ void decodeDefaultNullMapNode() {
.allMatch(it -> it.level().equals(ValidationLevel.MISSING_OPTIONAL_VALUE))).isTrue();

assertThat(result.getErrors()).anyMatch(it ->
("Missing Optional Value while decoding Object on path: db.host.port, from node: MapNode{mapNode={}}, " +
("Missing Optional Value while decoding Object on path: db.host.port, " +
"with class: DBInfoNoConstructor")
.equals(it.description()));

assertThat(result.getErrors()).anyMatch(it ->
("Missing Optional Value while decoding Object on path: db.host.uri, from node: MapNode{mapNode={}}, " +
("Missing Optional Value while decoding Object on path: db.host.uri, " +
"with class: DBInfoNoConstructor")
.equals(it.description()));

assertThat(result.getErrors()).anyMatch(it ->
("Missing Optional Value while decoding Object on path: db.host.password, from node: MapNode{mapNode={}}, " +
("Missing Optional Value while decoding Object on path: db.host.password, " +
"with class: DBInfoNoConstructor")
.equals(it.description()));

Expand Down Expand Up @@ -700,9 +692,7 @@ void decodeHttpPool() {

Assertions.assertEquals(1, result.getErrors().size());
Assertions.assertEquals(ValidationLevel.MISSING_OPTIONAL_VALUE, result.getErrors().get(0).level());
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.defaultWait, from node: " +
"MapNode{mapNode={maxperroute=LeafNode{value='10'}, keepalivetimeoutms=LeafNode{value='123'}, " +
"idletimeoutsec=LeafNode{value='1000'}, validateafterinactivity=LeafNode{value='60'}, maxtotal=LeafNode{value='100'}}}, " +
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.defaultWait, " +
"with class: DBPool",
result.getErrors().get(0).description());

Expand Down Expand Up @@ -770,8 +760,7 @@ void decodeWithAnnotationDefault() {

Assertions.assertEquals(1, result.getErrors().size());
Assertions.assertEquals(ValidationLevel.MISSING_OPTIONAL_VALUE, result.getErrors().get(0).level());
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.channel, from node: " +
"MapNode{mapNode={password=LeafNode{value='pass'}, uri=LeafNode{value='mysql.com'}}}",
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.channel",
result.getErrors().get(0).description());

DBInfoAnnotations results = (DBInfoAnnotations) result.results();
Expand All @@ -796,8 +785,7 @@ void decodeWithAnnotationOnlyDefault() {

Assertions.assertEquals(1, result.getErrors().size());
Assertions.assertEquals(ValidationLevel.MISSING_OPTIONAL_VALUE, result.getErrors().get(0).level());
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.port, from node: " +
"MapNode{mapNode={password=LeafNode{value='pass'}, uri=LeafNode{value='mysql.com'}}}",
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.port",
result.getErrors().get(0).description());

DBInfoAnnotationsDefault results = (DBInfoAnnotationsDefault) result.results();
Expand Down Expand Up @@ -854,9 +842,8 @@ void decodeWithAnnotationWrongDefaultUseClassDefault() {
"attempting to decode Integer", result.getErrors().get(0).description());

Assertions.assertEquals(ValidationLevel.MISSING_OPTIONAL_VALUE, result.getErrors().get(1).level());
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.channel, from node: " +
"MapNode{mapNode={password=LeafNode{value='pass'}, uri=LeafNode{value='mysql.com'}}}, " +
"with class: DBInfoBadAnnotationsWithClassDefault",
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.channel, " +
"with class: DBInfoBadAnnotationsWithClassDefault",
result.getErrors().get(1).description());

DBInfoBadAnnotationsWithClassDefault results = (DBInfoBadAnnotationsWithClassDefault) result.results();
Expand Down Expand Up @@ -920,8 +907,7 @@ void decodeDefaultWithMethodAnnotation() {

Assertions.assertEquals(1, result.getErrors().size());
Assertions.assertEquals(ValidationLevel.MISSING_OPTIONAL_VALUE, result.getErrors().get(0).level());
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.channel, from node: " +
"MapNode{mapNode={password=LeafNode{value='pass'}, uri=LeafNode{value='mysql.com'}}}",
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.channel",
result.getErrors().get(0).description());

DBInfoMethodAnnotations results = (DBInfoMethodAnnotations) result.results();
Expand All @@ -945,8 +931,7 @@ void decodeWithMethodAnnotationOnlyDefault() {

Assertions.assertEquals(1, result.getErrors().size());
Assertions.assertEquals(ValidationLevel.MISSING_OPTIONAL_VALUE, result.getErrors().get(0).level());
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.port, from node: " +
"MapNode{mapNode={password=LeafNode{value='pass'}, uri=LeafNode{value='mysql.com'}}}",
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.port",
result.getErrors().get(0).description());

DBInfoMethodAnnotationsDefault results = (DBInfoMethodAnnotationsDefault) result.results();
Expand Down Expand Up @@ -1020,8 +1005,7 @@ void decodeDefaultWithAnnotationPriorityConfig() {

Assertions.assertEquals(1, result.getErrors().size());
Assertions.assertEquals(ValidationLevel.MISSING_OPTIONAL_VALUE, result.getErrors().get(0).level());
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.channel, from node: " +
"MapNode{mapNode={password=LeafNode{value='pass'}, uri=LeafNode{value='mysql.com'}}}",
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.host.channel",
result.getErrors().get(0).description());

DBInfoBothAnnotations results = (DBInfoBothAnnotations) result.results();
Expand Down Expand Up @@ -1087,7 +1071,7 @@ void decodeWithDefaultPrimitive() {
Assertions.assertEquals(5.5f, results.myFloat);
Assertions.assertEquals(6.6D, results.myDouble);
Assertions.assertEquals('a', results.myChar);
Assertions.assertEquals(true, results.myBoolean);
Assertions.assertTrue(results.myBoolean);
}

@Test
Expand Down Expand Up @@ -1117,7 +1101,7 @@ void decodeWithOutDefaultPrimitive() {
Assertions.assertEquals(0f, results.myFloat);
Assertions.assertEquals(0D, results.myDouble);
Assertions.assertEquals(Character.MIN_VALUE, results.myChar);
Assertions.assertEquals(false, results.myBoolean);
Assertions.assertFalse(results.myBoolean);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ void decodeLeafIntegerEmpty() {
Assertions.assertFalse(result.results().isPresent());
Assertions.assertEquals(1, result.getErrors().size());
Assertions.assertEquals(ValidationLevel.MISSING_OPTIONAL_VALUE, result.getErrors().get(0).level());
Assertions.assertEquals("Missing Optional Value while decoding Optional on path: db.port, from node: LeafNode{value='null'}",
Assertions.assertEquals("Missing Optional Value while decoding Optional on path: db.port",
result.getErrors().get(0).description());
}

Expand Down Expand Up @@ -129,8 +129,7 @@ void decodeObjectOptionalIntegerEmpty() {
Assertions.assertTrue(result.results().isPresent());
Assertions.assertEquals(1, result.getErrors().size());
Assertions.assertEquals(ValidationLevel.MISSING_OPTIONAL_VALUE, result.getErrors().get(0).level());
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.port, from node: " +
"MapNode{mapNode={password=LeafNode{value='pass'}, uri=LeafNode{value='mysql.com'}}}, with class: DBInfoOptional",
Assertions.assertEquals("Missing Optional Value while decoding Object on path: db.port, with class: DBInfoOptional",
result.getErrors().get(0).description());

Optional<DBInfoOptional> results = (Optional<DBInfoOptional>) result.results();
Expand Down
Loading

0 comments on commit c09c02b

Please sign in to comment.