Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose the lambda context in the virtual alexa filter() method #68

Open
OpenDog opened this issue Jun 7, 2018 · 6 comments
Open

Expose the lambda context in the virtual alexa filter() method #68

OpenDog opened this issue Jun 7, 2018 · 6 comments

Comments

@OpenDog
Copy link

OpenDog commented Jun 7, 2018

The ability to tweak the lambda context as well in VirtualAlexa (filter function) would be a nice feature. Currently it only offers the request (which is the alexa payload in case of BST tools).

Use case

Our lambda is exposed with the AWS API GW (with Serverless) and it serves multiple skills, both on Alexa and Google Assistant and it relies on the request path to figure out the platform and the skill.

We use the BST proxy for debugging, and the Virtual Alexa for unit testing. The BST proxy puts the request path and the query parameters into the lambda context (request attribute). We need to mock that with VirtualAlexa in the unit tests.

But there is a bigger picture...

It would be really cool if the BST tools would offer an option to simulate the event and context format of the API Gateway Lambda Proxy. This is default with Serverless or ClaudiaJS installations. The important difference is that Amazon uses the event to pass in information about the HTTP request (BST sends the payload in the event). Also Amazon's callback response format is different too: they want you to wrap the response into a simple object.

The bottom line is this: the consolidation of the lambda event and context structures would make the code simpler for lambda projects exposed with Serverless or ClaudiaJS (anything that uses the default API GW Lambda proxy format).

To illustrate the "lambda context hell" we are in, this is what we call first thing in the lambda handler to sniff out the environment:

import * as fs from "fs";

/**
 * Pick out the data we need, depending on the environment we run in.
 * Currently we support AWS default lambda proxy, bst proxy (real-time debugging) 
 * and VirtualAlexa (unit testing).
 *
 * Additionally attach a function to build the response in a way that is specific to the
 * lambda environment.
 *
 * @param event
 * @param context
 * @returns {any}
 */
export function translateLambdaContext(event: any, context: any): any {
    if (!event || !context) { return {}; }

    let eventContext = {};

    if (event.requestContext) {
        eventContext = lambdaProxyContext(event, context);
    } else if (context.request) {
        eventContext = bstContext(event, context);
    } else if (event.testContext) {
        eventContext = virtualBstContext(event, context);
    }

    return eventContext;
}

/**
 * BST format
 *
 * @param lambdaEvent
 * @param lambdaContext
 * @returns {any}
 */
function bstContext(lambdaEvent: any, lambdaContext: any): any {
    const [path] = lambdaContext.request.url.split("?");

    const params = Object.assign({}, parsePath(path));
    params.rawBody = JSON.stringify(lambdaEvent);
    params.body = lambdaEvent;

    params.buildResponse = (code: number, result: any): any => {
        return result;
    };

    return params;
}

/**
 * Virtual BST format
 *
 * The path is piggybacked on the payload (lambda event) for now.
 *
 * @param lambdaEvent
 * @param lambdaContext
 * @returns {any}
 */
function virtualBstContext(lambdaEvent: any, lambdaContext: any): any {
    const path = lambdaEvent.testContext.path;

    const params = Object.assign({}, parsePath(path));
    params.rawBody = JSON.stringify(lambdaEvent);
    params.body = lambdaEvent;

    params.buildResponse = (code: number, result: any): any => {
        return result;
    };

    return params;
}

/**
 * Basic AWS API Gateway lambda proxy format
 *
 * @param lambdaEvent
 * @returns {any}
 */
function lambdaProxyContext(lambdaEvent: any, lambdaContext: any): any {
    if (!lambdaEvent.path) {
        return {};
    }

    const path = lambdaEvent.path;

    const params = Object.assign({}, parsePath(path));
    params.rawBody = lambdaEvent.body;
    params.body = JSON.parse(lambdaEvent.body);
    params.headers = lambdaEvent.headers;
    params.alexaApplicationId =
        lambdaEvent.queryStringParameters ? 
           lambdaEvent.queryStringParameters.alexaApplicationId : undefined;

    params.buildResponse = (code: number, result: any): any => {
        return {
            statusCode: code,
            body: JSON.stringify(result)
        };
    };

    return params;
}

/**
 * This follows the current path convention: .../dev/apps/{appId}/run/{platform}
 *
 * @param {string} path
 * @returns {any}
 */
function parsePath(path: string): any {
    const params: any = {};

    const pathParts: any = path.split("/");
    params.platform = pathParts.pop();
    pathParts.pop(); // "/run/"
    params.appId = pathParts.pop();

    return params;
}
@jkelvie
Copy link
Member

jkelvie commented Jun 7, 2018

So I understand passing the context. That's easy :-)

With regard to sorting out the "context 🔥" - I do understand better the issue based on your description. Thank you for that.

What do you think would be the best way to address this? Here is my stab at it:
Add a flag to virtualAlexa called "simulateGateway" that if set does the following:
I guess the ideal solution, we would have a flag like “simulateGateway” that:

  1. Pushes the event JSON onto an event.body field
  2. Looks for the Alexa JSON response on a body field of the response
    So rather than assuming it is the root element, it would take it from the "body" element of the response provided to context.done()

Am I on the right track?

@OpenDog
Copy link
Author

OpenDog commented Jun 7, 2018

Yes exactly. simulateGateway flag is great. BTW in the response there are more attributes you can use in BST proxy: http response code and return headers.

Maybe another flag to simulate the lambda event and context when the lambda is hooked directly to an Alexa skill (i.e. the event is a vanilla Alexa event - no API GW). I'm not sure what the format is. Or is it the same as the current BST format (event = payload)?

@OpenDog
Copy link
Author

OpenDog commented Jun 7, 2018

I forgot. for the simulateGateway you also need to push the request info (path, queryString) onto event. And the body is the JSON string not the object (like in BST).

@dgreene1
Copy link

So I understand passing the context. That's easy :-)

I haven't read all of the documentation yet (forgive me please), but can you share how to pass in the context?

Btw, my team and I are loving Bespoken!

@jperata
Copy link
Contributor

jperata commented Nov 14, 2018

@dgreene1 We meant adding the changes to pass the lambda context. Which we haven't done yet.

But do you mean the same, or do you mean the context used when an alexa session have more than one interaction. If it's the second, the instance keeps the context alive by default

@dgreene1
Copy link

Thank you for the quick response @jperata. :) I mean the first. I would like an option to send an event like you can do in the lamda UI tester that Amazon provides.

Additionally, I am interested in what @OpenDog was asking for with the gateway.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants