Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add as config properties other object types. #451

Closed
wants to merge 1 commit into from

Conversation

radcortez
Copy link
Member

@radcortez radcortez commented Nov 27, 2020

Fixes #447

@radcortez radcortez linked an issue Nov 27, 2020 that may be closed by this pull request
@radcortez
Copy link
Member Author

@vsevel do you want to give this a try?

@vsevel
Copy link

vsevel commented Dec 1, 2020

sure. I will give you a feedback today or tomorrow.

@vsevel
Copy link

vsevel commented Dec 2, 2020

hello, I gave it a try:

@ConfigProperties(prefix = "greeting")
public class GreetingConfig {

    public String foo;
    public Map<String, FooConfig> fooMap;
    public List<FooConfig> fooList;
}

public class FooConfig {
    public String bar;
}


greeting:
  foo: bar
  foo-list:
    - bar: x
    - bar: y
  foo-map:
    first:
      bar: a
    other:
      bar: b

@QuarkusTest
public class ConfigTest {

    @Inject
    GreetingConfig greetingConfig;

    @Test
    public void test() {
        Assertions.assertEquals("bar", greetingConfig.foo);
        Assertions.assertEquals(2, greetingConfig.fooMap.size());
        Assertions.assertEquals(2, greetingConfig.fooList.size());
    }
}

I rebuilt smallrye-config from your branch, and forced the dependency to be 1.9.4-SNAPSHOT and my getting-started example that was generated with quarkus 1.9.0.Final.

At startup I get the following error:

java.lang.RuntimeException: java.lang.RuntimeException: Failed to start quarkus

	at io.quarkus.test.junit.QuarkusTestExtension.throwBootFailureException(QuarkusTestExtension.java:549)
	at io.quarkus.test.junit.QuarkusTestExtension.interceptTestClassConstructor(QuarkusTestExtension.java:616)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$570/1225970981.apply(Unknown Source)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
	at org.junit.jupiter.engine.execution.ExecutableInvoker$$Lambda$571/726416425.apply(Unknown Source)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at org.junit.jupiter.api.extension.InvocationInterceptor.interceptTestClassConstructor(InvocationInterceptor.java:72)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$570/1225970981.apply(Unknown Source)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
	at org.junit.jupiter.engine.execution.ExecutableInvoker$$Lambda$571/726416425.apply(Unknown Source)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:77)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeTestClassConstructor(ClassBasedTestDescriptor.java:342)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateTestClass(ClassBasedTestDescriptor.java:289)
	at org.junit.jupiter.engine.descriptor.ClassTestDescriptor.instantiateTestClass(ClassTestDescriptor.java:79)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateAndPostProcessTestInstance(ClassBasedTestDescriptor.java:267)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$2(ClassBasedTestDescriptor.java:259)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$566/2058174333.get(Unknown Source)
	at java.util.Optional.orElseGet(Optional.java:267)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$3(ClassBasedTestDescriptor.java:258)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$239/2011482127.getTestInstances(Unknown Source)
	at org.junit.jupiter.engine.execution.TestInstancesProvider.getTestInstances(TestInstancesProvider.java:31)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$prepare$0(TestMethodTestDescriptor.java:101)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$565/356476647.execute(Unknown Source)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:100)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:65)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$prepare$1(NodeTestTask.java:111)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$192/1263793464.execute(Unknown Source)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.prepare(NodeTestTask.java:111)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:79)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService$$Lambda$212/520022247.accept(Unknown Source)
	at java.util.ArrayList.forEach(ArrayList.java:1249)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$208/2007331442.execute(Unknown Source)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$207/867398280.invoke(Unknown Source)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$206/270397815.execute(Unknown Source)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService$$Lambda$212/520022247.accept(Unknown Source)
	at java.util.ArrayList.forEach(ArrayList.java:1249)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$208/2007331442.execute(Unknown Source)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$207/867398280.invoke(Unknown Source)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda$206/270397815.execute(Unknown Source)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator$$Lambda$160/1320677379.accept(Unknown Source)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:75)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:71)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
Caused by: java.lang.RuntimeException: Failed to start quarkus
	at io.quarkus.runner.ApplicationImpl.doStart(ApplicationImpl.zig:642)
	at io.quarkus.runtime.Application.start(Application.java:90)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:497)
	at io.quarkus.runner.bootstrap.StartupActionImpl.run(StartupActionImpl.java:212)
	at io.quarkus.test.junit.QuarkusTestExtension.doJavaStart(QuarkusTestExtension.java:233)
	at io.quarkus.test.junit.QuarkusTestExtension.ensureStarted(QuarkusTestExtension.java:527)
	at io.quarkus.test.junit.QuarkusTestExtension.beforeAll(QuarkusTestExtension.java:560)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeBeforeAllCallbacks$8(ClassBasedTestDescriptor.java:368)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda$246/726281927.execute(Unknown Source)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeBeforeAllCallbacks(ClassBasedTestDescriptor.java:368)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.before(ClassBasedTestDescriptor.java:192)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.before(ClassBasedTestDescriptor.java:78)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:136)
	... 39 more
