jsons:getString() exception - wso2

I'm getting an exception which feels like it might be a defect. I'm wondering if anyone else sees a problem with this. Here is simplified but runnable service I am writing.
import ballerina.net.http;
import ballerina.lang.messages;
import ballerina.lang.jsons;
import ballerina.lang.system;
#http:BasePath("/weather")
service WeatherService {
#http:GET
#http:Path("/current")
resource current(message m) {
string url = "http://api.mesowest.net/v2/stations/nearesttime?stid=KBFI&within=60&vars=air_temp,wind_speed,wind_direction&obtimezone=local&token=demotoken";
http:ClientConnector weatherConnector = create http:ClientConnector(url);
message request = {};
message jsonResponse = http:ClientConnector.get(weatherConnector, "", request);
json jsonDocument = messages:getJsonPayload(jsonResponse);
json timestamp;
string timeString;
try {
timestamp = jsons:getJson(jsonDocument, "$..PERIOD_OF_RECORD.end");
}
catch (exception e) {
system:println("Error getting timestamp");
}
messages:setJsonPayload(m, timestamp);
reply m;
}
}
When I run this in the debugger, the json variable 'timestamp' is assigned the appropriate value from the JSON excerpt below:
"STATION": [
{
"STATUS": "ACTIVE",
"MNET_ID": "1",
"PERIOD_OF_RECORD": {
"start": "1969-12-31T16:00:00-0800",
"end": "2017-02-27T19:40:00-0800"
}
When I replace the line:
timestamp = jsons:getJson(jsonDocument, "$..PERIOD_OF_RECORD.end");
with the line
timeString = jsons:getString(jsonDocument, "$..PERIOD_OF_RECORD.end");
and stop and restart the service and test it, it throws an exception on the getString method. I haven't found a way to print an exception yet or get attributes of the exception in order to find why its failing. The console out is as follows.
Running weather2.bal service.
ballerina: deploying service(s) in '/Users/clarkm2/Projects/Ballerina/Weather/weather2.bal'
ballerina: started server connector http-9090
Error getting timestamp
An idea on this? If this is a defect, do these get reported on the wso2.com JIRA site?
Thanks.

The reason for this error is that, the element returned by $..PERIOD_OF_RECORD.end is not a string. It returns the following
"PERIOD_OF_RECORD": {
"end": "2017-02-27T21:00:00-0800",
"start": "1969-12-31T16:00:00-0800"
},
which cannot be converted to text. If you log the exception in the catch block you will be able to observe the following error.
Error getting timestamp : Failed to get string from json. Error while executing jsonpath: The element matching path: $..PERIOD_OF_RECORD.end is not a String.
To log the exception modify your code as :
catch (exception e) {
system:println("Error getting timestamp : " + exceptions:getMessage(e));
}
with import ballerina.lang.exceptions import statement.

Try changing the jsonpath as follow:
string timeString = jsons:getString(j, "$.STATION[0].PERIOD_OF_RECORD.end");

Related

GSP PubSub topic with proto schema - TimeStamp validation error

I would like to use GCP PubSub with proto schema validation where messages will be in JSON. A part of the message is also TimeStamp so I added TimeStamp definition into the message (because imports are not supported) and my schema definition looks like this:
syntax = "proto3";
message NewSourceEvent {
message Source {
Timestamp time = 1;
string username = 2;
}
message Timestamp {
int64 seconds = 1;
int32 nanos = 2;
}
Source source = 1;
}
Test message:
{
"source": {
"time": "1999-05-11T05:17:10Z",
"username": "un"
}
}
And when I test the message from my service (which is serialized protobuf object into JSON) it's failed because it cannot serialize the TimeStamp - Invalid schema message: (source.time): invalid value "1999-05-11T05:17:10Z" for type type.googleapis.com/NewSourceEvent.Timestamp.
Is there any way how to define the schema to be able to parse the message and time will be still TimeStamp?
the timestamp type is imported from a external definition, like import "google/protobuf/timestamp.proto";
BUT, Pub/Sub does not support external import for now, as you check here , so I'm afraid it's not going to work. The workaround would be change it to string type.
I could not find a way to enable imports or to use timestamp without it.
The definition you have created is actually validating a message like this:
{
"source":{
"time":{
"seconds":"999",
"nanos":"999"
},
"username":"un"
}
You could try using a unix timestamp format in a int64 variable: 1643886443340 = Thursday, February 3, 2022 11:07:23.340 AM

Propogating error message through Fail state in aws Step Functions

I am using aws Step Functions to manage a workflow. I am using Fail states to handle errors within the workflow. I would like to propagate some of the json from the Step Function workflow so that a user can easily identify the source of their error. So for example, if the json input to a Fail state looked like this:
{
"error": "some error text",
"other_stuff": {...}
}
Then I would like to pull the source of the error. I have set up my Fail state like so:
FailState:
Type: Fail
Cause: States.Format($.error)
Error: Failure Here
However, this simply produces the literal string States.Format($.error) as the Cause for the Fail state. How can I use aws states language and the Fail state to show the actual error as a part of the output of the Fail state? Any solution that can successfully propagate the error text from Step Input to Step Output for the Fail state would be sufficient to solve this problem.
If anyone else stumbles on this question, I contacted AWS support and this is what they told me:
"The ‘Cause’ and ‘Error’ fields in this state only accept the string type values. This is why you are getting the literal string as a response. However, the good news is that, we already have an existing feature request, pending with Step Functions Development team, to implement a feature for sending JSON Path(like $.error) into the Fail state."
So for some reason AWS step functions does not allow you to pass dynamic error messages. They did offer some workarounds such as changing the failure state to success and propagating the error message that way, or creating an sns topic in the case of statemachine failure to post to. I personally just updated the status polling API to grab the state at index [-2] to propagate the error to the user. In any case, to use this functionality currently some workaround should be employed, and hopefully AWS can get this feature out quickly.
I was able to achieve something close to a desired behavior by creating a "Fail-Me" Lambda that fails with an unhandled exception, dynamically choosing exception class based on the Error and Cause provided as its payload. If "Error" was a name of a built-in exception class it uses it, otherwise it creates its own class.
In State Machine, use an Invoke Lambda state calling this "Fail-Me" lambda with no retrier and no catcher.
import inspect, sys
# Expected payload (event), example:
# {
# "Error":"RuntimeError",
# "Cause":"No Cause"
# }
def lambda_handler(event, context):
ErrorType = event.get("Error") or ""
ErrorCause = event.get("Cause") or ""
if not ErrorType.isidentifier():
ErrorCause = "{}: {}".format(ErrorType, ErrorCause)
ErrorType = "OtherError"
DynamicExceptionClass = type(ErrorType, (BaseException,), {})
for name, obj in inspect.getmembers(sys.modules["builtins"]):
if inspect.isclass(obj) and issubclass (obj, BaseException):
if name==ErrorType: #ErrorType is an existing built-in exception - use it
raise obj(ErrorCause)
#Create our own dynamic class and raise it
DynamicExceptionClass = type(ErrorType, (BaseException,), {})
raise DynamicExceptionClass(ErrorCause)
I too want to use a JSON path in a Fail state to dynamically set the Cause and Error fields. In my case, I have a known set of errors so for each one I created a separate Fail state. Each one of those fail states corresponded with a catch object where Next is set to the appropriate fail state.
{
"TaskState": {
"Type": "Task",
...
"Catch": [
{
"ErrorEquals": ["ErrorA"],
"Next": "FailureA"
},
{
"ErrorEquals": ["ErrorB"],
"Next": "FailureB"
}
]
},
"FailureA": {
"Type": "Fail",
"Error": "FailureA",
"Cause": "This failed because of A"
},
"FailureB": {
"Type": "Fail",
"Error": "FailureB",
"Cause": "This failed because of B"
}
}

Lambda connected to appsync always returns Lambda:Unhandled errorType no matter the custom Exception

I have a chat lambda that stores messages into dynamodb. If the user is not authorized to add the message, I need to return a custom error exception with unique errorType and message to my client.
I have setup my custom error using the documentation at https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference-lambda.html
i.e.:
export default class ForbiddenError extends Error {
/**
* ForbiddenError constructor
* #param {string} name
* #param {string} message
*/
constructor(name = null, message = null) {
message = message || 'You are forbidden from performing this action.'
super(message)
this.name = name || 'Forbidden'
}
}
and then I throw the error inside my app via:
throw new ForbiddenError()
When I test my lambda locally, everything works nicely, the code encounters an error. I catch it and call
context.fail(error)
Even when I test my lambda using a testing lambda in the AWS console, I get a beautiful response containing both the message and the error type:
class ForbiddenError extends Error {
constructor(message) {
super(message)
this.name = 'ForbiddenError'
}
}
exports.handler = (event, context, callback) => {
throw new ForbiddenError('You are forbidden from performing this action.')
return context.fail('test');
};
and the error:
{
"errorType": "ForbiddenError",
"errorMessage": "You are forbidden from performing this action.",
"trace": [
"ForbiddenError: You are forbidden from performing this action.",
" at Runtime.exports.handler (/var/task/index.js:9:21)",
" at Runtime.handleOnce (/var/runtime/Runtime.js:66:25)"
]
}
but when I call my lambda using appsync, suddenly only the message is passed into the error, but the errorType is always the same: Lambda:Unhandled i.e.:
{
"graphQLErrors": [
{
"path": [
"storeStreamChatMessage"
],
"data": null,
"errorType": "Lambda:Unhandled",
"errorInfo": null,
"locations": [
{
"line": 2,
"column": 3,
"sourceName": null
}
],
"message": "You are forbidden from performing this action."
}
],
"networkError": null,
"message": "GraphQL error: You are forbidden from performing this action."
}
The response mapping template is the same is in the documentation:
ResponseMappingTemplate: |
#if($ctx.error)
$util.error($ctx.error.message, $ctx.error.type)
#end
$util.toJson($context.result)
I was trying to change the $ctx.error.type to $ctx.error.errorType, but then the errorType is returned as "Custom lambda error" and not "Forbidden".
I have tried using both the context.fail() and callback methods from the lambda exports.handler, both work nicely in the console but both only return the message when called from the appsync resolver.
I have tested that the context.fail(error) is indeed called in the lambda and that the exception is caught via .catch() statement before finally calling the context.fail(error)
Even cloudwatch displays the error with the message and errorType when the main lambda is called, so I'm suspecting an error in exepction and context.fail() returning
In the end, I stringified the error and passed it to my clients via the message column. Unfortunately, when I managed to pass the proper errorType via the resolver response mapping template, the returned error started returning "Custom Error" as the return errorType.

AWS API Gateway: Can't map thrown error from Lambda to Method Response using Integration Response

The issue
Using the Integration Response feature of AWS API Gateway, I can successfully handle errors thrown by Lambda. However, I'm having trouble mapping the Javascript error object to the mapping template. The error object looks like this.
{
"errorMessage": "Error: ABC123",
"errorType": "Error",
"stackTrace": [
"exports.handler (/var/task/index.js:9:11)"
]
}
Here's my Integration Response mapping for application/json
#set($errorMessage = $input.path('$.errorMessage'))
{
"message" : $errorMessage
}
What I've tried
Using this configuration, this is the entire response returned to the client: Unexpected 'E'. This is one string and is not contained in a JSON object.
This E that's referenced in the error is the first character from my thrown error message, which is used to match the Lambda Error Regex. I know this as I briefly changed the first letter to X and I got Unexpected 'X' as the response.
When I change the first line of my mapping template to this (mapping the entire object rather than attempting to just map the errorMessage property)
#set($errorMessage = $input.path('$'))
I get only the stack trace from the Javascript error object.
{
"message" : [
"exports.handler (/var/task/index.js:9:11)"
]
}
This would suggest that either the entire response object being returned to API Gateway is just the stackTrace property from the Javascript error. But to me, this doesn't make sense as something is picking up the errorMessage as thats where the Unexpected 'E' message is coming from.
Similarly, when I try and map the errorType property, I get the same error as it also starts with E. I can only successfully map the message property when I use the entire $ input object, or just the stackTrace property.
What am I doing wrong here?
Other relevant code
Here's the API Gateway Error Model. The message property is clearly marked as a string type yet it only works when I return an array. Note: this is the default code
{
"$schema" : "http://json-schema.org/draft-04/schema#",
"title" : "Error Schema",
"type" : "object",
"properties" : {
"message" : { "type" : "string" }
}
}
Here's the Lambda function code
exports.handler = async (event, context, callback) => {
throw new Error(Error: ABC123);
};
I've figured it out. The solution is to use $input.json() rather than $input.path()
Here's my new mapping template
{
"errorMessage" : $input.json('$.errorMessage')
}

Obtaining error while using amazon lex "Invalid Lambda Response: Received invalid response from Lambda"

I'm trying to develop a chatbot using AWS Lex. But unfortunately, I'm getting an error while building the chat on Lex. I'm using one intent and 2 slots. For some reason, when the lambda function is connected to the chat, the second value of the slot is saved as null. But when I run it in lambda as a test case, it's successful.
Right now, all I want to do is show a response message after the details of the slot is entered.
This is my code
public class LexBot implements RequestHandler<Map<String, Object>, Object> {
#Override
public Object handleRequest(Map<String, Object> input, Context context) {
// LexRequest lexRequest = LexRequestFactory.createLexRequest(input);
String content = "Request came from the bot: ";
Message message = new Message("PlainText", content);
DialogAction dialogAction = new DialogAction("Close", "Fullfiled", message);
return new LexRespond(dialogAction);
}
}
And this is the error I'm getting in AWS Lex:
An error has occurred: Invalid Lambda Response: Received invalid
response from Lambda: Can not construct instance of Message, problem:
content must not be blank at [Source:
{"dialogAction":{"type":"Close","message":{"contentType":"PlainText","some_respond_message":"Request
came from the bot: "}}}; line: 1, column: 122]
If you are using amazon lexv2, then amazon lex expecting a different JSON response compared to lexv1.
Sample lambda response which is accpeted by lex:
{
"sessionState": {
"dialogAction": {
"type": "Close"
},
"intent": {
"confirmationState": "Confirmed",
"name": "SearchProducts",
"state": "Fulfilled",
},
},
"messages": [
{
"contentType": "PlainText",
"content": "Select from the list",
}
]
}
Check here for the full response structure https://docs.aws.amazon.com/lexv2/latest/dg/lambda.html
According to the docs, below is the correct format for constructing the final response:
{
"sessionAttributes": session_attributes,
"dialogAction":{
"type":"Close",
"fulfillmentState":"Fulfilled",
"message":{
"contentType":"PlainText",
"content":message
}
}
}
Use this format for constructing the response to avoid the error.
You spelled "fulfilled" incorrectly - you typed "Fullfiled" as pasted in below:
DialogAction dialogAction = new DialogAction("Close", "Fullfiled", message);