AWS Lambda Handler extend S3event - json

I have the following pipeline:
A file is uploaded to S3, it triggers a Lambda (Let's call it L1) which runs and does some processing.
So at the moment, my entry point looks like this:
public Response handleRequest(S3Event event, Context context) {
....
}
Now, a S3Event JSON looks like this:
{
"Records": [
{
"awsRegion": "xxxxx",
"eventName": "ObjectCreated:Put",
"eventSource": "aws:s3",
"eventTime": "2017-09-12T09:27:59.471Z",
"eventVersion": "2.0",
"requestParameters": {
"sourceIPAddress": "xxxxxx"
},
"responseElements": {
"x-amz-id-2": "xxxxxx",
"x-amz-request-id": "xxxx"
},
"s3": {
"configurationId": "xxxxxx1",
"bucket": {
"name": "xxxxx",
"ownerIdentity": {
"principalId": "xxxxx"
},
"arn": "xxx"
},
"object": {
"key": "xxx",
"size": xxx,
"eTag": "xxxx",
"versionId": null,
"sequencer": "xxx",
"urlDecodedKey": "xxx"
},
"s3SchemaVersion": "1.0"
},
"userIdentity": {
"principalId": "xxxx"
}
}
],
}
If you pass this JSON in the "Test" section, it will succeed.
Now, to the point: I wish to add information to this JSON, something that would look like this:
{
"Records": [
{
"awsRegion": "xxxxx",
"eventName": "ObjectCreated:Put",
"eventSource": "aws:s3",
"eventTime": "2017-09-12T09:27:59.471Z",
"eventVersion": "2.0",
"requestParameters": {
"sourceIPAddress": "xxxxxx"
},
"responseElements": {
"x-amz-id-2": "xxxxxx",
"x-amz-request-id": "xxxx"
},
"s3": {
"configurationId": "xxxxxx1",
"bucket": {
"name": "xxxxx",
"ownerIdentity": {
"principalId": "xxxxx"
},
"arn": "xxx"
},
"object": {
"key": "xxx",
"size": xxx,
"eTag": "xxxx",
"versionId": null,
"sequencer": "xxx",
"urlDecodedKey": "xxx"
},
"s3SchemaVersion": "1.0"
},
"userIdentity": {
"principalId": "xxxx"
}
}
],
"MyErrorMessage":
{
"EnvelopeErrors": [
{
"EnvelopeErrorTrace": "stackTrace",
"EnvelopeErrorPositions": 1,
"EnvelopeErrorLength": 2
},
{
"EnvelopeErrorTrace": "SecondTrace",
"EnvelopeErrorPositions": 3,
"EnvelopeErrorLength": 4
}
],
}
}
Notice is the S3Event JSon but with a bit more data.
My question problem is the following: I want to have a custom input that also works when a pure S3Event is called.
public Response handleRequest(MyS3Event event, Context context) {
....
}
However, I have not been able to achieve this.
I have tried a custom POJO but it does not work when I upload to S3 a file.
I tried to extend the S3EventNotification class (from which S3Event extends), but again with no success.
Is it possible what I am trying to do?

What you can do is to have your Lambda (L1) call itself (asynchronously) by sending it the new, modified event similar to how recursive functions work.
Just be very careful though. You have to put a limit as to how deep you want to keep recursing. You don't want to end up with infinite calls. I am not sure if AWS guards against this.

In the AWS SDK Lambda has an invoke method:
Invokes a specific Lambda function. For an example, see Create the Lambda Function and Test It Manually.
If you are using the versioning feature, you can invoke the specific
function version by providing function version or alias name that is
pointing to the function version using the Qualifier parameter in the
request. If you don't provide the Qualifier parameter, the $LATEST
version of the Lambda function is invoked. Invocations occur at least
once in response to an event and functions must be idempotent to
handle this. For information about the versioning feature, see AWS Lambda Function Versioning and Aliases.
This operation requires permission for the lambda:InvokeFunction
action.
var params = {
FunctionName: 'STRING_VALUE', /* required */
ClientContext: 'STRING_VALUE',
InvocationType: Event | RequestResponse | DryRun,
LogType: None | Tail,
Payload: new Buffer('...') || 'STRING_VALUE',
Qualifier: 'STRING_VALUE'
};
lambda.invoke(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
One of the params you send a Payload which is the event the invoked function receives so you can send your MyErrorMessage in/as this payload to get the desired result.

Related

How to validate the contents inside the JSON body

In postman i want to to validate '"name": "Twilio"', which is inside data > twilio > viewable > name
here is the JSON response ive got
{
"success": true,
"timestamp": "2019-08-12T12:31:33+00:00",
"response_code": 200,
"data": {
"twilio": {
"name": "Twilio",
"slug": "twilio",
"image": "https://s3-eu-west-1.amazonaws.com/connector-assets/images/twilio.png",
"description": "Twilio is a cloud communications platform as a service. Integrate your Twilio account to send outgoing SMS from Purple.",
"category": "communication",
"connectedCount": 1,
"allowMultipleAdd": false,
"editable": [],
"viewable": {
"cf-5d51503868af3": {
"name": "Twilio"
}
},
"id": "cf-5d515c2527579",
"overrideAllowed": false
},
"salesforce-mc": {
"name": "Salesforce marketing cloud",
"slug": "salesforce-mc",
"image": "https://s3-eu-west-1.amazonaws.com/connector-assets/images/sf-mc.png",
"description": "Salesforce Marketing Cloud is a provider of digital marketing automation and analytics software and services.",
"category": "marketing-automation",
"connectedCount": 0,
"allowMultipleAdd": true,
"editable": [],
"viewable": [],
"id": "cf-5d515c252bc31",
"overrideAllowed": true
}
}
}
The easiest way is to process the response as JSON, and simply check your hierarchy:
In the tests section, you can add a number of tests:
pm.test("Response should be OK", function () {
pm.response.to.be.ok;
});
// You can actually check for explicit response code if a 200 series is not enough.
pm.test("Status code is 200", function () {
pm.response.to.have.status(200);
});
// Optionally test the returned Content-Type is correct.
pm.test("Content-Type is present", function () {
pm.response.to.have.header("Content-Type");
});
pm.test("Response should be okay to process", function () {
pm.response.to.be.ok;
pm.response.to.not.be.error;
});
pm.test("Body contains JSON data result with Twilio as the viewable name", function () {
const JsonData = pm.response.json();
pm.expect(JsonData.data.twilio.viewable.name).to.equal('Twilio');
});
For safety, you can check that each parent element exists first (validate data is in JsonData) to avoid errors should the payload change or not match what you expect.
EDIT: Your JSON structure:
{
"success": true,
"timestamp": "2019-08-12T12:31:33+00:00",
"response_code": 200,
"data": {
"twilio": {
"name": "Twilio",
"slug": "twilio",
"image": "https://s3-eu-west-1.amazonaws.com/connector-assets/images/twilio.png",
"description": "Twilio is a cloud communications platform as a service. Integrate your Twilio account to send outgoing SMS from Purple.",
"category": "communication",
"connectedCount": 1,
"allowMultipleAdd": false,
"editable": [],
"viewable": {
"cf-5d51503868af3": {
"name": "Twilio"
}
},
"id": "cf-5d515c2527579",
"overrideAllowed": false
},
"salesforce-mc": {
"name": "Salesforce marketing cloud",
"slug": "salesforce-mc",
"image": "https://s3-eu-west-1.amazonaws.com/connector-assets/images/sf-mc.png",
"description": "Salesforce Marketing Cloud is a provider of digital marketing automation and analytics software and services.",
"category": "marketing-automation",
"connectedCount": 0,
"allowMultipleAdd": true,
"editable": [],
"viewable": [],
"id": "cf-5d515c252bc31",
"overrideAllowed": true
}
}
}
Based on this, the text you are looking for is present, but under an ID, not directly available as you indicated. There are two checks that could be done:
1. Use the name in the main block
pm.test("Body contains JSON data result with Twilio as the viewable name", function () {
const JsonData = pm.response.json();
pm.expect(JsonData.data.twilio.name).to.equal('Twilio');
});
The other would be to extract the viewable value ('cf-5d51503868af3') and use this value as a key to do the test. Not sure if this is available to you from your user id or another data source. If not, then you need to extract it from the JsonData.data.twilio.viwable object. There are some more useful examples here in this answer.

Use Apps Script URLFetchApp to access Google Datastore Data

I want to experiment with Google Datastore via Apps Script because I have a current solution based on Google sheets that runs into timeout issues inherent in constantly transacting with Drive files. I've created a test project in Google cloud with a service account and enabled library MZx5DzNPsYjVyZaR67xXJQai_d-phDA33
(cGoa) to handle the Oauth2 work. I followed the guide to start it up here and got all the pertinent confirmation that it works with my token (and that removing the token throws an 'authentication failed prompt').
Now I want to start with a basic query to display the one entity I already put in. I can use the API Explorer here and run this query body:
{
"query": {}
}
and get this result:
{
"batch": {
"entityResultType": "FULL",
"entityResults": [
{
"entity": {
"key": {
"partitionId": {
"projectId": "project-id-5200707333336492774"
},
"path": [
{
"kind": "Transaction",
"id": "5629499534213120"
}
]
},
"properties": {
"CommentIn": {
"stringValue": "My First Test Transaction"
},
"Status": {
"stringValue": "Closed"
},
"auditStatus": {
"stringValue": "Logged"
},
"User": {
"stringValue": "John Doe"
},
"Start": {
"timestampValue": "2017-08-17T18:07:04.681Z"
},
"CommentOut": {
"stringValue": "Done for today!"
},
"End": {
"timestampValue": "2017-08-17T20:07:38.058Z"
},
"Period": {
"stringValue": "08/16/2017-08/31/2017"
}
}
},
"cursor": "CkISPGogc35whh9qZWN0LWlkLTUyMDA3MDcwODA1MDY0OTI3NzRyGAsSC1RyYW5zYWN0aW9uGICAgICAgIAKDBgAIAA=",
"version": "1503004124243000"
}
],
"endCursor": "CkISPGogc35wcm9qZWN0LWlkLTUyMDAxxDcwODA1MDY0OTI3NzRyGAsSC1RyYW5zYWN0aW9uGICAgICAgIAKDBgAIAA=",
"moreResults": "NO_MORE_RESULTS"
}
}
I try to do the same thing with this code:
function doGet(e)
{
var goa = cGoa.GoaApp.createGoa('Oauth2-Service-Account',
PropertiesService.getScriptProperties()).execute(e);
if(goa.hasToken()) {var token = goa.getToken();}
var payload = {"query":{}}
;
var result = UrlFetchApp.fetch('https://datastore.googleapis.com/v1/projects/project-id-5200707333336492774:runQuery',
{
method: "POST",
headers: {authorization: "Bearer " + goa.getToken()},
muteHttpExceptions : true,
payload: payload
});
Logger.log(result.getBlob().getDataAsString());
}
and get this error in the logger:
"error": {
"code": 400,
"message": "Invalid JSON payload received. Unknown name \"query\": Cannot bind query parameter. 'query' is a message type. Parameters can only be bound to primitive types.",
"status": "INVALID_ARGUMENT",
"details": [
{
"#type": "type.googleapis.com/google.rpc.BadRequest",
"fieldViolations": [
{
"description": "Invalid JSON payload received. Unknown name \"query\": Cannot bind query parameter. 'query' is a message type. Parameters can only be bound to primitive types."
}
]
}
]
}
}
If I try to use another word such as 'resource' or 'GqlQuery', I get this error:
"error": {
"code": 400,
"message": "Invalid JSON payload received. Unknown name \"GqlQuery\": Cannot bind query parameter. Field 'GqlQuery' could not be found in request message.",
"status": "INVALID_ARGUMENT",
"details": [
{
"#type": "type.googleapis.com/google.rpc.BadRequest",
"fieldViolations": [
{
"description": "Invalid JSON payload received. Unknown name \"GqlQuery\": Cannot bind query parameter. Field 'GqlQuery' could not be found in request message."
}
]
}
]
}
}
I can't tell from the API Documentation what my syntax is supposed to be. Can anyone tell me how to compile a functional request body from Apps Script to Datastore?
You need to set the contentType of your payload as well as stringify your JSON payload as follows:
var result = UrlFetchApp.fetch(
'https://datastore.googleapis.com/v1/projects/project-id-5200707333336492774:runQuery',
{
'method':'post',
'contentType':'application/json',
'headers': {authorization: "Bearer " + goa.getToken()},
'payload':JSON.stringify(payload)
}
);

