@@ -53,65 +53,70 @@ class DataClassDecoder : NullHandlingDecoder<Any> {
53
53
return ConfigFailure .DataClassWithoutConstructor (klass).invalid()
54
54
}
55
55
56
- val constructor = klass.constructors.first()
57
-
58
- // we have a special case, which is a data class with a single field with the name 'value'.
59
- // we call this a "value type" and we can instantiate a value directly into this data class
60
- // without needing nested config, if the node is a primitive type
61
-
62
- // try for the value type
63
- if (constructor .parameters.size == 1 && constructor .parameters[0 ].name == " value" && node is PrimitiveNode ) {
64
- return context.decoder(constructor .parameters[0 ])
65
- .flatMap { it.decode(node, constructor .parameters[0 ].type, context) }
66
- .map { constructor .parameters[0 ] to it }
67
- .mapInvalid { ConfigFailure .ValueTypeFailure (klass, constructor .parameters[0 ], it) }
68
- .flatMap { construct(type, constructor , mapOf (it)) }
69
- }
70
-
71
- data class Arg (val parameter : KParameter ,
56
+ data class Arg (val constructor : KFunction <Any >,
57
+ val parameter : KParameter ,
72
58
val configName : String , // the config value name that was used
73
59
val value : Any? )
74
60
75
- // create a map of parameter to value. in the case of defaults, we skip the parameter completely.
76
- val args: ValidatedNel <ConfigFailure , List <Arg >> = constructor .parameters.mapNotNull { param ->
77
-
78
- var name = " <<undefined>>"
79
-
80
- // try each parameter mapper in turn to find the node
81
- val n = context.paramMappers.fold<ParameterMapper , Node >(Undefined ) { n, mapper ->
82
- if (n.isDefined) n else {
83
- name = mapper.map(param)
84
- node.atKey(name)
85
- }
61
+ val argsList = klass.constructors.map { constructor ->
62
+
63
+ // try for the value type
64
+ // we have a special case, which is a data class with a single field with the name 'value'.
65
+ // we call this a "value type" and we can instantiate a value directly into this data class
66
+ // without needing nested config, if the node is a primitive type
67
+ if (constructor .parameters.size == 1 && constructor .parameters[0 ].name == " value" && node is PrimitiveNode ) {
68
+ return context.decoder(constructor .parameters[0 ])
69
+ .flatMap { it.decode(node, constructor .parameters[0 ].type, context) }
70
+ .map { constructor .parameters[0 ] to it }
71
+ .mapInvalid { ConfigFailure .ValueTypeFailure (klass, constructor .parameters[0 ], it) }
72
+ .flatMap { construct(type, constructor , mapOf (it)) }
86
73
}
87
74
88
- when {
89
- // if we have no value for this parameter at all, and it is optional we can skip it, and
90
- // kotlin will use the default
91
- param.isOptional && n is Undefined -> null
92
- else -> context.decoder(param)
93
- .flatMap { it.decode(n, param.type, context) }
94
- .map { Arg (param, name, it) }
95
- .mapInvalid { ConfigFailure .ParamFailure (param, it) }
96
- }
97
- }.sequence()
75
+ // create a map of parameter to value. in the case of defaults, we skip the parameter completely.
76
+ val args: ValidatedNel <ConfigFailure , List <Arg >> = constructor .parameters.mapNotNull { param ->
98
77
99
- return when (args) {
100
- // in invalid we wrap in an error containing each individual error
101
- is Validated .Invalid -> ConfigFailure .DataClassFieldErrors (args.error, type, node.pos).invalid()
102
- is Validated .Valid -> {
78
+ var name = " <<undefined>>"
103
79
104
- // in strict mode we throw an error if not all config values were used for the class
105
- if (node is MapNode ) {
106
- if (context.mode == DecodeMode . Strict && args.value.size != node.size) {
107
- val unusedValues = node .map.keys.minus(args.value.map { it.configName } )
108
- return ConfigFailure . UnusedConfigValues (unusedValues.toList()).invalid( )
80
+ // try each parameter mapper in turn to find the node
81
+ val n = context.paramMappers.fold< ParameterMapper , Node >( Undefined ) { n, mapper ->
82
+ if (n.isDefined) n else {
83
+ name = mapper .map(param )
84
+ node.atKey(name )
109
85
}
110
86
}
111
87
112
- construct(type, constructor , args.value.map { it.parameter to it.value }.toMap())
113
- }
88
+ when {
89
+ // if we have no value for this parameter at all, and it is optional we can skip it, and
90
+ // kotlin will use the default
91
+ param.isOptional && n is Undefined -> null
92
+ else -> context.decoder(param)
93
+ .flatMap { it.decode(n, param.type, context) }
94
+ .map { Arg (constructor , param, name, it) }
95
+ .mapInvalid { ConfigFailure .ParamFailure (param, it) }
96
+ }
97
+ }.sequence()
98
+ args
114
99
}
100
+ val firstValidOrLastInvalidArgs = argsList.firstOrNull{ it is Validated .Valid } ? :
101
+ argsList.last { it is Validated .Invalid }
102
+ return when (firstValidOrLastInvalidArgs) {
103
+ // in invalid we wrap in an error containing each individual error
104
+ is Validated .Invalid -> ConfigFailure .DataClassFieldErrors (
105
+ firstValidOrLastInvalidArgs.error, type, node.pos).invalid()
106
+ is Validated .Valid -> {
107
+
108
+ // in strict mode we throw an error if not all config values were used for the class
109
+ if (node is MapNode ) {
110
+ if (context.mode == DecodeMode .Strict && firstValidOrLastInvalidArgs.value.size != node.size) {
111
+ val unusedValues = node.map.keys.minus(firstValidOrLastInvalidArgs.value.map { it.configName })
112
+ return ConfigFailure .UnusedConfigValues (unusedValues.toList()).invalid()
113
+ }
114
+ }
115
+
116
+ return construct(type, firstValidOrLastInvalidArgs.value.first().constructor ,
117
+ firstValidOrLastInvalidArgs.value.map { it.parameter to it.value }.toMap())
118
+ }
119
+ }
115
120
}
116
121
117
122
private fun <A > construct (
0 commit comments