diff --git a/api/src/main/java/com/fnproject/fn/api/FunctionInvoker.java b/api/src/main/java/com/fnproject/fn/api/FunctionInvoker.java
index 7f1fa29a..6dd0a8ef 100644
--- a/api/src/main/java/com/fnproject/fn/api/FunctionInvoker.java
+++ b/api/src/main/java/com/fnproject/fn/api/FunctionInvoker.java
@@ -12,8 +12,11 @@ public interface FunctionInvoker {
* If the invoker returns an empty option then no action has been taken and another invoker may attempt to tryInvoke
*
* In particular this means that implementations must not read the input event body until they are committed to handling this event.
- *
- * A RuntimeException thrown by the implementation will cause the entire function invocation to fail.
+ *
+ * An {@link InputCoercion.InvalidFunctionInputException} thrown by the implementation will indicate that the input
+ * provided to the function by the user is invalid.
+ *
+ * Any other RuntimeException thrown by the implementation will cause the entire function invocation to fail.
*
* @param ctx the context for a single invocation
* @param evt the incoming event
diff --git a/api/src/main/java/com/fnproject/fn/api/InputCoercion.java b/api/src/main/java/com/fnproject/fn/api/InputCoercion.java
index 1483092b..f546bff9 100644
--- a/api/src/main/java/com/fnproject/fn/api/InputCoercion.java
+++ b/api/src/main/java/com/fnproject/fn/api/InputCoercion.java
@@ -6,20 +6,39 @@
* Handles the coercion of an input event to a parameter
*/
public interface InputCoercion {
+ /**
+ * An implementor of InputCoercion can throw this exception to inform the platform that the user input in the http
+ * request was invalid.
+ */
+ class InvalidFunctionInputException extends RuntimeException {
+ public InvalidFunctionInputException(String s, Throwable t) {
+ super(s, t);
+ }
+
+ public InvalidFunctionInputException(String s) {
+ super(s);
+ }
+ }
+
/**
* Handle coercion for a function parameter
*
- * Coercions may choose to act on a parameter, in which case they should return a fulfilled option) or may
+ * Coercions may choose to act on a parameter, in which case they must return a fulfilled option) or may
* ignore parameters (allowing other coercions to act on the parameter)
*
- * When a coercion ignores a parameter it must not consume the input stream of the event.
+ * When a coercion ignores a parameter it must not consume the input stream of the event and it must return an empty
+ * option.
+ *
+ * If a coercion throws an {@link InvalidFunctionInputException}, the message will be communicated to the user in a
+ * "bad request" error and no further coercions will be tried.
*
- * If a coercion throws a RuntimeException, no further coercions will be tried and the function invocation will fail.
+ * If a coercion throws any other RuntimeException, no further coercions will be tried and the entire function will
+ * fail.
*
* @param currentContext the invocation context for this event - this links to the {@link com.fnproject.fn.api.RuntimeContext} and method and class
* @param arg the parameter index for the argument being extracted
* @param input the input event
- * @return the result of the coercion, if it succeeded
+ * @return the result of the coercion, if it succeeded
*/
- Optional tryCoerceParam(InvocationContext currentContext, int arg, InputEvent input);
+ Optional tryCoerceParam(InvocationContext currentContext, int arg, InputEvent input) throws InvalidFunctionInputException;
}
diff --git a/runtime/src/main/java/com/fnproject/fn/runtime/EntryPoint.java b/runtime/src/main/java/com/fnproject/fn/runtime/EntryPoint.java
index 6493dbca..b6561103 100644
--- a/runtime/src/main/java/com/fnproject/fn/runtime/EntryPoint.java
+++ b/runtime/src/main/java/com/fnproject/fn/runtime/EntryPoint.java
@@ -2,6 +2,7 @@
import com.fnproject.fn.api.FunctionInvoker;
+import com.fnproject.fn.api.InputCoercion;
import com.fnproject.fn.api.InputEvent;
import com.fnproject.fn.api.OutputEvent;
import com.fnproject.fn.runtime.flow.FlowContinuationInvoker;
@@ -71,7 +72,7 @@ public int run(Map env, InputStream functionInput, OutputStream
} else if (format == null || format.equalsIgnoreCase("default")) {
codec = new DefaultEventCodec(env, functionInput, functionOutput);
} else {
- throw new FunctionInputHandlingException("Unsupported function format:" + format);
+ throw new FunctionFormatException("Unsupported function format: " + format);
}
do {
@@ -101,6 +102,10 @@ public int run(Map env, InputStream functionInput, OutputStream
lastStatus = 1;
fic.fireOnFailedInvocation();
}
+ } catch (InputCoercion.InvalidFunctionInputException e) {
+ fic.fireOnFailedInvocation();
+ lastStatus = 1;
+ codec.writeEvent(OutputEvent.fromBytes(e.getMessage().getBytes(), 400, "text/plain"));
} catch (IOException e) {
fic.fireOnFailedInvocation();
throw new FunctionInputHandlingException("Error closing function input", e);
diff --git a/runtime/src/main/java/com/fnproject/fn/runtime/MethodFunctionInvoker.java b/runtime/src/main/java/com/fnproject/fn/runtime/MethodFunctionInvoker.java
index b0da96cb..12667a5f 100644
--- a/runtime/src/main/java/com/fnproject/fn/runtime/MethodFunctionInvoker.java
+++ b/runtime/src/main/java/com/fnproject/fn/runtime/MethodFunctionInvoker.java
@@ -1,13 +1,10 @@
package com.fnproject.fn.runtime;
-import com.fnproject.fn.api.FunctionInvoker;
-import com.fnproject.fn.api.InputEvent;
-import com.fnproject.fn.api.InvocationContext;
-import com.fnproject.fn.api.OutputEvent;
+import com.fnproject.fn.api.*;
import com.fnproject.fn.runtime.exception.FunctionInputHandlingException;
-import com.fnproject.fn.runtime.exception.InternalFunctionInvocationException;
import com.fnproject.fn.runtime.exception.FunctionOutputHandlingException;
+import com.fnproject.fn.runtime.exception.InternalFunctionInvocationException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -29,7 +26,7 @@ public class MethodFunctionInvoker implements FunctionInvoker {
* @throws InternalFunctionInvocationException if the invocation fails
*/
@Override
- public Optional tryInvoke(InvocationContext ctx, InputEvent evt) throws InternalFunctionInvocationException {
+ public Optional tryInvoke(InvocationContext ctx, InputEvent evt) {
Object[] userFunctionParams = doInputCoercions(ctx, evt);
@@ -59,18 +56,25 @@ private Object[] doInputCoercions(InvocationContext ctx, InputEvent evt) {
for (int i = 0; i < userFunctionParams.length; i++) {
int param = i;
- Optional