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

feature: WEOS-1384 As a developer I should be able to specify a Go template I want to use to render a response #149

Merged
merged 4 commits into from
Mar 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions controller/rest/fixtures/templates/index1.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{{ define "title" }}About{{ end }}


{{ define "content" }}<p>About us page now</p>{{ end }}
4 changes: 4 additions & 0 deletions controller/rest/fixtures/templates/index12.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{{ define "title" }}About{{ end }}


{{ define "content" }}<p>About us page now</p>{{ end }}
26 changes: 2 additions & 24 deletions controllers/rest/controller_standard.go
Original file line number Diff line number Diff line change
Expand Up @@ -857,37 +857,15 @@ func DefaultResponseMiddleware(api *RESTAPI, projection projections.Projection,
} else if fileName != "" {
ctxt.File(fileName)
} else if len(templates) != 0 {
parameters := map[string]interface{}{}
for _, param := range operation.Parameters { //get parameter name to get from the context and add to map
name := param.Value.Name
if ctx.Value(name) == nil {
if tcontextName, ok := param.Value.ExtensionProps.Extensions[AliasExtension]; ok {
err := json.Unmarshal(tcontextName.(json.RawMessage), &name)
if err != nil {
api.e.Logger.Debugf("unexpected error finding parameter alias %s: %s ", name, err)
return NewControllerError(fmt.Sprintf("unexpected error finding parameter alias %s: %s ", name, err), nil, http.StatusBadRequest)

}
} else {
api.e.Logger.Debugf("unexpected error finding parameter alias %s ", name)
return NewControllerError(fmt.Sprintf("unexpected error finding parameter alias %s ", name), nil, http.StatusBadRequest)
}
}
if ctx.Value(name) == nil {
api.e.Logger.Debugf("unexpected error parameter %s not found ", name)
return NewControllerError(fmt.Sprintf("unexpected error parameter %s not found ", name), nil, http.StatusBadRequest)

}
parameters[name] = ctx.Value(name)
}
contextValues := ReturnContextValues(ctx)
t := template.New(path1.Base(templates[0]))
t, err := t.ParseFiles(templates...)
if err != nil {
api.e.Logger.Debugf("unexpected error %s ", err)
return NewControllerError(fmt.Sprintf("unexpected error %s ", err), err, http.StatusInternalServerError)

}
err = t.Execute(ctxt.Response().Writer, parameters)
err = t.Execute(ctxt.Response().Writer, contextValues)
if err != nil {
api.e.Logger.Debugf("unexpected error %s ", err)
return NewControllerError(fmt.Sprintf("unexpected error %s ", err), err, http.StatusInternalServerError)
Expand Down
137 changes: 21 additions & 116 deletions controllers/rest/controller_standard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2268,7 +2268,7 @@ func TestStandardControllers_RenderTemplates(t *testing.T) {
t.Errorf("expected results to be %s got %s", expectResp, string(results))
}
})
t.Run("invalid go templates ", func(t *testing.T) {
t.Run("invalid endpoint since file doesnt exist ", func(t *testing.T) {
path := swagger.Paths.Find("/badtemplates")
resp := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/badtemplates", nil)
Expand All @@ -2282,18 +2282,29 @@ func TestStandardControllers_RenderTemplates(t *testing.T) {
response := resp.Result()
defer response.Body.Close()

expectResp := "<html>\n <body>\n <h1></h1>\n\n\n \n </body>\n</html>"

if response.StatusCode != http.StatusOK {
if response.StatusCode != http.StatusInternalServerError {
t.Errorf("expected response code to be %d, got %d", http.StatusOK, response.StatusCode)
}
results, err := io.ReadAll(response.Body)
if err != nil {
t.Errorf("unexpected error reading the response body: %s", err)
}
if !strings.Contains(expectResp, string(results)) {
t.Errorf("expected results to be %s got %s", expectResp, string(results))

})
t.Run("sending invalid template", func(t *testing.T) {
path := swagger.Paths.Find("/badtemplates1")
resp := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/badtemplates1", nil)
mw := rest.Context(restAPI, nil, nil, nil, nil, path, path.Get)
cResponseMiddleware := rest.ContentTypeResponseMiddleware(restAPI, nil, nil, nil, nil, path, path.Get)
defaultMiddleware := rest.DefaultResponseMiddleware(restAPI, nil, nil, nil, nil, path, path.Get)
controller := rest.DefaultResponseController(restAPI, nil, nil, nil, nil)
e.GET("/badtemplates1", controller, mw, cResponseMiddleware, defaultMiddleware)
e.ServeHTTP(resp, req)

response := resp.Result()
defer response.Body.Close()

if response.StatusCode != http.StatusInternalServerError {
t.Errorf("expected response code to be %d, got %d", http.StatusOK, response.StatusCode)
}

})
}

Expand Down Expand Up @@ -2342,109 +2353,3 @@ func TestAPI_ContextZapLogger(t *testing.T) {
t.Errorf("expected the status code to be %d, got %d", 200, response.StatusCode)
}
}

func TestStandardControllers_RenderTemplates(t *testing.T) {
content, err := ioutil.ReadFile("./fixtures/blog.yaml")
if err != nil {
t.Fatalf("error loading api specification '%s'", err)
}
//change the $ref to another marker so that it doesn't get considered an environment variable WECON-1
tempFile := strings.ReplaceAll(string(content), "$ref", "__ref__")
//replace environment variables in file
tempFile = os.ExpandEnv(string(tempFile))
tempFile = strings.ReplaceAll(string(tempFile), "__ref__", "$ref")
//update path so that the open api way of specifying url parameters is change to the echo style of url parameters
re := regexp.MustCompile(`\{([a-zA-Z0-9\-_]+?)\}`)
tempFile = re.ReplaceAllString(tempFile, `:$1`)
content = []byte(tempFile)
loader := openapi3.NewSwaggerLoader()
swagger, err := loader.LoadSwaggerFromData(content)
if err != nil {
t.Fatalf("error loading api specification '%s'", err)
}
//instantiate api
e := echo.New()
restAPI := &rest.RESTAPI{}
restAPI.SetEchoInstance(e)

t.Run("specify html response with multiple files ", func(t *testing.T) {
path := swagger.Paths.Find("/multipletemplates")
resp := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/multipletemplates", nil)
mw := rest.Context(restAPI, nil, nil, nil, nil, path, path.Get)
cResponseMiddleware := rest.ContentTypeResponseMiddleware(restAPI, nil, nil, nil, nil, path, path.Get)
defaultMiddleware := rest.DefaultResponseMiddleware(restAPI, nil, nil, nil, nil, path, path.Get)
controller := rest.DefaultResponseController(restAPI, nil, nil, nil, nil)
e.GET("/multipletemplates", controller, mw, cResponseMiddleware, defaultMiddleware)
e.ServeHTTP(resp, req)

response := resp.Result()
defer response.Body.Close()
expectResp := "<html>\n <body>\n <h1>About</h1>\n\n\n \n<p>About us page now</p>\n\n </body>\n</html>"

if response.StatusCode != http.StatusOK {
t.Errorf("expected response code to be %d, got %d", http.StatusOK, response.StatusCode)
}
results, err := io.ReadAll(response.Body)
if err != nil {
t.Errorf("unexpected error reading the response body: %s", err)
}
if !strings.Contains(expectResp, string(results)) {
t.Errorf("expected results to be %s got %s", expectResp, string(results))
}
})
t.Run("rendering go template with data in the context ", func(t *testing.T) {
path := swagger.Paths.Find("/templates")
resp := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/templates?title=Test&content=LoremIpsum", nil)
mw := rest.Context(restAPI, nil, nil, nil, nil, path, path.Get)
cResponseMiddleware := rest.ContentTypeResponseMiddleware(restAPI, nil, nil, nil, nil, path, path.Get)
defaultMiddleware := rest.DefaultResponseMiddleware(restAPI, nil, nil, nil, nil, path, path.Get)
controller := rest.DefaultResponseController(restAPI, nil, nil, nil, nil)
e.GET("/templates", controller, mw, cResponseMiddleware, defaultMiddleware)
e.ServeHTTP(resp, req)

response := resp.Result()
defer response.Body.Close()
expectResp := "<html>\n <body>\n <h1>Test</h1>\n\n\n LoremIpsum\n </body>\n</html>"

if response.StatusCode != http.StatusOK {
t.Errorf("expected response code to be %d, got %d", http.StatusOK, response.StatusCode)
}
results, err := io.ReadAll(response.Body)
if err != nil {
t.Errorf("unexpected error reading the response body: %s", err)
}
if !strings.Contains(expectResp, string(results)) {
t.Errorf("expected results to be %s got %s", expectResp, string(results))
}
})
t.Run("invalid go templates ", func(t *testing.T) {
path := swagger.Paths.Find("/badtemplates")
resp := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/badtemplates", nil)
mw := rest.Context(restAPI, nil, nil, nil, nil, path, path.Get)
cResponseMiddleware := rest.ContentTypeResponseMiddleware(restAPI, nil, nil, nil, nil, path, path.Get)
defaultMiddleware := rest.DefaultResponseMiddleware(restAPI, nil, nil, nil, nil, path, path.Get)
controller := rest.DefaultResponseController(restAPI, nil, nil, nil, nil)
e.GET("/badtemplates", controller, mw, cResponseMiddleware, defaultMiddleware)
e.ServeHTTP(resp, req)

response := resp.Result()
defer response.Body.Close()

expectResp := "<html>\n <body>\n <h1></h1>\n\n\n \n </body>\n</html>"

if response.StatusCode != http.StatusOK {
t.Errorf("expected response code to be %d, got %d", http.StatusOK, response.StatusCode)
}
results, err := io.ReadAll(response.Body)
if err != nil {
t.Errorf("unexpected error reading the response body: %s", err)
}
if !strings.Contains(expectResp, string(results)) {
t.Errorf("expected results to be %s got %s", expectResp, string(results))
}
})

}
17 changes: 14 additions & 3 deletions controllers/rest/fixtures/blog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,19 @@ paths:
200:
description: Homepage
x-templates:
- ./fixtures/templates/base1.html
- ./fixtures/templates/base12.html
404:
description: File not found
402:
description: User not authenticated
/badtemplates1:
get:
operationId: getAsset
responses:
200:
description: Homepage
x-templates:
- ./fixtures/templates/badTem.html
404:
description: File not found
402:
Expand Down Expand Up @@ -340,8 +352,7 @@ paths:
_filters:
- field: status
operator: eq
value:
- Active
value: Active
- field: lastUpdated
operator: between
values:
Expand Down
8 changes: 8 additions & 0 deletions controllers/rest/fixtures/templates/badTem.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

<hea>

<title> {{ template "content123wh" }}</title>

<body>

</body>
8 changes: 8 additions & 0 deletions controllers/rest/fixtures/templates/base2.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<html>
<body>
<h1>{{ template "title" }}</h1>


{{ template "content" }}
</body>
</html>
8 changes: 8 additions & 0 deletions controllers/rest/fixtures/templates/base3.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<html>
<body>
<h1>{{.title}}</h1>


{{.content}}
</body>
</html>
4 changes: 4 additions & 0 deletions controllers/rest/fixtures/templates/index1.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{{ define "title" }}About{{ end }}


{{ define "content" }}<p>About us page now</p>{{ end }}
11 changes: 11 additions & 0 deletions controllers/rest/operation_initializers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -382,3 +382,14 @@ func TestRouteInitializer(t *testing.T) {
}
})
}

