Skip to content

Commit

Permalink
Link source function to type conversion errors (improves error messag…
Browse files Browse the repository at this point in the history
…es for invalid enum entry name)
  • Loading branch information
iamgio committed Sep 13, 2024
1 parent 37fa7c0 commit 68c49e7
Show file tree
Hide file tree
Showing 5 changed files with 30 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import eu.iamgio.quarkdown.function.FunctionParameter
import eu.iamgio.quarkdown.function.call.FunctionCall
import eu.iamgio.quarkdown.function.call.FunctionCallArgument
import eu.iamgio.quarkdown.function.error.InvalidArgumentCountException
import eu.iamgio.quarkdown.function.error.InvalidFunctionCallException
import eu.iamgio.quarkdown.function.error.MismatchingArgumentTypeException
import eu.iamgio.quarkdown.function.error.UnnamedArgumentAfterNamedException
import eu.iamgio.quarkdown.function.error.UnresolvedParameterException
import eu.iamgio.quarkdown.function.reflect.DynamicValueConverter
import eu.iamgio.quarkdown.function.value.DynamicValue
import eu.iamgio.quarkdown.function.value.StringValue
import eu.iamgio.quarkdown.function.value.ValueFactory
import eu.iamgio.quarkdown.pipeline.error.PipelineException
import kotlin.reflect.full.isSubclassOf

/**
Expand Down Expand Up @@ -80,7 +82,14 @@ class RegularArgumentsBinder(private val call: FunctionCall<*>) : ArgumentsBinde
// The dynamic value is converted into the expected parameter type.
// Throws error if the conversion could not happen.
val staticValue =
DynamicValueConverter(value).convertTo(parameter.type, call.context)
try {
DynamicValueConverter(value).convertTo(parameter.type, call.context)
} catch (e: PipelineException) {
// In case the conversion fails, the error is wrapped so that it can refer to this function call as a source.
throw InvalidFunctionCallException(call, e.message, includeArguments = false)
}
// convertTo returns null if the called ValueFactory method returns null.
// This means the supplied value cannot be converted to the expected type.
?: throw MismatchingArgumentTypeException(call, parameter, argument)

argument.copy(expression = staticValue)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,17 @@ import eu.iamgio.quarkdown.function.call.asString
* An exception thrown if a [FunctionCall] could not be executed.
* @param call the invalid call
* @param reason optional additional reason the call failed for
* @param includeArguments whether to include supplied function call arguments in the error message
*/
open class InvalidFunctionCallException(val call: FunctionCall<*>, reason: String? = null) :
open class InvalidFunctionCallException(
val call: FunctionCall<*>,
reason: String? = null,
includeArguments: Boolean = true,
) :
FunctionException(
"Cannot call function ${call.function.asString()} with arguments ${call.arguments.asString()}" +
(reason?.let { ": $it" } ?: ""),
code = BAD_FUNCTION_CALL_EXIT_CODE,
function = call.function,
)
"Cannot call function ${call.function.asString()}" +
(" with arguments ${call.arguments.asString()}".takeIf { includeArguments } ?: "") +
(reason?.let { ": $it" } ?: ""),
code = BAD_FUNCTION_CALL_EXIT_CODE,
function = call.function,
)
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import eu.iamgio.quarkdown.pipeline.error.PipelineException
* Exception thrown when an element (e.g. an enum value from a Quarkdown function argument)
* does not exist among elements of a look-up table.
*/
class NoSuchElementFunctionException(element: Any, values: Iterable<*>) :
class NoSuchElementException(element: Any, values: Iterable<*>) :
PipelineException("No such element '$element' among values $values", NO_SUCH_ELEMENT_EXIT_CODE) {
constructor(element: Any, values: Array<Enum<*>>) : this(element, values.map { it.quarkdownName })
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package eu.iamgio.quarkdown.function.reflect

import eu.iamgio.quarkdown.context.Context
import eu.iamgio.quarkdown.function.call.FunctionCall
import eu.iamgio.quarkdown.function.error.NoSuchElementFunctionException
import eu.iamgio.quarkdown.function.error.NoSuchElementException
import eu.iamgio.quarkdown.function.value.DynamicValue
import eu.iamgio.quarkdown.function.value.InputValue
import eu.iamgio.quarkdown.function.value.Value
Expand All @@ -25,6 +25,8 @@ class DynamicValueConverter(private val value: DynamicValue) {
* This type is unwrapped (e.g. if [type] is `String`, the output is of type `StringValue`)
* @param context context to evaluate the value for
* @return a new typed [InputValue], automatically determined from [type], or `null` if it could not be converted
* @throws IllegalArgumentException if the value could not be converted to the target type or if [context] is required and it's `null`
* @throws NoSuchElementException if the value could not be converted to an enum entry
*/
@Suppress("UNCHECKED_CAST")
fun convertTo(
Expand All @@ -46,7 +48,7 @@ class DynamicValueConverter(private val value: DynamicValue) {
val values = valuesFunction.call()

return ValueFactory.enum(raw.toString(), values)
?: throw NoSuchElementFunctionException(element = raw, values)
?: throw NoSuchElementException(element = raw, values)
}

// Gets ValueFactory methods annotated with @FromDynamicType(X::class),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import eu.iamgio.quarkdown.function.call.FunctionCall
import eu.iamgio.quarkdown.function.call.FunctionCallArgument
import eu.iamgio.quarkdown.function.call.binding.ArgumentBindings
import eu.iamgio.quarkdown.function.error.InvalidArgumentCountException
import eu.iamgio.quarkdown.function.error.InvalidFunctionCallException
import eu.iamgio.quarkdown.function.error.MismatchingArgumentTypeException
import eu.iamgio.quarkdown.function.error.NoSuchElementFunctionException
import eu.iamgio.quarkdown.function.error.NoSuchElementException
import eu.iamgio.quarkdown.function.error.UnnamedArgumentAfterNamedException
import eu.iamgio.quarkdown.function.error.UnresolvedParameterException
import eu.iamgio.quarkdown.function.expression.ComposedExpression
Expand Down Expand Up @@ -580,7 +581,7 @@ class StandaloneFunctionTest {
),
)

assertFailsWith<NoSuchElementFunctionException> {
assertFailsWith<InvalidFunctionCallException> {
call.execute()
}
}
Expand Down

0 comments on commit 68c49e7

Please sign in to comment.