Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge branch 'develop' into event-object-omitempty-issue
  • Loading branch information
PaulMaddox committed Dec 7, 2017
2 parents 515050b + 739c48e commit 998f9dc
Show file tree
Hide file tree
Showing 126 changed files with 64,624 additions and 43 deletions.
167 changes: 161 additions & 6 deletions router/api.go
Expand Up @@ -2,10 +2,26 @@ package router

import (
"fmt"
"log"
"io/ioutil"
"encoding/json"

"github.com/awslabs/goformation/cloudformation"
"github.com/go-openapi/spec"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"strings"
)

const apiGatewayIntegrationExtension = "x-amazon-apigateway-integration"
const apiGatewayAnyMethodExtension = "x-amazon-apigateway-any-method"

// temporary object. This is just used to marshal and unmarshal the any method
// API Gateway swagger extension
type ApiGatewayAnyMethod struct {
IntegrationSettings interface{} `json:"x-amazon-apigateway-integration"`
}

// AWSServerlessApi wraps GoFormation's AWS::Serverless::Api definition
// and adds some convenience methods for extracting the ServerlessRouterMount's
// from the swagger defintion etc.
Expand All @@ -16,7 +32,121 @@ type AWSServerlessApi struct {
// Mounts fetches an array of the ServerlessRouterMount's for this API.
// These contain the path, method and handler function for each mount point.
func (api *AWSServerlessApi) Mounts() ([]*ServerlessRouterMount, error) {
return nil, nil
jsonDefinition, err := api.Swagger();

if err != nil {
// this is our own error so we return it directly
return nil, err
}
swagger := spec.Swagger{}
err = swagger.UnmarshalJSON(jsonDefinition)

if err != nil {
return nil, fmt.Errorf("Cannot parse Swagger definition: %s", err.Error())
}

mounts := []*ServerlessRouterMount{}

log.Printf("Reading paths from Swagger")

for path, pathItem := range swagger.Paths.Paths {
// temporary tracking of mounted methods for the current path. Used to
// mount all non-existing methods for the any extension. This is because
// the err from JSONLookup did not work as expected
mappedMethods := map[string]bool{}

for _, method := range HttpMethods {

if operationIface, err := pathItem.JSONLookup(strings.ToLower(method)); err == nil {
operation := spec.Operation{}

operationJson, err := json.Marshal(operationIface)
if err != nil {
return nil, fmt.Errorf("Could not parse %s operation: %s", method, err.Error())
}
operation.UnmarshalJSON(operationJson)
if operation.Extensions[apiGatewayIntegrationExtension] == nil {
continue
}

integration, _ := operation.Extensions[apiGatewayIntegrationExtension]
mounts = append(mounts, api.createMount(
path,
strings.ToLower(method),
api.parseIntegrationSettings(integration)))
mappedMethods[method] = true
}
}

anyMethod, available := pathItem.Extensions[apiGatewayAnyMethodExtension]
if available {
// any method to json then unmarshal to temporary object
anyMethodJson, err := json.Marshal(anyMethod)
if err != nil {
return nil, fmt.Errorf("Could not marshal any method object to json")
}

anyMethodObject := ApiGatewayAnyMethod{}
err = json.Unmarshal(anyMethodJson, &anyMethodObject)

if err != nil {
return nil, fmt.Errorf("Could not unmarshal any method josn to object model")
}

for _, method := range HttpMethods {
if _, ok := mappedMethods[method]; !ok {
mounts = append(mounts, api.createMount(
path,
strings.ToLower(method),
api.parseIntegrationSettings(anyMethodObject.IntegrationSettings)))
}
}
}
}

return mounts, nil
}

// parses a byte[] for the API Gateway inetegration extension from a method and return
// the object representation
func (api *AWSServerlessApi) parseIntegrationSettings(integrationData interface{}) *ApiGatewayIntegration {
integrationJson, err := json.Marshal(integrationData);
if err != nil {
log.Printf("Could not parse integration data to json")
return nil
}

integration := ApiGatewayIntegration{}
err = json.Unmarshal(integrationJson, &integration)

if err != nil {
log.Printf("Could not unmarshal integration data to ApiGatewayIntegration model")
return nil
}

return &integration
}

func (api *AWSServerlessApi) createMount(path string, verb string, integration *ApiGatewayIntegration) *(ServerlessRouterMount) {
newMount := &ServerlessRouterMount{
Name: path,
Path: path,
Method: verb,
}

if integration == nil {
log.Printf("No integration defined for method")
return newMount
}

functionName, err := integration.GetFunctionArn()

if err != nil {
log.Printf("Could not extract Lambda function ARN: %s", err.Error())
}
newMount.IntegrationArn = functionName

return newMount;
}

// Swagger gets the swagger definition for the API.
Expand Down Expand Up @@ -59,17 +189,42 @@ func (api *AWSServerlessApi) Swagger() ([]byte, error) {
}

func (api *AWSServerlessApi) getSwaggerFromURI(uri string) ([]byte, error) {
return nil, nil
data, err := ioutil.ReadFile(uri)
if err != nil {
return nil, fmt.Errorf("Cannot read local Swagger definition (%s): %s", uri, err.Error())
}
return data, nil
}

func (api *AWSServerlessApi) getSwaggerFromS3Location(cloudformation.AWSServerlessApi_S3Location) ([]byte, error) {
return nil, nil
func (api *AWSServerlessApi) getSwaggerFromS3Location(loc cloudformation.AWSServerlessApi_S3Location) ([]byte, error) {
sess := session.Must(session.NewSession())
client := s3.New(sess)

objectVersion := string(loc.Version)
s3Input := s3.GetObjectInput{
Bucket: &loc.Bucket,
Key: &loc.Key,
VersionId: &objectVersion,
}

object, err := client.GetObject(&s3Input)

if err != nil {
return nil, fmt.Errorf("Error while fetching Swagger template from S3: %s\n%s", loc.Bucket + loc.Key, err.Error())
}

body, err := ioutil.ReadAll(object.Body)

if err != nil {
return nil, fmt.Errorf("Cannot read s3 Swagger boject body: %s", err.Error())
}
return body, nil
}

func (api *AWSServerlessApi) getSwaggerFromString(input string) ([]byte, error) {
return nil, nil
return []byte(input), nil
}

func (api *AWSServerlessApi) getSwaggerFromMap(input map[string]interface{}) ([]byte, error) {
return nil, nil
return json.Marshal(input)
}
81 changes: 81 additions & 0 deletions router/api_test.go
@@ -0,0 +1,81 @@
package router_test

import (
"github.com/awslabs/aws-sam-local/router"
"github.com/awslabs/goformation/cloudformation"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

func getApiResourceFromTemplate(path string) (*router.AWSServerlessApi) {
templateUri := &path
apiResource := &router.AWSServerlessApi{
AWSServerlessApi: &cloudformation.AWSServerlessApi{
DefinitionUri: &cloudformation.AWSServerlessApi_DefinitionUri{
String: templateUri,
},
},
}
return apiResource
}

var _ = Describe("Api", func() {

Context("Load local Swagger definitions", func() {
/*apiResource := router.AWSServerlessApi{}
templateUri := new(string)
*templateUri = "test/templates/open-api/pet-store-simple.json"
definitionUri := new(cloudformation.AWSServerlessApi_StringOrS3Location)
definitionUri.String = templateUri
apiResource.DefinitionUri = definitionUri*/
It("Succesfully loads the basic template", func() {
apiResource := getApiResourceFromTemplate("../test/templates/open-api/pet-store-simple.json")

mounts, err := apiResource.Mounts()

Expect(err).Should(BeNil())
Expect(mounts).ShouldNot(BeNil())

Expect(mounts).ShouldNot(BeEmpty())
Expect(len(mounts)).Should(BeIdenticalTo(4))
})

It("Succesfully reads integration definition", func() {
apiResource := getApiResourceFromTemplate("../test/templates/open-api/pet-store-simple.json")

mounts, err := apiResource.Mounts()

Expect(err).Should(BeNil())
Expect(mounts).ShouldNot(BeNil())

for _, mount := range mounts {
if mount.Method == "get" && mount.Path == "/pets" {
Expect(mount.IntegrationArn.Arn).Should(BeIdenticalTo("arn:aws:lambda:us-west-2:123456789012:function:Calc"))
}
}
})

It("Loads the proxy template", func() {
apiResource := getApiResourceFromTemplate("../test/templates/open-api/pet-store-proxy.json")

mounts, err := apiResource.Mounts()

Expect(err).Should(BeNil())
Expect(mounts).ShouldNot(BeNil())
// we expect 9 here because the any method should generate all 7
Expect(len(mounts)).To(BeIdenticalTo(9))

for _, mount := range mounts {
if mount.Method == "post" && mount.Path == "/pets/{proxy+}" {
Expect(mount.IntegrationArn.Arn).Should(ContainSubstring("AnyMethod"))
}
if mount.Method == "delete" && mount.Path == "/pets/{proxy+}" {
Expect(mount.IntegrationArn.Arn).Should(ContainSubstring("Calc"))
}
}
})

})
})
4 changes: 0 additions & 4 deletions router/function.go
Expand Up @@ -2,7 +2,6 @@ package router

import (
"net/http"
"regexp"

"github.com/awslabs/goformation/cloudformation"
)
Expand All @@ -15,8 +14,6 @@ type AWSServerlessFunction struct {
handler http.HandlerFunc
}

var catchAllPathVar = regexp.MustCompile(`\{([^}/]+)\+\}$`)

// Mounts fetches an array of the ServerlessRouterMount's for this API.
// These contain the path, method and handler function for each mount point.
func (f *AWSServerlessFunction) Mounts() ([]*ServerlessRouterMount, error) {
Expand All @@ -32,7 +29,6 @@ func (f *AWSServerlessFunction) Mounts() ([]*ServerlessRouterMount, error) {
Method: event.Properties.ApiEvent.Method,
Handler: f.handler,
Function: f,
UsePrefix: catchAllPathVar.MatchString(event.Properties.ApiEvent.Path),
})
}
}
Expand Down

0 comments on commit 998f9dc

Please sign in to comment.