Fixed read request body, added read request body wf function, added more tests

This commit is contained in:
Peter Grlica
2022-02-17 15:00:16 +01:00
parent 78237d3b5b
commit e822ad7c06
24 changed files with 969 additions and 793 deletions
+109
View File
@@ -0,0 +1,109 @@
package automation
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
// automation/automation/apigw_body_handler.yaml
import (
"context"
atypes "github.com/cortezaproject/corteza-server/automation/types"
"github.com/cortezaproject/corteza-server/pkg/expr"
"github.com/cortezaproject/corteza-server/pkg/http"
"github.com/cortezaproject/corteza-server/pkg/wfexec"
)
var _ wfexec.ExecResponse
type (
apigwBodyHandlerRegistry interface {
AddFunctions(ff ...*atypes.Function)
Type(ref string) expr.Type
}
)
func (h apigwBodyHandler) register() {
h.reg.AddFunctions(
h.Read(),
)
}
type (
apigwBodyReadArgs struct {
hasRequest bool
Request *http.Request
}
apigwBodyReadResults struct {
Body string
}
)
// Read function Read request body from integration gateway
//
// expects implementation of read function:
// func (h apigwBodyHandler) read(ctx context.Context, args *apigwBodyReadArgs) (results *apigwBodyReadResults, err error) {
// return
// }
func (h apigwBodyHandler) Read() *atypes.Function {
return &atypes.Function{
Ref: "apigwBodyRead",
Kind: "function",
Labels: map[string]string(nil),
Meta: &atypes.FunctionMeta{
Short: "Read request body from integration gateway",
},
Parameters: []*atypes.Param{
{
Name: "request",
Types: []string{"HttpRequest"}, Required: true,
},
},
Results: []*atypes.Param{
{
Name: "body",
Types: []string{"String"},
},
},
Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) {
var (
args = &apigwBodyReadArgs{
hasRequest: in.Has("request"),
}
)
if err = in.Decode(args); err != nil {
return
}
var results *apigwBodyReadResults
if results, err = h.read(ctx, args); err != nil {
return
}
out = &expr.Vars{}
{
// converting results.Body (string) to String
var (
tval expr.TypedValue
)
if tval, err = h.reg.Type("String").Cast(results.Body); err != nil {
return
} else if err = expr.Assign(out, "body", tval); err != nil {
return
}
}
return
},
}
}
@@ -0,0 +1,41 @@
package automation
import (
"context"
"fmt"
"io"
)
type (
apigwBodyHandler struct {
reg apigwBodyHandlerRegistry
}
)
func ApigwBodyHandler(reg queueHandlerRegistry) *apigwBodyHandler {
h := &apigwBodyHandler{
reg: reg,
}
h.register()
return h
}
func (h apigwBodyHandler) read(ctx context.Context, args *apigwBodyReadArgs) (res *apigwBodyReadResults, err error) {
res = &apigwBodyReadResults{}
if !args.hasRequest {
err = fmt.Errorf("could not read body, contents missing")
return
}
bb, err := io.ReadAll(args.Request.Body)
if err != nil {
return
}
res.Body = string(bb)
return
}
@@ -0,0 +1,17 @@
name: apigwBody
imports:
- github.com/cortezaproject/corteza-server/pkg/http
functions:
read:
meta:
short: Read request body from integration gateway
params:
request:
required: true
types:
- { wf: HttpRequest, go: '*http.Request' }
results:
body:
wf: String
-578
View File
@@ -11,7 +11,6 @@ package automation
import (
"context"
"fmt"
"github.com/cortezaproject/corteza-server/automation/types"
. "github.com/cortezaproject/corteza-server/pkg/expr"
"sync"
)
@@ -67,580 +66,3 @@ func (t *EmailMessage) Assign(val interface{}) error {
return nil
}
}
// HttpRequest is an expression type, wrapper for *types.HttpRequest type
type HttpRequest struct {
value *types.HttpRequest
mux sync.RWMutex
}
// NewHttpRequest creates new instance of HttpRequest expression type
func NewHttpRequest(val interface{}) (*HttpRequest, error) {
if c, err := CastToHttpRequest(val); err != nil {
return nil, fmt.Errorf("unable to create HttpRequest: %w", err)
} else {
return &HttpRequest{value: c}, nil
}
}
// Get return underlying value on HttpRequest
func (t *HttpRequest) Get() interface{} {
t.mux.RLock()
defer t.mux.RUnlock()
return t.value
}
// GetValue returns underlying value on HttpRequest
func (t *HttpRequest) GetValue() *types.HttpRequest {
t.mux.RLock()
defer t.mux.RUnlock()
return t.value
}
// Type return type name
func (HttpRequest) Type() string { return "HttpRequest" }
// Cast converts value to *types.HttpRequest
func (HttpRequest) Cast(val interface{}) (TypedValue, error) {
return NewHttpRequest(val)
}
// Assign new value to HttpRequest
//
// value is first passed through CastToHttpRequest
func (t *HttpRequest) Assign(val interface{}) error {
if c, err := CastToHttpRequest(val); err != nil {
return err
} else {
t.value = c
return nil
}
}
func (t *HttpRequest) AssignFieldValue(key string, val TypedValue) error {
t.mux.Lock()
defer t.mux.Unlock()
return assignToHttpRequest(t.value, key, val)
}
// SelectGVal implements gval.Selector requirements
//
// It allows gval lib to access HttpRequest's underlying value (*types.HttpRequest)
// and it's fields
//
func (t *HttpRequest) SelectGVal(ctx context.Context, k string) (interface{}, error) {
t.mux.RLock()
defer t.mux.RUnlock()
return httpRequestGValSelector(t.value, k)
}
// Select is field accessor for *types.HttpRequest
//
// Similar to SelectGVal but returns typed values
func (t *HttpRequest) Select(k string) (TypedValue, error) {
t.mux.RLock()
defer t.mux.RUnlock()
return httpRequestTypedValueSelector(t.value, k)
}
func (t *HttpRequest) Has(k string) bool {
t.mux.RLock()
defer t.mux.RUnlock()
switch k {
case "Method":
return true
case "URL":
return true
case "Header":
return true
case "Body":
return true
case "Form":
return true
case "PostForm":
return true
}
return false
}
// httpRequestGValSelector is field accessor for *types.HttpRequest
func httpRequestGValSelector(res *types.HttpRequest, k string) (interface{}, error) {
if res == nil {
return nil, nil
}
switch k {
case "Method":
return res.Method, nil
case "URL":
return res.URL, nil
case "Header":
return res.Header, nil
case "Body":
return res.Body, nil
case "Form":
return res.Form, nil
case "PostForm":
return res.PostForm, nil
}
return nil, fmt.Errorf("unknown field '%s'", k)
}
// httpRequestTypedValueSelector is field accessor for *types.HttpRequest
func httpRequestTypedValueSelector(res *types.HttpRequest, k string) (TypedValue, error) {
if res == nil {
return nil, nil
}
switch k {
case "Method":
return NewString(res.Method)
case "URL":
return NewUrl(res.URL)
case "Header":
return NewKVV(res.Header)
case "Body":
return NewHttpRequestBody(res.Body)
case "Form":
return NewKVV(res.Form)
case "PostForm":
return NewKVV(res.PostForm)
}
return nil, fmt.Errorf("unknown field '%s'", k)
}
// assignToHttpRequest is field value setter for *types.HttpRequest
func assignToHttpRequest(res *types.HttpRequest, k string, val interface{}) error {
switch k {
case "Method":
aux, err := CastToString(val)
if err != nil {
return err
}
res.Method = aux
return nil
case "URL":
aux, err := CastToUrl(val)
if err != nil {
return err
}
res.URL = aux
return nil
case "Header":
aux, err := CastToKVV(val)
if err != nil {
return err
}
res.Header = aux
return nil
case "Body":
aux, err := CastToHttpRequestBody(val)
if err != nil {
return err
}
res.Body = aux
return nil
case "Form":
aux, err := CastToKVV(val)
if err != nil {
return err
}
res.Form = aux
return nil
case "PostForm":
aux, err := CastToKVV(val)
if err != nil {
return err
}
res.PostForm = aux
return nil
}
return fmt.Errorf("unknown field '%s'", k)
}
// HttpRequestBody is an expression type, wrapper for *types.HttpRequestBody type
type HttpRequestBody struct {
value *types.HttpRequestBody
mux sync.RWMutex
}
// NewHttpRequestBody creates new instance of HttpRequestBody expression type
func NewHttpRequestBody(val interface{}) (*HttpRequestBody, error) {
if c, err := CastToHttpRequestBody(val); err != nil {
return nil, fmt.Errorf("unable to create HttpRequestBody: %w", err)
} else {
return &HttpRequestBody{value: c}, nil
}
}
// Get return underlying value on HttpRequestBody
func (t *HttpRequestBody) Get() interface{} {
t.mux.RLock()
defer t.mux.RUnlock()
return t.value
}
// GetValue returns underlying value on HttpRequestBody
func (t *HttpRequestBody) GetValue() *types.HttpRequestBody {
t.mux.RLock()
defer t.mux.RUnlock()
return t.value
}
// Type return type name
func (HttpRequestBody) Type() string { return "HttpRequestBody" }
// Cast converts value to *types.HttpRequestBody
func (HttpRequestBody) Cast(val interface{}) (TypedValue, error) {
return NewHttpRequestBody(val)
}
// Assign new value to HttpRequestBody
//
// value is first passed through CastToHttpRequestBody
func (t *HttpRequestBody) Assign(val interface{}) error {
if c, err := CastToHttpRequestBody(val); err != nil {
return err
} else {
t.value = c
return nil
}
}
func (t *HttpRequestBody) AssignFieldValue(key string, val TypedValue) error {
t.mux.Lock()
defer t.mux.Unlock()
return assignToHttpRequestBody(t.value, key, val)
}
// SelectGVal implements gval.Selector requirements
//
// It allows gval lib to access HttpRequestBody's underlying value (*types.HttpRequestBody)
// and it's fields
//
func (t *HttpRequestBody) SelectGVal(ctx context.Context, k string) (interface{}, error) {
t.mux.RLock()
defer t.mux.RUnlock()
return httpRequestBodyGValSelector(t.value, k)
}
// Select is field accessor for *types.HttpRequestBody
//
// Similar to SelectGVal but returns typed values
func (t *HttpRequestBody) Select(k string) (TypedValue, error) {
t.mux.RLock()
defer t.mux.RUnlock()
return httpRequestBodyTypedValueSelector(t.value, k)
}
func (t *HttpRequestBody) Has(k string) bool {
t.mux.RLock()
defer t.mux.RUnlock()
switch k {
case "Body":
return true
case "Buffer":
return true
}
return false
}
// httpRequestBodyGValSelector is field accessor for *types.HttpRequestBody
func httpRequestBodyGValSelector(res *types.HttpRequestBody, k string) (interface{}, error) {
if res == nil {
return nil, nil
}
switch k {
case "Body":
return res.Body, nil
case "Buffer":
return res.Buffer, nil
}
return nil, fmt.Errorf("unknown field '%s'", k)
}
// httpRequestBodyTypedValueSelector is field accessor for *types.HttpRequestBody
func httpRequestBodyTypedValueSelector(res *types.HttpRequestBody, k string) (TypedValue, error) {
if res == nil {
return nil, nil
}
switch k {
case "Body":
return NewReader(res.Body)
case "Buffer":
return NewBytes(res.Buffer)
}
return nil, fmt.Errorf("unknown field '%s'", k)
}
// assignToHttpRequestBody is field value setter for *types.HttpRequestBody
func assignToHttpRequestBody(res *types.HttpRequestBody, k string, val interface{}) error {
switch k {
case "Body":
aux, err := CastToReader(val)
if err != nil {
return err
}
res.Body = aux
return nil
case "Buffer":
aux, err := CastToBytes(val)
if err != nil {
return err
}
res.Buffer = aux
return nil
}
return fmt.Errorf("unknown field '%s'", k)
}
// Url is an expression type, wrapper for *types.Url type
type Url struct {
value *types.Url
mux sync.RWMutex
}
// NewUrl creates new instance of Url expression type
func NewUrl(val interface{}) (*Url, error) {
if c, err := CastToUrl(val); err != nil {
return nil, fmt.Errorf("unable to create Url: %w", err)
} else {
return &Url{value: c}, nil
}
}
// Get return underlying value on Url
func (t *Url) Get() interface{} {
t.mux.RLock()
defer t.mux.RUnlock()
return t.value
}
// GetValue returns underlying value on Url
func (t *Url) GetValue() *types.Url {
t.mux.RLock()
defer t.mux.RUnlock()
return t.value
}
// Type return type name
func (Url) Type() string { return "Url" }
// Cast converts value to *types.Url
func (Url) Cast(val interface{}) (TypedValue, error) {
return NewUrl(val)
}
// Assign new value to Url
//
// value is first passed through CastToUrl
func (t *Url) Assign(val interface{}) error {
if c, err := CastToUrl(val); err != nil {
return err
} else {
t.value = c
return nil
}
}
func (t *Url) AssignFieldValue(key string, val TypedValue) error {
t.mux.Lock()
defer t.mux.Unlock()
return assignToUrl(t.value, key, val)
}
// SelectGVal implements gval.Selector requirements
//
// It allows gval lib to access Url's underlying value (*types.Url)
// and it's fields
//
func (t *Url) SelectGVal(ctx context.Context, k string) (interface{}, error) {
t.mux.RLock()
defer t.mux.RUnlock()
return urlGValSelector(t.value, k)
}
// Select is field accessor for *types.Url
//
// Similar to SelectGVal but returns typed values
func (t *Url) Select(k string) (TypedValue, error) {
t.mux.RLock()
defer t.mux.RUnlock()
return urlTypedValueSelector(t.value, k)
}
func (t *Url) Has(k string) bool {
t.mux.RLock()
defer t.mux.RUnlock()
switch k {
case "Scheme":
return true
case "Opaque":
return true
case "Host":
return true
case "Path":
return true
case "RawPath":
return true
case "ForceQuery":
return true
case "RawQuery":
return true
case "Fragment":
return true
case "RawFragment":
return true
}
return false
}
// urlGValSelector is field accessor for *types.Url
func urlGValSelector(res *types.Url, k string) (interface{}, error) {
if res == nil {
return nil, nil
}
switch k {
case "Scheme":
return res.Scheme, nil
case "Opaque":
return res.Opaque, nil
case "Host":
return res.Host, nil
case "Path":
return res.Path, nil
case "RawPath":
return res.RawPath, nil
case "ForceQuery":
return res.ForceQuery, nil
case "RawQuery":
return res.RawQuery, nil
case "Fragment":
return res.Fragment, nil
case "RawFragment":
return res.RawFragment, nil
}
return nil, fmt.Errorf("unknown field '%s'", k)
}
// urlTypedValueSelector is field accessor for *types.Url
func urlTypedValueSelector(res *types.Url, k string) (TypedValue, error) {
if res == nil {
return nil, nil
}
switch k {
case "Scheme":
return NewString(res.Scheme)
case "Opaque":
return NewString(res.Opaque)
case "Host":
return NewString(res.Host)
case "Path":
return NewString(res.Path)
case "RawPath":
return NewString(res.RawPath)
case "ForceQuery":
return NewBoolean(res.ForceQuery)
case "RawQuery":
return NewString(res.RawQuery)
case "Fragment":
return NewString(res.Fragment)
case "RawFragment":
return NewString(res.RawFragment)
}
return nil, fmt.Errorf("unknown field '%s'", k)
}
// assignToUrl is field value setter for *types.Url
func assignToUrl(res *types.Url, k string, val interface{}) error {
switch k {
case "Scheme":
aux, err := CastToString(val)
if err != nil {
return err
}
res.Scheme = aux
return nil
case "Opaque":
aux, err := CastToString(val)
if err != nil {
return err
}
res.Opaque = aux
return nil
case "Host":
aux, err := CastToString(val)
if err != nil {
return err
}
res.Host = aux
return nil
case "Path":
aux, err := CastToString(val)
if err != nil {
return err
}
res.Path = aux
return nil
case "RawPath":
aux, err := CastToString(val)
if err != nil {
return err
}
res.RawPath = aux
return nil
case "ForceQuery":
aux, err := CastToBoolean(val)
if err != nil {
return err
}
res.ForceQuery = aux
return nil
case "RawQuery":
aux, err := CastToString(val)
if err != nil {
return err
}
res.RawQuery = aux
return nil
case "Fragment":
aux, err := CastToString(val)
if err != nil {
return err
}
res.Fragment = aux
return nil
case "RawFragment":
aux, err := CastToString(val)
if err != nil {
return err
}
res.RawFragment = aux
return nil
}
return fmt.Errorf("unknown field '%s'", k)
}
+3 -85
View File
@@ -1,15 +1,11 @@
package automation
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"github.com/cortezaproject/corteza-server/automation/types"
atypes "github.com/cortezaproject/corteza-server/pkg/apigw/types"
"github.com/cortezaproject/corteza-server/pkg/expr"
"github.com/cortezaproject/corteza-server/pkg/http"
"gopkg.in/mail.v2"
)
@@ -37,74 +33,6 @@ func CastToEmailMessage(val interface{}) (out *emailMessage, err error) {
}
}
func CastToHttpRequest(val interface{}) (out *types.HttpRequest, err error) {
switch val := val.(type) {
case expr.Iterator:
out = &types.HttpRequest{}
return out, val.Each(func(k string, v expr.TypedValue) error {
return assignToHttpRequest(out, k, v)
})
}
switch val := expr.UntypedValue(val).(type) {
case *http.Request:
rr := &types.HttpRequest{}
assignToHttpRequest(rr, "Method", val.Method)
assignToHttpRequest(rr, "URL", val.URL)
assignToHttpRequest(rr, "Header", val.Header)
assignToHttpRequest(rr, "Body", val.Body)
assignToHttpRequest(rr, "Form", val.Form)
assignToHttpRequest(rr, "PostForm", val.PostForm)
return rr, nil
case *types.HttpRequest:
return val, nil
case nil:
return &types.HttpRequest{}, nil
default:
return &types.HttpRequest{}, fmt.Errorf("unable to cast type %T to %T", val, out)
}
}
func CastToHttpRequestBody(val interface{}) (out *types.HttpRequestBody, err error) {
switch val := val.(type) {
case io.ReadCloser:
rr := &types.HttpRequestBody{}
return rr, assignToHttpRequestBody(rr, "Body", val)
}
switch val := expr.UntypedValue(val).(type) {
case *io.ReadCloser:
rr := &types.HttpRequestBody{}
return rr, assignToHttpRequestBody(rr, "Body", val)
case *types.HttpRequestBody:
return val, nil
case nil:
return &types.HttpRequestBody{}, nil
default:
return &types.HttpRequestBody{}, fmt.Errorf("unable to cast type %T to %T", val, out)
}
}
func CastToUrl(val interface{}) (out *types.Url, err error) {
switch val := expr.UntypedValue(val).(type) {
case *url.URL:
u := &types.Url{}
m, _ := json.Marshal(val)
_ = json.Unmarshal(m, u)
return u, nil
case *types.Url:
return val, nil
case nil:
return &types.Url{}, nil
default:
return &types.Url{}, fmt.Errorf("unable to cast type %T to %T", val, out)
}
}
func ReadRequestBody(in interface{}) (s string) {
var (
b []byte
@@ -112,18 +40,8 @@ func ReadRequestBody(in interface{}) (s string) {
)
switch val := in.(type) {
case *HttpRequest:
b, err = val.Get().(*types.HttpRequest).ReadBody()
case *HttpRequestBody:
b, err = val.Get().(*types.HttpRequestBody).Read()
case *atypes.HttpRequest:
b, err = val.ReadBody()
case *types.HttpRequest:
b, err = val.ReadBody()
case *atypes.HttpRequestBody:
b, err = val.Read()
case *types.HttpRequestBody:
b, err = val.Read()
case *http.Request:
b, err = io.ReadAll(val.Body)
case io.Reader:
b, err = io.ReadAll(val)
default:
+1 -31
View File
@@ -1,8 +1,5 @@
package: automation
imports:
- github.com/cortezaproject/corteza-server/automation/types
types:
EmailMessage:
# using ad-hoc type for now, we'll port this to something internal
@@ -19,31 +16,4 @@ types:
# - { name: 'parts', exprType: 'Any', goType: 'map[string]io.Reader', mode: ro }
# - { name: 'embedded', exprType: 'Any', goType: 'map[string]io.Reader', mode: ro }
# - { name: 'attachments', exprType: 'Any', goType: 'map[string]io.Reader', mode: ro }
HttpRequest:
as: '*types.HttpRequest'
struct:
- { name: 'Method', exprType: 'String', goType: 'string' }
- { name: 'URL', exprType: 'Url', goType: 'url.Url' }
- { name: 'Header', exprType: 'KVV', goType: 'map[string][]string' }
- { name: 'Body', exprType: 'HttpRequestBody', goType: '*types.HttpRequestBody' }
- { name: 'Form', exprType: 'KVV', goType: 'map[string][]string' }
- { name: 'PostForm', exprType: 'KVV', goType: 'map[string][]string' }
HttpRequestBody:
as: '*types.HttpRequestBody'
struct:
- { name: 'Body', exprType: 'Reader', goType: 'io.Reader' }
- { name: 'Buffer', exprType: 'Bytes', goType: '[]byte' }
Url:
as: '*types.Url'
struct:
- { name: 'Scheme', exprType: 'String', goType: 'string' }
- { name: 'Opaque', exprType: 'String', goType: 'string' }
- { name: 'Host', exprType: 'String', goType: 'string' }
- { name: 'Path', exprType: 'String', goType: 'string' }
- { name: 'RawPath', exprType: 'String', goType: 'string' }
- { name: 'ForceQuery', exprType: 'Boolean', goType: 'bool' }
- { name: 'RawQuery', exprType: 'String', goType: 'string' }
- { name: 'Fragment', exprType: 'String', goType: 'string' }
- { name: 'RawFragment', exprType: 'String', goType: 'string' }
-2
View File
@@ -63,8 +63,6 @@ func (h jsenvHandler) execute(ctx context.Context, args *jsenvExecuteArgs) (res
}
switch vv := out.(type) {
case uint64:
res.ResultInt = int64(vv)
case int64:
res.ResultInt = int64(vv)
case string:
+201
View File
@@ -0,0 +1,201 @@
package automation
import (
"context"
"errors"
"io/ioutil"
"net/http"
"strings"
"testing"
"github.com/cortezaproject/corteza-server/pkg/expr"
h "github.com/cortezaproject/corteza-server/pkg/http"
"github.com/stretchr/testify/require"
)
func Test_jsenvHandler(t *testing.T) {
type (
exp struct {
s string
i int64
a interface{}
}
tf struct {
name string
exp *exp
err error
params *jsenvExecuteArgs
}
)
var (
handler = &jsenvHandler{}
tcc = []tf{
{
name: "jsenv handler check payload",
err: errors.New(`could not process payload, scope missing`),
params: &jsenvExecuteArgs{
hasScope: false,
hasSource: true,
},
},
{
name: "jsenv handler check payload",
err: errors.New(`could not process payload, function missing`),
params: &jsenvExecuteArgs{
hasScope: true,
hasSource: false,
},
},
{
name: "jsenv handler invalid function",
err: errors.New(`could not register jsenv function: SyntaxError: SyntaxError: (anonymous): Line 1:74 Unexpected token function (and 1 more errors)`),
params: &jsenvExecuteArgs{
hasScope: true,
hasSource: true,
Source: `invalid function here...`,
},
},
{
name: "jsenv handler check payload",
err: errors.New(``),
params: &jsenvExecuteArgs{
hasScope: true,
hasSource: true,
Source: `return nonexistent`,
},
},
{
name: "jsenv handler parse request body json to string",
exp: &exp{
s: `bar`,
i: 0,
},
params: &jsenvExecuteArgs{
hasScope: true,
hasSource: true,
Scope: mustAny(expr.NewAny(makeRequest(t, `{"foo":"bar"}`))),
Source: `const b = JSON.parse(readRequestBody(input)); return b.foo;`,
},
},
{
name: "jsenv handler parse request body json to int",
exp: &exp{
s: ``,
i: 42,
},
params: &jsenvExecuteArgs{
hasScope: true,
hasSource: true,
Scope: mustAny(expr.NewAny(makeRequest(t, `{"foo":42}`))),
Source: `const b = JSON.parse(readRequestBody(input)); return b.foo;`,
},
},
{
name: "jsenv handler parse request body json to int",
exp: &exp{
s: ``,
i: 42,
},
params: &jsenvExecuteArgs{
hasScope: true,
hasSource: true,
Scope: mustAny(expr.NewAny(makeRequest(t, `42`))),
Source: `const b = readRequestBody(input); return parseInt(b);`,
},
},
{
name: "jsenv handler parse request body try catch",
exp: &exp{
s: `caught`,
i: 0,
},
params: &jsenvExecuteArgs{
hasScope: true,
hasSource: true,
Scope: mustAny(expr.NewAny(makeRequest(t, `42`))),
Source: `try { const b = readRequestBody(input_NONEXISTENT); } catch (e) { return 'caught'; }`,
},
},
{
name: "jsenv handler parse request body json to float",
exp: &exp{
s: ``,
i: 0,
a: 42.690,
},
params: &jsenvExecuteArgs{
hasScope: true,
hasSource: true,
Scope: mustAny(expr.NewAny(makeRequest(t, `42.690`))),
Source: `const b = readRequestBody(input); return parseFloat(b);`,
},
},
{
name: "jsenv handler input scope",
exp: &exp{
s: ``,
i: 41,
},
params: &jsenvExecuteArgs{
hasScope: true,
hasSource: true,
Scope: mustAny(expr.NewAny(makeRequest(t, `42`))),
Source: `const b = readRequestBody(input); return b - 1;`,
},
},
}
)
for _, tc := range tcc {
t.Run(tc.name, func(t *testing.T) {
var (
req = require.New(t)
ctx = context.Background()
)
handler.preloadVm()
out, err := handler.execute(ctx, tc.params)
if tc.err == nil {
req.NoError(err)
} else {
req.Error(err)
}
if tc.exp != nil {
req.Equal(tc.exp.s, out.ResultString)
req.Equal(tc.exp.i, out.ResultInt)
if tc.exp.a != nil {
req.Equal(tc.exp.a, out.ResultAny)
}
}
})
}
}
func makeRequest(t *testing.T, b string) *h.Request {
r, err := http.NewRequest("POST", "/foo", ioutil.NopCloser(strings.NewReader(b)))
if err != nil {
t.Error(err)
}
ar, err := h.NewRequest(r)
if err != nil {
t.Error(err)
}
return ar
}
func mustAny(v *expr.Any, err error) *expr.Any {
if err != nil {
panic(err)
}
return v
}
+1 -2
View File
@@ -113,10 +113,9 @@ func Initialize(ctx context.Context, log *zap.Logger, s store.Storer, ws websock
&expr.KVV{},
&expr.Reader{},
&expr.Vars{},
&expr.HttpRequest{},
&automation.EmailMessage{},
&automation.HttpRequest{},
&automation.HttpRequestBody{},
)
automation.HttpRequestHandler(Registry())
+1 -2
View File
@@ -7,7 +7,6 @@ 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"
@@ -194,7 +193,7 @@ func NewJsonResponse(reg typesRegistry) (e *jsonResponse) {
}
func (j jsonResponse) New() types.Handler {
return NewJsonResponse(service.Registry())
return NewJsonResponse(j.reg)
}
func (j jsonResponse) String() string {
+9 -2
View File
@@ -7,13 +7,16 @@ import (
"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"
)
type (
mockHandlerRegistry struct{}
)
func Test_redirectionMerge(t *testing.T) {
var (
tcc = []tf{
@@ -141,7 +144,7 @@ func Test_jsonResponse(t *testing.T) {
r = r.WithContext(agctx.ScopeToContext(context.Background(), scope))
h := getHandler(NewJsonResponse(service.Registry()))
h := getHandler(NewJsonResponse(&mockHandlerRegistry{}))
h, err := h.Merge([]byte(tc.expr))
req.NoError(err)
@@ -164,3 +167,7 @@ func Test_jsonResponse(t *testing.T) {
func getHandler(h types.Handler) types.Handler {
return h
}
func (r *mockHandlerRegistry) Type(ref string) expr.Type {
return expr.Any{}
}
+40 -37
View File
@@ -6,9 +6,9 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"github.com/cortezaproject/corteza-server/automation/automation"
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"
@@ -29,6 +29,7 @@ type (
}
WfExecer interface {
Load(ctx context.Context) error
Exec(ctx context.Context, workflowID uint64, p atypes.WorkflowExecParams) (*expr.Vars, atypes.Stacktrace, error)
}
@@ -36,6 +37,7 @@ type (
types.FilterMeta
vm jsenv.Vm
fn *jsenv.Fn
log *zap.Logger
params struct {
@@ -80,7 +82,12 @@ func (h workflow) Meta() types.FilterMeta {
func (h *workflow) Merge(params []byte) (types.Handler, error) {
err := json.NewDecoder(bytes.NewBuffer(params)).Decode(&h.params)
return h, err
if err != nil {
return h, err
}
// preload workflow cache
return h, h.d.Load(context.Background())
}
func (h workflow) Handler() types.HandlerFunc {
@@ -91,21 +98,10 @@ func (h workflow) Handler() types.HandlerFunc {
scope = agctx.ScopeFromContext(ctx)
)
// original request with body as io.Reader
// read-only
ar, err := scope.Get("request")
// cleanup scope for wf
scp := filterScope(scope, "opts")
if err != nil {
return err
}
// setup scope for workflow
vv := map[string]interface{}{
"request": ar,
}
// get the request data and put it into vars
in, err := expr.NewVars(vv)
in, err := expr.NewVars(scp.Dict())
if err != nil {
return pe.Internal("could not validate request data: %v", err)
@@ -143,17 +139,10 @@ func (h workflow) Handler() types.HandlerFunc {
scope.Set(k, v)
}
ss := scope.Filter(func(k string, v interface{}) bool {
if k == "eventType" || k == "resourceType" {
return false
}
scope = filterScope(scope, "eventType", "resourceType", "invoker")
return true
})
scope = ss
scope.Set("request", ar)
// update scope for next items in pipeline
r.WithContext(agctx.ScopeToContext(ctx, scope))
return nil
}
@@ -162,7 +151,6 @@ func (h workflow) Handler() types.HandlerFunc {
func NewPayload(l *zap.Logger) (p *processerPayload) {
p = &processerPayload{}
// todo - check the consequences of doing this here
p.vm = jsenv.New(jsenv.NewTransformer(jsenv.LoaderJS, jsenv.TargetES2016))
p.log = l
@@ -179,11 +167,7 @@ func NewPayload(l *zap.Logger) (p *processerPayload) {
}
// register a request body reader
// since it's a readcloser, it can be read only once
p.vm.Register("readRequestBody", func(rc io.ReadCloser) string {
b, _ := io.ReadAll(rc)
return string(b)
})
p.vm.Register("readRequestBody", automation.ReadRequestBody)
return
}
@@ -211,6 +195,8 @@ func (h *processerPayload) Merge(params []byte) (types.Handler, error) {
return nil, errors.New("could not register function, body empty")
}
h.fn, _ = h.vm.RegisterFunction(h.params.Func)
return h, err
}
@@ -221,15 +207,18 @@ func (h processerPayload) Handler() types.HandlerFunc {
scope = agctx.ScopeFromContext(ctx)
)
scope.Set("request", r)
fn, err := h.vm.RegisterFunction(h.params.Func)
// cleanup scope for js
scp := filterScope(scope, "opts")
// check fn type
if err != nil {
return pe.InvalidData("could not register function: %v", err)
}
out, err := fn.Exec(h.vm.New(scope))
// need to find a consistent approach to the workflow jsenv function
// wf: input expr Var and the resulting variable in the jsenv is `input`
// apigw: input types.Scope and the resulting variable in the jsenv is `input['some_var']`
out, err := h.fn.Exec(h.vm.New(scp))
if err != nil {
return pe.Internal("could not exec payload function: %v", err)
@@ -240,7 +229,7 @@ func (h processerPayload) Handler() types.HandlerFunc {
// check if string
switch out.(type) {
case string:
case string, []byte:
// handling the newline, to keep the consistency with the json encoder
// which automatically appends the newline
_, err = rw.Write([]byte(fmt.Sprintf("%s\n", out)))
@@ -259,3 +248,17 @@ func (h processerPayload) Handler() types.HandlerFunc {
func (h processerPayload) VM() jsenv.Vm {
return h.vm
}
func filterScope(scope *types.Scp, kk ...string) (s *types.Scp) {
s = scope.Filter(func(k string, v interface{}) bool {
for _, v := range kk {
if k == v {
return false
}
}
return true
})
return
}
+115 -8
View File
@@ -3,19 +3,108 @@ package filter
import (
"context"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"
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"
"github.com/cortezaproject/corteza-server/pkg/expr"
h "github.com/cortezaproject/corteza-server/pkg/http"
"github.com/cortezaproject/corteza-server/pkg/options"
"github.com/cortezaproject/corteza-server/pkg/wfexec"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
)
type (
wfServicer struct {
load func(ctx context.Context) error
exec func(ctx context.Context, workflowID uint64, p atypes.WorkflowExecParams) (*expr.Vars, atypes.Stacktrace, error)
}
)
func Test_processerWorkflow(t *testing.T) {
type (
tf struct {
name string
err string
params string
wfs wfServicer
exp []string
}
)
var (
tcc = []tf{
{
name: "workflow processer",
exp: []string{"opts", "request"},
params: `{"workflow":"1"}`,
err: `could not exec workflow: mocked error`,
wfs: wfServicer{
load: func(ctx context.Context) error {
return nil
},
exec: func(ctx context.Context, workflowID uint64, p atypes.WorkflowExecParams) (*expr.Vars, atypes.Stacktrace, error) {
return must(expr.NewVars(map[string]interface{}{"foo": "bar"})), make([]*wfexec.Frame, 0), errors.New("mocked error")
},
},
},
{
name: "workflow processer",
exp: []string{"foo", "request", "opts"},
params: `{"workflow":"1"}`,
wfs: wfServicer{
load: func(ctx context.Context) error {
return nil
},
exec: func(ctx context.Context, workflowID uint64, p atypes.WorkflowExecParams) (*expr.Vars, atypes.Stacktrace, error) {
return must(expr.NewVars(map[string]interface{}{"foo": "bar"})), make([]*wfexec.Frame, 0), nil
},
},
},
}
)
for _, tc := range tcc {
t.Run(tc.name, func(t *testing.T) {
var (
req = require.New(t)
rc = httptest.NewRecorder()
rq, _ = http.NewRequest("POST", "/foo", http.NoBody)
ar, err = h.NewRequest(rq)
pp = NewWorkflow(tc.wfs)
)
_, err = pp.Merge([]byte(tc.params))
req.NoError(err)
scope := &types.Scp{
"opts": options.Apigw(),
"request": ar,
}
rq = rq.WithContext(agctx.ScopeToContext(context.Background(), scope))
hn := pp.Handler()
err = hn(rc, rq)
if tc.err != "" {
req.EqualError(err, tc.err)
} else {
req.NoError(err)
}
req.ElementsMatch(tc.exp, agctx.ScopeFromContext(rq.Context()).Keys())
})
}
}
func Test_processerPayload(t *testing.T) {
type (
tf struct {
@@ -31,26 +120,26 @@ func Test_processerPayload(t *testing.T) {
var (
tcc = []tf{
{
name: "payload processer",
name: "payload processer parse request body",
rq: &http.Request{
Method: "POST",
Body: ioutil.NopCloser(strings.NewReader(`[1,2,3]`)),
},
exp: "2\n",
params: prepareFuncPayload(t, `
var b = JSON.parse(readRequestBody(input.Get('request').Body));
const b = JSON.parse(readRequestBody(input.Get('request')));
return b[1];
`),
},
{
name: "payload processer js map",
name: "payload processer js map request body",
rq: &http.Request{
Method: "POST",
Body: ioutil.NopCloser(strings.NewReader(`[{"name":"johnny", "surname":"mnemonic"},{"name":"johnny", "surname":"knoxville"}]`)),
},
exp: "{\"count\":2,\"results\":[{\"fullname\":\"Johnny Mnemonic\"},{\"fullname\":\"Johnny Knoxville\"}]}\n",
params: prepareFuncPayload(t, `
var b = JSON.parse(readRequestBody(input.Get('request').Body));
const b = JSON.parse(readRequestBody(input.Get('request')));
return {
"results":
@@ -78,12 +167,13 @@ func Test_processerPayload(t *testing.T) {
for _, tc := range tcc {
t.Run(tc.name, func(t *testing.T) {
var (
req = require.New(t)
rc = httptest.NewRecorder()
req = require.New(t)
rc = httptest.NewRecorder()
ar, err = h.NewRequest(tc.rq)
)
pp := NewPayload(zap.NewNop())
_, err := pp.Merge([]byte(tc.params))
_, err = pp.Merge([]byte(tc.params))
if tc.errv != "" {
req.EqualError(err, tc.errv)
@@ -93,7 +183,8 @@ func Test_processerPayload(t *testing.T) {
}
scope := &types.Scp{
"opts": options.Apigw(),
"opts": options.Apigw(),
"request": ar,
}
tc.rq = tc.rq.WithContext(agctx.ScopeToContext(context.Background(), scope))
@@ -118,3 +209,19 @@ func prepareFuncPayload(t *testing.T, s string) string {
}
return string(aux)
}
func (f wfServicer) Load(ctx context.Context) error {
return f.load(ctx)
}
func (f wfServicer) Exec(ctx context.Context, workflowID uint64, p atypes.WorkflowExecParams) (*expr.Vars, atypes.Stacktrace, error) {
return f.exec(ctx, workflowID, p)
}
func must(v *expr.Vars, err error) *expr.Vars {
if err != nil {
return nil
}
return v
}
+1 -5
View File
@@ -8,8 +8,6 @@ import (
"github.com/cortezaproject/corteza-server/pkg/apigw/filter"
"github.com/cortezaproject/corteza-server/pkg/apigw/filter/proxy"
"github.com/cortezaproject/corteza-server/pkg/apigw/types"
"github.com/cortezaproject/corteza-server/pkg/logger"
"github.com/cortezaproject/corteza-server/pkg/options"
)
type (
@@ -74,7 +72,5 @@ func (r *Registry) Preload() {
}
func NewWorkflow() (wf filter.WfExecer) {
// implementation assumes that Corredor & Workflow options can not be changed
// in the run-time.
return service.Workflow(logger.Default().Named("workflow"), *options.Corredor(), *options.Workflow())
return service.DefaultWorkflow
}
+3 -5
View File
@@ -8,8 +8,8 @@ import (
actx "github.com/cortezaproject/corteza-server/pkg/apigw/ctx"
"github.com/cortezaproject/corteza-server/pkg/apigw/types"
"github.com/cortezaproject/corteza-server/pkg/auth"
h "github.com/cortezaproject/corteza-server/pkg/http"
"github.com/cortezaproject/corteza-server/pkg/options"
"github.com/cortezaproject/corteza-server/system/automation"
"go.uber.org/zap"
)
@@ -42,12 +42,10 @@ func (r route) ServeHTTP(w http.ResponseWriter, req *http.Request) {
r.log.Debug("started serving route")
// create a new automation HttpRequest
ar, err := automation.NewHttpRequest(req)
ar, err := h.NewRequest(req)
if err != nil {
r.log.Error("could not prepare a request holder", zap.Error(err))
return
r.log.Error("could not get initial request", zap.Error(err))
}
scope.Set("opts", r.opts)
+248
View File
@@ -11,7 +11,9 @@ package expr
import (
"context"
"fmt"
"github.com/cortezaproject/corteza-server/pkg/http"
"io"
"net/url"
"sync"
"time"
)
@@ -464,6 +466,203 @@ func (t Handle) Compare(to TypedValue) (int, error) {
}
}
// HttpRequest is an expression type, wrapper for *http.Request type
type HttpRequest struct {
value *http.Request
mux sync.RWMutex
}
// NewHttpRequest creates new instance of HttpRequest expression type
func NewHttpRequest(val interface{}) (*HttpRequest, error) {
if c, err := CastToHttpRequest(val); err != nil {
return nil, fmt.Errorf("unable to create HttpRequest: %w", err)
} else {
return &HttpRequest{value: c}, nil
}
}
// Get return underlying value on HttpRequest
func (t *HttpRequest) Get() interface{} {
t.mux.RLock()
defer t.mux.RUnlock()
return t.value
}
// GetValue returns underlying value on HttpRequest
func (t *HttpRequest) GetValue() *http.Request {
t.mux.RLock()
defer t.mux.RUnlock()
return t.value
}
// Type return type name
func (HttpRequest) Type() string { return "HttpRequest" }
// Cast converts value to *http.Request
func (HttpRequest) Cast(val interface{}) (TypedValue, error) {
return NewHttpRequest(val)
}
// Assign new value to HttpRequest
//
// value is first passed through CastToHttpRequest
func (t *HttpRequest) Assign(val interface{}) error {
if c, err := CastToHttpRequest(val); err != nil {
return err
} else {
t.value = c
return nil
}
}
func (t *HttpRequest) AssignFieldValue(key string, val TypedValue) error {
t.mux.Lock()
defer t.mux.Unlock()
return assignToHttpRequest(t.value, key, val)
}
// SelectGVal implements gval.Selector requirements
//
// It allows gval lib to access HttpRequest's underlying value (*http.Request)
// and it's fields
//
func (t *HttpRequest) SelectGVal(ctx context.Context, k string) (interface{}, error) {
t.mux.RLock()
defer t.mux.RUnlock()
return httpRequestGValSelector(t.value, k)
}
// Select is field accessor for *http.Request
//
// Similar to SelectGVal but returns typed values
func (t *HttpRequest) Select(k string) (TypedValue, error) {
t.mux.RLock()
defer t.mux.RUnlock()
return httpRequestTypedValueSelector(t.value, k)
}
func (t *HttpRequest) Has(k string) bool {
t.mux.RLock()
defer t.mux.RUnlock()
switch k {
case "Method":
return true
case "URL":
return true
case "Header":
return true
case "Body":
return true
case "Form":
return true
case "PostForm":
return true
}
return false
}
// httpRequestGValSelector is field accessor for *http.Request
func httpRequestGValSelector(res *http.Request, k string) (interface{}, error) {
if res == nil {
return nil, nil
}
switch k {
case "Method":
return res.Method, nil
case "URL":
return res.URL, nil
case "Header":
return res.Header, nil
case "Body":
return res.Body, nil
case "Form":
return res.Form, nil
case "PostForm":
return res.PostForm, nil
}
return nil, fmt.Errorf("unknown field '%s'", k)
}
// httpRequestTypedValueSelector is field accessor for *http.Request
func httpRequestTypedValueSelector(res *http.Request, k string) (TypedValue, error) {
if res == nil {
return nil, nil
}
switch k {
case "Method":
return NewString(res.Method)
case "URL":
return NewUrl(res.URL)
case "Header":
return NewKVV(res.Header)
case "Body":
return NewReader(res.Body)
case "Form":
return NewKVV(res.Form)
case "PostForm":
return NewKVV(res.PostForm)
}
return nil, fmt.Errorf("unknown field '%s'", k)
}
// assignToHttpRequest is field value setter for *http.Request
func assignToHttpRequest(res *http.Request, k string, val interface{}) error {
switch k {
case "Method":
aux, err := CastToString(val)
if err != nil {
return err
}
res.Method = aux
return nil
case "URL":
aux, err := CastToUrl(val)
if err != nil {
return err
}
res.URL = aux
return nil
case "Header":
aux, err := CastToKVV(val)
if err != nil {
return err
}
res.Header = aux
return nil
case "Body":
aux, err := CastToReader(val)
if err != nil {
return err
}
res.Body = aux
return nil
case "Form":
aux, err := CastToKVV(val)
if err != nil {
return err
}
res.Form = aux
return nil
case "PostForm":
aux, err := CastToKVV(val)
if err != nil {
return err
}
res.PostForm = aux
return nil
}
return fmt.Errorf("unknown field '%s'", k)
}
// ID is an expression type, wrapper for uint64 type
type ID struct {
value uint64
@@ -883,6 +1082,55 @@ func (t UnsignedInteger) Compare(to TypedValue) (int, error) {
}
}
// Url is an expression type, wrapper for *url.URL type
type Url struct {
value *url.URL
mux sync.RWMutex
}
// NewUrl creates new instance of Url expression type
func NewUrl(val interface{}) (*Url, error) {
if c, err := CastToUrl(val); err != nil {
return nil, fmt.Errorf("unable to create Url: %w", err)
} else {
return &Url{value: c}, nil
}
}
// Get return underlying value on Url
func (t *Url) Get() interface{} {
t.mux.RLock()
defer t.mux.RUnlock()
return t.value
}
// GetValue returns underlying value on Url
func (t *Url) GetValue() *url.URL {
t.mux.RLock()
defer t.mux.RUnlock()
return t.value
}
// Type return type name
func (Url) Type() string { return "Url" }
// Cast converts value to *url.URL
func (Url) Cast(val interface{}) (TypedValue, error) {
return NewUrl(val)
}
// Assign new value to Url
//
// value is first passed through CastToUrl
func (t *Url) Assign(val interface{}) error {
if c, err := CastToUrl(val); err != nil {
return err
} else {
t.value = c
return nil
}
}
// Vars is an expression type, wrapper for map[string]TypedValue type
type Vars struct {
value map[string]TypedValue
+31
View File
@@ -16,6 +16,7 @@ import (
"github.com/PaesslerAG/gval"
"github.com/cortezaproject/corteza-server/pkg/errors"
"github.com/cortezaproject/corteza-server/pkg/handle"
h "github.com/cortezaproject/corteza-server/pkg/http"
"github.com/spf13/cast"
)
@@ -654,6 +655,36 @@ func CastToReader(val interface{}) (out io.Reader, err error) {
}
}
func CastToHttpRequest(val interface{}) (out *h.Request, err error) {
switch val := val.(type) {
case Iterator:
out = &h.Request{}
return out, val.Each(func(k string, v TypedValue) error {
return assignToHttpRequest(out, k, v)
})
}
switch val := UntypedValue(val).(type) {
case *h.Request:
return val, nil
case nil:
return &h.Request{}, nil
default:
return &h.Request{}, fmt.Errorf("unable to cast type %T to %T", val, out)
}
}
func CastToUrl(val interface{}) (out *url.URL, err error) {
switch val := UntypedValue(val).(type) {
case *url.URL:
return val, nil
case nil:
return &url.URL{}, nil
default:
return &url.URL{}, fmt.Errorf("unable to cast type %T to %T", val, out)
}
}
func (t *KVV) Each(fn func(k string, v TypedValue) error) (err error) {
if t == nil || t.value == nil {
return
+14
View File
@@ -2,6 +2,8 @@ package: expr
imports:
- io
- time
- github.com/cortezaproject/corteza-server/pkg/http
- net/url
types:
Any:
@@ -76,3 +78,15 @@ types:
Reader:
as: 'io.Reader'
HttpRequest:
as: '*http.Request'
struct:
- { name: 'Method', exprType: 'String', goType: 'string' }
- { name: 'URL', exprType: 'Url', goType: 'url.Url' }
- { name: 'Header', exprType: 'KVV', goType: 'map[string][]string' }
- { name: 'Body', exprType: 'Reader', goType: '*types.BufferedReader' }
- { name: 'Form', exprType: 'KVV', goType: 'map[string][]string' }
- { name: 'PostForm', exprType: 'KVV', goType: 'map[string][]string' }
Url:
as: '*url.URL'
+3 -31
View File
@@ -5,7 +5,6 @@ import (
"database/sql/driver"
"encoding/json"
"fmt"
"net/http"
"reflect"
"strings"
@@ -363,37 +362,10 @@ func (t *Vars) MarshalJSON() ([]byte, error) {
aux[k] = &typedValueWrap{Type: v.Type()}
rv := v.Get()
// @todo this is a temporary solution.
// The JSON marshling failed due to some receiver functions on the
// HTTP request struct.
if hv, ok := rv.(*http.Request); ok {
aux[k].Value = map[string]interface{}{
"Method": hv.Method,
"URL": hv.URL,
"Proto": hv.Proto,
"ProtoMajor": hv.ProtoMajor,
"ProtoMinor": hv.ProtoMinor,
"Header": hv.Header,
"ContentLength": hv.ContentLength,
"TransferEncoding": hv.TransferEncoding,
"Close": hv.Close,
"Host": hv.Host,
"Form": hv.Form,
"PostForm": hv.PostForm,
"MultipartForm": hv.MultipartForm,
"Trailer": hv.Trailer,
"RemoteAddr": hv.RemoteAddr,
"RequestURI": hv.RequestURI,
"TLS": hv.TLS,
"Response": hv.Response,
}
if _, is := v.(json.Marshaler); is {
aux[k].Value = v
} else {
if _, is := v.(json.Marshaler); is {
aux[k].Value = v
} else {
aux[k].Value = v.Get()
}
aux[k].Value = v.Get()
}
}
-2
View File
@@ -28,8 +28,6 @@ type (
config *Config
}
Request http.Request
DebugLevel string
)
+91
View File
@@ -0,0 +1,91 @@
package http
import (
"encoding/json"
"io"
"mime/multipart"
"net/http"
"net/url"
)
type (
Request struct {
*http.Request
Body io.Reader
}
BufferedReader struct {
buffer []byte
}
)
// NewBufferedReader creates a new reader from readcloser
func NewBufferedReader(r io.ReadCloser) (b *BufferedReader, err error) {
var bb []byte
if bb, err = io.ReadAll(r); err != nil {
return
}
b = &BufferedReader{bb}
return
}
// NewRequest creates a new Request with the buffered ready body
func NewRequest(r *http.Request) (rr *Request, err error) {
rs, err := NewBufferedReader(r.Body)
if err != nil {
return
}
rr = &Request{r, rs}
return
}
func (bb *BufferedReader) Read(p []byte) (n int, err error) {
if len(bb.buffer) <= n {
err = io.EOF
return
}
if c := cap(p); c > 0 {
for n < c {
if len(bb.buffer) <= n {
err = io.EOF
break
}
p[n] = bb.buffer[n]
n++
}
}
return
}
func (bb *Request) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
Method string
URL *url.URL
Header http.Header
ContentLength int64
Host string
Form url.Values
PostForm url.Values
MultipartForm *multipart.Form
RemoteAddr string
RequestURI string
}{
Method: bb.Method,
URL: bb.URL,
Header: bb.Header,
ContentLength: bb.ContentLength,
Host: bb.Host,
Form: bb.Form,
PostForm: bb.PostForm,
MultipartForm: bb.MultipartForm,
RemoteAddr: bb.RemoteAddr,
RequestURI: bb.RequestURI,
})
}
+40
View File
@@ -0,0 +1,40 @@
package http
import (
"io"
h "net/http"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
func Test_requestReadMultiple(t *testing.T) {
var req = require.New(t)
r, _ := h.NewRequest("POST", "/foo", strings.NewReader(`foo body`))
rs, err := NewBufferedReader(r.Body)
req.NoError(err)
req.Equal(`foo body`, must(io.ReadAll(rs)))
req.Equal(`foo body`, must(io.ReadAll(rs)))
}
func Test_requestReadMultipleNoBody(t *testing.T) {
var req = require.New(t)
r, _ := h.NewRequest("POST", "/foo", h.NoBody)
rs, err := NewBufferedReader(r.Body)
req.NoError(err)
req.Equal(``, must(io.ReadAll(rs)))
req.Equal(``, must(io.ReadAll(rs)))
}
func must(b []byte, e error) string {
if e != nil {
panic(e)
}
return string(b)
}
-1
View File
@@ -11,7 +11,6 @@ package rdbms
import (
"context"
"database/sql"
"github.com/Masterminds/squirrel"
"github.com/cortezaproject/corteza-server/pkg/errors"
"github.com/cortezaproject/corteza-server/pkg/filter"
-2
View File
@@ -51,7 +51,6 @@ var (
testApp *app.CortezaApp
r chi.Router
// defStore store.Storer
eventBus = eventbus.New()
)
@@ -69,7 +68,6 @@ func InitTestApp() {
return err
}
// defStore = app.Store
eventbus.Set(eventBus)
return nil
})