Skip to content

Commit

Permalink
Merge branch '2021.9.x-feature-apigw-postfilters' into 2021.9.x
Browse files Browse the repository at this point in the history
  • Loading branch information
petergrlica committed Dec 6, 2021
2 parents a2091db + 96a1c1d commit b0590d2
Show file tree
Hide file tree
Showing 10 changed files with 762 additions and 22 deletions.
129 changes: 127 additions & 2 deletions pkg/apigw/filter/postfilter.go
Expand Up @@ -7,11 +7,18 @@ import (
"net/http"
"net/url"

"github.com/cortezaproject/corteza-server/automation/service"
atypes "github.com/cortezaproject/corteza-server/automation/types"
agctx "github.com/cortezaproject/corteza-server/pkg/apigw/ctx"
"github.com/cortezaproject/corteza-server/pkg/apigw/types"
pe "github.com/cortezaproject/corteza-server/pkg/errors"
errors "github.com/cortezaproject/corteza-server/pkg/errors"
"github.com/cortezaproject/corteza-server/pkg/expr"
)

type (
typesRegistry interface {
Type(ref string) expr.Type
}
redirection struct {
types.FilterMeta

Expand All @@ -33,6 +40,17 @@ type (
}
}

jsonResponse struct {
types.FilterMeta

reg typesRegistry

params struct {
Exp *atypes.Expr
Evaluable expr.Evaluable
}
}

defaultJsonResponse struct {
types.FilterMeta
}
Expand Down Expand Up @@ -80,6 +98,10 @@ func (h redirection) Weight() int {
func (h *redirection) Merge(params []byte) (types.Handler, error) {
err := json.NewDecoder(bytes.NewBuffer(params)).Decode(&h.params)

if err != nil {
return h, err
}

loc, err := url.ParseRequestURI(h.params.Location)

if err != nil {
Expand Down Expand Up @@ -135,7 +157,7 @@ func (j defaultJsonResponse) Handler() types.HandlerFunc {
rw.WriteHeader(http.StatusAccepted)

if _, err := rw.Write([]byte(`{}`)); err != nil {
return pe.Internal("could not write to body: %v", err)
return errors.Internal("could not write to body: %v", err)
}

return nil
Expand All @@ -150,3 +172,106 @@ func checkStatus(typ string, status int) bool {
return true
}
}

func NewJsonResponse(reg typesRegistry) (e *jsonResponse) {
e = &jsonResponse{}

e.Name = "jsonResponse"
e.Label = "JSON response"
e.Kind = types.PostFilter

e.Args = []*types.FilterMetaArg{
{
Type: "input",
Label: "input",
Options: map[string]interface{}{},
},
}

e.reg = reg

return
}

func (j jsonResponse) New() types.Handler {
return NewJsonResponse(service.Registry())
}

func (j jsonResponse) String() string {
return fmt.Sprintf("apigw filter %s (%s)", j.Name, j.Label)
}

func (j jsonResponse) Meta() types.FilterMeta {
return j.FilterMeta
}

func (j *jsonResponse) Merge(params []byte) (h types.Handler, err error) {
var (
parser = expr.NewParser()
)

err = json.NewDecoder(bytes.NewBuffer(params)).Decode(&j.params.Exp)

if err != nil {
return j, err
}

j.params.Evaluable, err = parser.Parse(j.params.Exp.Expr)

if err != nil {
return j, fmt.Errorf("could not evaluate expression: %s", err)
}

j.params.Exp.SetEval(j.params.Evaluable)

return j, err
}

func (j jsonResponse) Handler() types.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) (err error) {
var (
ctx = r.Context()
scope = agctx.ScopeFromContext(ctx)

evald interface{}
)

in, err := expr.NewVars(scope.Dict())

if err != nil {
return errors.Internal("could not validate request data: %v", err)
}

// set type to the registered expression from
// any of the already registered types
j.params.Exp.SetType(func(name string) (e expr.Type, err error) {
if name == "" {
name = "Any"
}

if typ := j.reg.Type(name); typ != nil {
return typ, nil
} else {
return nil, errors.Internal("unknown or unregistered type %s", name)
}
})

evald, err = j.params.Exp.Eval(ctx, in)

if err != nil {
return
}

rw.Header().Add("Content-Type", "application/json")

switch v := evald.(type) {
case string:
rw.Write([]byte(v))
default:
e := json.NewEncoder(rw)
err = e.Encode(v)
}

return
}
}
74 changes: 74 additions & 0 deletions pkg/apigw/filter/postfilter_test.go
@@ -1,11 +1,16 @@
package filter

import (
"context"
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/cortezaproject/corteza-server/automation/service"
agctx "github.com/cortezaproject/corteza-server/pkg/apigw/ctx"
"github.com/cortezaproject/corteza-server/pkg/apigw/types"
"github.com/cortezaproject/corteza-server/pkg/expr"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -86,6 +91,75 @@ func Test_redirection(t *testing.T) {
}
}

func Test_jsonResponse(t *testing.T) {
type (
tf struct {
name string
expr string
err string
exp string
scope interface{}
}

aux struct {
Name string `json:"name"`
Surname string `json:"surname"`
}
)

var (
tcc = []tf{
{
name: "Any response as JSON",
expr: `{"expr": "records", "type": "KV"}`,
scope: expr.Must(expr.Any{}.Cast([]float64{3.14, 42.690})),
exp: `[3.14,42.69]`,
},
{
name: "KV response as JSON",
expr: `{"expr": "records", "type": "KV"}`,
scope: map[string]string{"foo": "bar", "baz": "bzz"},
exp: `{"baz":"bzz","foo":"bar"}`,
},
{
name: "struct array response as JSON",
expr: `{"expr": "toJSON(records)", "type": "String"}`,
scope: []aux{{"First", "Last"}, {"Foo", "bar"}},
exp: `[{"name":"First","surname":"Last"},{"name":"Foo","surname":"bar"}]`,
},
}
)

for _, tc := range tcc {
t.Run(tc.name, func(t *testing.T) {
var (
req = require.New(t)
r = httptest.NewRequest(http.MethodGet, "/foo", http.NoBody)
rc = httptest.NewRecorder()
scope = &types.Scp{"records": tc.scope}
)

r = r.WithContext(agctx.ScopeToContext(context.Background(), scope))

h := getHandler(NewJsonResponse(service.Registry()))
h, err := h.Merge([]byte(tc.expr))

req.NoError(err)

hn := h.Handler()
err = hn(rc, r)

if tc.err != "" {
req.EqualError(err, tc.err)
return
}

req.NoError(err)
req.Equal(tc.exp, strings.TrimSuffix(rc.Body.String(), "\n"))
})
}
}

// hackity hack
func getHandler(h types.Handler) types.Handler {
return h
Expand Down
12 changes: 6 additions & 6 deletions pkg/apigw/filter/processer.go
Expand Up @@ -91,16 +91,17 @@ func (h workflow) Handler() types.HandlerFunc {
scope = agctx.ScopeFromContext(ctx)
)

payload, err := scope.Get("payload")
// original request with body as io.Reader
// read-only
ar, err := scope.Get("request")

if err != nil {
return pe.Internal("could not get payload: %v", err)
return err
}

// setup scope for workflow
vv := map[string]interface{}{
"payload": payload,
"request": r,
"request": ar,
}

// get the request data and put it into vars
Expand Down Expand Up @@ -152,8 +153,7 @@ func (h workflow) Handler() types.HandlerFunc {

scope = ss

scope.Set("request", r)
scope.Set("payload", payload)
scope.Set("request", ar)

return nil
}
Expand Down
1 change: 1 addition & 0 deletions pkg/apigw/registry/registry.go
Expand Up @@ -69,6 +69,7 @@ func (r *Registry) Preload() {

// postfilters
r.Add("redirection", filter.NewRedirection())
r.Add("jsonResponse", filter.NewJsonResponse(service.Registry()))
r.Add("defaultJsonResponse", filter.NewDefaultJsonResponse())
}

Expand Down
22 changes: 8 additions & 14 deletions pkg/apigw/route.go
@@ -1,18 +1,15 @@
package apigw

import (
"bytes"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/http/httputil"
"time"

actx "github.com/cortezaproject/corteza-server/pkg/apigw/ctx"
"github.com/cortezaproject/corteza-server/pkg/apigw/types"
"github.com/cortezaproject/corteza-server/pkg/auth"
"github.com/cortezaproject/corteza-server/pkg/options"
"github.com/cortezaproject/corteza-server/system/automation"
"go.uber.org/zap"
)

Expand Down Expand Up @@ -45,19 +42,16 @@ func (r route) ServeHTTP(w http.ResponseWriter, req *http.Request) {

r.log.Debug("started serving route")

b, _ := io.ReadAll(req.Body)
body := string(b)
// create a new automation HttpRequest
ar, err := automation.NewHttpRequest(req)

// write again
req.Body = ioutil.NopCloser(bytes.NewBuffer(b))
if err != nil {
r.log.Error("could not prepare a request holder", zap.Error(err))
return
}

scope.Set("opts", r.opts)
scope.Set("payload", body)

if r.opts.LogEnabled {
o, _ := httputil.DumpRequest(req, r.opts.LogRequestBody)
r.log.Debug("incoming request", zap.Any("request", string(o)))
}
scope.Set("request", ar)

req = req.WithContext(actx.ScopeToContext(ctx, &scope))

Expand Down
9 changes: 9 additions & 0 deletions pkg/apigw/types/scope.go
Expand Up @@ -65,6 +65,10 @@ func (s Scp) Get(k string) (v interface{}, err error) {
return
}

func (s *Scp) Dict() map[string]interface{} {
return *s
}

func (s *Scp) Filter(fn func(k string, v interface{}) bool) *Scp {
ss := Scp{}

Expand All @@ -78,3 +82,8 @@ func (s *Scp) Filter(fn func(k string, v interface{}) bool) *Scp {

return &ss
}

func (s Scp) Has(k string) (has bool) {
_, has = s[k]
return
}

0 comments on commit b0590d2

Please sign in to comment.