Repository: serverless/serverless (JavaScript)
Date: 2016-11-14 02:35:11+00:00
Task type: Bug Fix

Issue:
invoke --data '{ json }' does not parse for Java Lambdas


# This is a (Bug Report / Feature Proposal)
Bug
## Description
The --data argument to 'serverless invoke' is not deserializable in Java lambdas.
For bug reports:
* What went wrong?
Create the sample java lamba
Run 
serverless invoke -f hello --data '{ "key1" : "pwd" }'
( and yes I have tried with all 3 keys )

Result:
```
[0][dlee@z600 newService]$ serverless invoke -f hello  --data '{ "key1" : "pwd" }'
{
    "errorMessage": "An error occurred during JSON parsing",
    "errorType": "java.lang.RuntimeException",
    "stackTrace": [],
    "cause": {
        "errorMessage": "com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate value of type [simple type, class hello.Request] from String value ('{ \"key1\" : \"pwd\" }'); no single-String constructor/factory method\n at [Source: lambdainternal.util.NativeMemoryAsInputStream@dc24521; line: 1, column: 1]",
        "errorType": "java.io.UncheckedIOException",
        "stackTrace": [],
        "cause": {
            "errorMessage": "Can not instantiate value of type [simple type, class hello.Request] from String value ('{ \"key1\" : \"pwd\" }'); no single-String constructor/factory method\n at [Source: lambdainternal.util.NativeMemoryAsInputStream@dc24521; line: 1, column: 1]",
            "errorType": "com.fasterxml.jackson.databind.JsonMappingException",
            "stackTrace": [
                "com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148)",
                "com.fasterxml.jackson.databind.DeserializationContext.mappingException(DeserializationContext.java:875)",
                "com.fasterxml.jackson.databind.deser.ValueInstantiator._createFromStringFallbacks(ValueInstantiator.java:281)",
                "com.fasterxml.jackson.databind.deser.std.StdValueInstantiator.createFromString(StdValueInstantiator.java:284)",
                "com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromString(BeanDeserializerBase.java:1176)",
                "com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:143)",
                "com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:134)",
                "com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:1511)",
                "com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1102)"
            ]
        }
    }
}
```

OTOH run aws cli

`aws lambda invoke --function-name newService-dev-hello  --payload '{ "key1" : "pwd" }'
`
The payload parses and the result is correct.

Also run


`serverless invoke -f hello --path data.json
`
Where 'data'json' has the same content. 
Result: works as expected.



* What did you expect should have happened?

Payload data pass unchanged so it would deserialize properly

* What was the config you used?

unmodified java8 runtime

* What stacktrace or error message from your provider did you see?
given


* ***Serverless Framework Version you're using***:
1.1.0
* ***Operating System***:
centos7

* ***Stack Trace***:
provided
* ***Provider Error messages***:
provided


Issue discussion:

Solution patch:
diff --git a/lib/plugins/aws/invoke/index.js b/lib/plugins/aws/invoke/index.js
index 2a34b60984b..8e938994fd3 100644
--- a/lib/plugins/aws/invoke/index.js
+++ b/lib/plugins/aws/invoke/index.js
@@ -28,6 +28,16 @@ class AwsInvoke {
     // validate function exists in service
     this.options.functionObj = this.serverless.service.getFunction(this.options.function);
 
+    // if data is provided via CLI, sanitize it
+    if (typeof this.options.data === 'string') {
+      try {
+        this.options.data = JSON.parse(this.options.data);
+      } catch (exception) {
+        // do nothing if it's a simple string
+      }
+    }
+
+    // if path is provided, load data from path
     if (this.options.path) {
       const absolutePath = path.isAbsolute(this.options.path) ?
         this.options.path :
diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js
index 84db92a0d64..89836a9b66e 100644
--- a/lib/plugins/aws/invokeLocal/index.js
+++ b/lib/plugins/aws/invokeLocal/index.js
@@ -27,6 +27,16 @@ class AwsInvokeLocal {
     // validate function exists in service
     this.options.functionObj = this.serverless.service.getFunction(this.options.function);
 
+    // if data is provided via CLI, sanitize it
+    if (typeof this.options.data === 'string') {
+      try {
+        this.options.data = JSON.parse(this.options.data);
+      } catch (exception) {
+        // do nothing if it's a simple string
+      }
+    }
+
+    // if path is provided, load data from path
     if (this.options.path) {
       const absolutePath = path.isAbsolute(this.options.path) ?
         this.options.path :


Test patch:
diff --git a/lib/plugins/aws/invoke/index.test.js b/lib/plugins/aws/invoke/index.test.js
index b25653c2117..80026db4a8b 100644
--- a/lib/plugins/aws/invoke/index.test.js
+++ b/lib/plugins/aws/invoke/index.test.js
@@ -79,6 +79,26 @@ describe('AwsInvoke', () => {
       expect(() => awsInvoke.extendedValidate()).to.throw(Error);
     });
 
+    it('should parse JSON data if it is provided via CLI', () => {
+      awsInvoke.options.data = '{ "key1" : "pwd" }';
+
+      const parsedData = {
+        key1: 'pwd',
+      };
+
+      return awsInvoke.extendedValidate().then(() => {
+        expect(awsInvoke.options.data).to.deep.equal(parsedData);
+      });
+    });
+
+    it('should keep data if it is a simple string', () => {
+      awsInvoke.options.data = 'simple-string';
+
+      return awsInvoke.extendedValidate().then(() => {
+        expect(awsInvoke.options.data).to.equal('simple-string');
+      });
+    });
+
     it('it should parse file if relative file path is provided', () => {
       serverless.config.servicePath = testUtils.getTmpDirPath();
       const data = {
diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js
index 418e63efdaf..0cc41dcaaf5 100644
--- a/lib/plugins/aws/invokeLocal/index.test.js
+++ b/lib/plugins/aws/invokeLocal/index.test.js
@@ -80,6 +80,26 @@ describe('AwsInvokeLocal', () => {
       expect(() => awsInvokeLocal.extendedValidate()).to.throw(Error);
     });
 
+    it('should parse JSON data if it is provided via CLI', () => {
+      awsInvokeLocal.options.data = '{ "key1" : "pwd" }';
+
+      const parsedData = {
+        key1: 'pwd',
+      };
+
+      return awsInvokeLocal.extendedValidate().then(() => {
+        expect(awsInvokeLocal.options.data).to.deep.equal(parsedData);
+      });
+    });
+
+    it('should keep data if it is a simple string', () => {
+      awsInvokeLocal.options.data = 'simple-string';
+
+      return awsInvokeLocal.extendedValidate().then(() => {
+        expect(awsInvokeLocal.options.data).to.equal('simple-string');
+      });
+    });
+
     it('it should parse mock file if provided', () => {
       serverless.config.servicePath = testUtils.getTmpDirPath();
       const data = {