func TestGettersForOperationFunctions(t *testing.T) {
ctx := context.Background()

t.Run("getting schema builder sending empty context", func(t *testing.T) {
builders := rest.GetSchemaBuilders(ctx)
if builders == nil {
t.Errorf("unexpected error expected map of builders to be returned got nil")
}
})
}
31 changes: 31 additions & 0 deletions controllers/rest/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ package rest
import (
"bufio"
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"reflect"
"strconv"
"strings"
"unsafe"

"github.com/labstack/echo/v4"
"github.com/labstack/gommon/log"
Expand Down Expand Up @@ -343,3 +346,31 @@ func JSONMarshal(t interface{}) ([]byte, error) {
result = bytes.ReplaceAll(result, []byte(`\t`), []byte(""))
return result, err
}

//ReturnContextValues pulls out all the values stored in the context and adds it to a map to be returned
func ReturnContextValues(ctxt interface{}) map[interface{}]interface{} {
contextValues := map[interface{}]interface{}{}
contextKeys := []interface{}{}
contextV := reflect.ValueOf(ctxt).Elem()
contextK := reflect.TypeOf(ctxt).Elem()
if contextK.Kind() == reflect.Struct {
for i := 0; i < contextV.NumField(); i++ {
reflectValue := contextV.Field(i)
reflectValue = reflect.NewAt(reflectValue.Type(), unsafe.Pointer(reflectValue.UnsafeAddr())).Elem()
reflectField := contextK.Field(i)

if reflectField.Name == "Context" {
contextVals := ReturnContextValues(reflectValue.Interface())
for key, value := range contextVals {
contextValues[key] = value
}
} else if reflectField.Name == "key" {
contextKeys = append(contextKeys, reflectValue.Interface())
}
}
}
for _, cKeys := range contextKeys {
contextValues[cKeys] = ctxt.(context.Context).Value(cKeys)
}
return contextValues
}
3 changes: 1 addition & 2 deletions end2end_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
weosContext "github.com/wepala/weos/context"
"io"
"mime/multipart"
"net/http"
"net/http/httptest"
Expand Down Expand Up @@ -1574,7 +1574,6 @@ func theResponseBodyShouldBe(expectResp *godog.DocString) error {
if err != nil {
return err
}

if !strings.Contains(expectResp.Content, string(results)) {
if bytes.Compare(results, exp) != 0 {
return fmt.Errorf("expected response to be %s, got %s", results, exp)
Expand Down
Loading