How to get json value from Angular2 app

I need to get values from a JSON file which is served from fake-json-server.
To be precise, I need an exact value, e.g. I need to get all "type": "values" where group is Air.
I'm using Angular2 with TypeScript and here is a part of the code where I'm doing a get request in the TransformerService file:
getVehicleGroups(groupName: string) {
return this.http.get(`${this.apiUrl}/vehicleTypes?group=${groupName}`)
.map((res: Response) => res.json() as VehicleTypes[]).catch(this.handleError);
}
Exported class:
export class VehicleTypes {
// vehicleGroup: string;
vehicleType: string;
vehicleModel: string;
}
And here I'm calling that method in the separate file:
getVehicleGroups() {
return this.transformersService.getVehicleGroups(this.vehicleGroup)
.subscribe((vehicleTypes => this.vehicleTypes = vehicleTypes));
}
The url of the fake-server "http://localhost:3000/vehicleTypes" and this is the code from db.json on that server (url):
[
{
"group": "Air",
"type": "Plane",
"model": "F-22"
},
{
"group": "Air",
"type": "Plane",
"model": "Sukhoi"
},
{
"group": "Air",
"type": "Plane",
"model": "MiG"
},
{
"group": "Air",
"type": "Helicopter",
"model": "Apache"
},
{
"group": "Air",
"type": "Helicopter",
"model": "Kamov"
}
{
"group": "Sea",
"type": "Boat",
"model": "Sailboat"
},
{
"group": "Sea",
"type": "Boat",
"model": "Jetboat"
},
{
"group": "Sea",
"type": "Submarine",
"model": "Standard"
},
{
"group": "Land",
"type": "Car",
"model": "Camaro"
},
{
"group": "Land",
"type": "Car",
"model": "AMG GT R"
},
{
"group": "Land",
"type": "Car",
"model": "Lamborghini"
},
{
"group": "Land",
"type": "Truck",
"model": "Unimog"
},
{
"group": "Land",
"type": "Truck",
"model": "Western Star 5700"
}
]
I need to mention, all my files are set well. I don't get any errors, I'm just not getting the right values..
I need to get all "type": "values" where group is Air
First you need do filter your json result to get Air group only.
You can apply observable filter
getVehicleGroups(groupName: string) {
return this.http.get(`${this.apiUrl}/vehicleTypes?group=${groupName}`)
.filter(data => data.group === "Air")
.map((res: Response) => res.json() as VehicleTypes[]).catch(this.handleError);
}
Second your VehicleTypes model variable names are different with json response so how will angular convert your json array into VehicleTypes array. you need change VehicleTypes class or your backend code send match variables name.
export interface VehicleTypes {
type: string;
model: string;
}
Adjust the brackets of your method.
From:
getVehicleGroups() {
return this.transformersService.getVehicleGroups(this.vehicleGroup)
.subscribe((vehicleTypes => this.vehicleTypes = vehicleTypes));
}
To:
getVehicleGroups() {
return this.transformersService.getVehicleGroups(this.vehicleGroup)
.subscribe((vehicleTypes) => this.vehicleTypes = vehicleTypes);
}
Map the data with your model:
Option 1: Change the model to match the json data.
export class VehicleTypes {
type: string;
model: string;
}
Option 2: Change the json properties at service level, right after converting to json.
getVehicleGroups(groupName: string) {
return this.http.get(`${this.apiUrl}/vehicleTypes?group=${groupName}`)
.map((res: Response) => res.json().map(res => new VehicleTypes(res.type, res.model)).catch(this.handleError);
}
and you would need to create a constructor for the VehicleTypes.
You do not need a class in this case, a type interface will suffice since you don't have (or seem to need) any methods in your vehicle, you're only using it for type assertion.
A class exists in the emitted JavaScript incurring unnecessary overhead, whereas an interface provides type safety without emitting classes to JavaScript: it's only used by the type-checker in tsc and then discarded.
export interface VehicleTypes {
// vehicleGroup: string;
vehicleType: string;
vehicleModel: string;
}
Declare the type that your service returns:
getVehicleGroups(groupName: string): Observable<VehicleTypes[]> {
return this.http.get(`${this.apiUrl}/vehicleTypes?group=${groupName}`)
.map(res.json)
.catch(this.handleError);
}
And consume it in your Component as:
// Assert the type vehicleTypes expects
vehicleTypes: <VehicleTypes>[];
getVehicleGroups() {
return this.transformersService.getVehicleGroups(this.vehicleGroup)
.subscribe(vehicleTypes => this.vehicleTypes = vehicleTypes);
}
Note that you don't need the (res: Response) assertion in the chain from http.get: that's what get is typed to return anyway so the type-checker already knows what to expect. Since you can remove the parameter, you can make the chain even shorter, as you did with .catch.

Get a custom JSON response from Loopback

I made a simple API using Loopback.It works fine and give the result below from this URL.
http://localhost:3000/api/CoffeeShops
[
{
"name": "Coffee shop 1",
"city": "City one",
"id": 1
}
]
I need to change this JSON to this template, By using Loopback middleware.
{
"_embedded": {
"CoffeeShops": [
{
"name": "Coffee shop 1",
"city": "City one",
"_links": {
"self": {
"href": "http://localhost:3000/CoffeeShops/1"
},
"CoffeeShop": {
"href": "http://localhost:3000/CoffeeShops/1"
}
}
}
]
}
}
Better yet than a middleware, you can use a remote hook
Use afterRemote hooks to modify, log, or otherwise use the results of a remote method before sending it to a remote client. Because an afterRemote hook runs after the remote method is executed, it can access the result of the remote method, but cannot modify the input arguments.
The following code inside coffee-shop.js will do the trick
CoffeeShop.afterRemote('find', function(ctx, output, next) {
ctx.result = {
_embedded: {
CoffeeShops: [{
name: output.name,
city: output.city,
_links: {
self: {
href: "http://localhost:3000/CoffeeShops/" + id
},
CoffeeShop: {
href: "http://localhost:3000/CoffeeShops/" + id
}
}
}]
}
};
next();
});

How to pass nested json object through soapui in request api

I am new to REST and i am making test cases for server client application. where server is made in php using symphony framework and clients are available both in android and ios. Server implements Rest instead of Soap. Now i have to make a test case in soapui to add a group in which nested JSON parameter is going but i am failed to send parameter as nested. I can show you the request from the log on server side.
[2014-09-24 11:14:36] las_api.INFO:
{
"request": {
"url": "http:\/\/192.168.1.134\/las_web\/web\/app_dev.php\/api\/users\/2\/groups.json",
"attributes": {
"_controller": "Las\\ApiBundle\\Controller\\User\\GroupController::cpostAction",
"_format": "json",
"userId": "2",
"_route": "api_post_user_groups",
"_route_params": {
"_format": "json",
"userId": "2"
}
},
"post": {
"group": {
"name": "Seniors"
},
"token": "{81A9B0D9-CA0E-E70F-ADFF-116EE7A1A980}"
},
"get": [
]
},
"ip": "192.168.1.134"
}
Below is the parameter request parameter. where in group there are two parameters first is name of the group and second is the token generated by server on login time.
{
"group": {
"name": "Seniors"
},
"token": "{81A9B0D9-CA0E-E70F-ADFF-116EE7A1A980}"
}
I have successfully made test case for login. I can show you the request from server log while logging.
[2014-09-24 10:11:37] las_api.INFO:
{
"request": {
"url": "http:\/\/192.168.1.134\/las_web\/web\/app_dev.php\/api\/tokens",
"attributes": {
"_controller": "Las\\ApiBundle\\Controller\\TokenController::cpostAction",
"_format": "json",
"_route": "api_post_tokens",
"_route_params": {
"_format": "json"
}
},
"post": {
"password": "abc123",
"username": "abc#gmail.com"
},
"get": [
]
},
"ip": "192.168.1.134"
}