Caused by: javax.enterprise.inject.spi.DeploymentException: No config value of type [java.util.Map] exists for: greeting.foo-map
	at io.quarkus.arc.runtime.ConfigRecorder.validateConfigProperties(ConfigRecorder.java:39)
	at io.quarkus.deployment.steps.ConfigBuildStep$validateConfigProperties1249763973.deploy_0(ConfigBuildStep$validateConfigProperties1249763973.zig:192)
	at io.quarkus.deployment.steps.ConfigBuildStep$validateConfigProperties1249763973.deploy(ConfigBuildStep$validateConfigProperties1249763973.zig:40)
	at io.quarkus.runner.ApplicationImpl.doStart(ApplicationImpl.zig:521)
	... 55 more

anything I did wrong?

@radcortez
Copy link
Member Author

Quarkus @ConfigProperties doesn't work with Map: https://quarkusio.zulipchat.com/#narrow/stream/187030-users/topic/ConfigProperties.20dumb.20question/near/214668764

I've used the same code in a project of mine and got: Nested configuration class 'java.util.Map' must contain a no-args constructor. In your example, it seems that is not able to retrieve the config for some reason.

Anyway, to use a Map, you need to use SR Config @ConfigMapping. We are intending to replace Quarkus @ConfigProperties with @ConfigMapping, which supports way more stuff: https://smallrye.io/docs/smallrye-config/mapping/mapping.html. The only missing feature that I can remember is the BVal annotations, which I need to add to @ConfigMapping.

Now, there is another issue which is related with how MP Config handles lists. Each element separated by a comma is a different element. This doesn't work well when you place a complex structure in the config value. And that is why I use a wrapper object to map the List here:

public static class Users {
List<User> users;
public List<User> getUsers() {
return users;
}
public void setUsers(final List<User> users) {
this.users = users;
}
}
public static class User {
String email;
String username;
String password;
List<String> roles;
public String getEmail() {
return email;
}
public void setEmail(final String email) {
this.email = email;
}
public String getUsername() {
return username;
}
public void setUsername(final String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(final String password) {
this.password = password;
}
public List<String> getRoles() {
return roles;
}
public void setRoles(final List<String> roles) {
this.roles = roles;
}
}
static class UserConverter implements Converter<Users> {
@Override
public Users convert(final String value) {
return new Yaml().loadAs(value, Users.class);
}
}
@ConfigMapping(prefix = "admin")
interface UsersMapping {
Users users();
}

The MP Config spec was not designed to work this way, so all of these are workarounds to be able to properly use and convert values. I'll try to figure out a way to make this more friendly with YAML sources.

@vsevel
Copy link

vsevel commented Dec 4, 2020

hello. I am a bit confused with the returned values. for list:

{"users": [{"name": "bob"}, {"name": "joe", "age": !!int "45"}]}

I would have expected to get:

[{"name": "bob"}, {"name": "joe", "age": 45}]

that is no wrapping element, and no leaking artifact !!!int from snakeyaml.

same comment for the map example:

{"users": {"bob": !!null "null", "joe": {"age": !!int "45"}}}

I think this should be:

{"bob": {}, "joe": {"age": 45}}

and I would correct the unit test with:

"  bob: {}\n" +

I am confused on this PR really fixing #447.
if this is merged and bumped into Quarkus, what additional capability will it offer?
If I used a ConfigMapping, would I be able to read a map of structs, or a list of structs? how?

@radcortez
Copy link
Member Author

Yes, maybe we need to remove the wrapping element. I guess it depends on how we developer would like to convert.

The PR won't fix nothing directly. Is just that due to the nature on how configs are stored, there is no way for us to relate the properties. If you think in the case of a List, where each element is store in a different configuration key, it requires multiple configuration lookups to be able to construct the map, and we cannot be really sure that the properties are really related. Main idea was to give a way to retrieve the complete structure of the element and then provide a Converter to do the conversion.

Maybe we can try to think in something more friendly. In your mind what would you expect to configure, code and get back?

@vsevel
Copy link

vsevel commented Dec 9, 2020

Maybe we can try to think in something more friendly. In your mind what would you expect to configure, code and get back?

this is probably naive, but as a user, I would expect to be able to map a yaml config on a complex datastructure, very much like the kind of yaml databinding support offered by jackson, as in this example, at least everything but the davanced use cases (e.g. polymorphism).

missing some of the details, it sounds the yaml config is limited by the properties format. ideally this would be 2 different modules, each allowing datastructures as sophisticated as the format itself supports. so I would expect to handle more complex use cases with the yaml format, than the properties format.

@radcortez
Copy link
Member Author

Replaced by #489.

@radcortez radcortez closed this Jan 25, 2021
@radcortez radcortez deleted the fix-447 branch April 27, 2021 17:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support for Map<String, T> and List<T>
2 participants