Skip to content

Commit

Permalink
feat(MutateCheck): add option for dataset mutation checks, ExecFile -…
Browse files Browse the repository at this point in the history
…> ExecScript
  • Loading branch information
b5 committed Nov 6, 2018
1 parent 016069d commit c7e65a3
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 37 deletions.
39 changes: 32 additions & 7 deletions ds/dataset.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,22 @@ import (
"github.com/qri-io/starlib/util"
)

// MutateFieldCheck is a function to check if a dataset field can be mutated
// before mutating a field, dataset will call MutateFieldCheck with as specific
// a path as possible and bail if an error is returned
type MutateFieldCheck func(path ...string) error

// Dataset is a qri dataset starlark type
type Dataset struct {
ds *dataset.Dataset
infile cafs.File
body starlark.Iterable
check MutateFieldCheck
}

// NewDataset creates a dataset object
func NewDataset(ds *dataset.Dataset, infile cafs.File) *Dataset {
return &Dataset{ds: ds, infile: infile}
func NewDataset(ds *dataset.Dataset, infile cafs.File, check MutateFieldCheck) *Dataset {
return &Dataset{ds: ds, infile: infile, check: check}
}

// Dataset returns the underlying dataset
Expand All @@ -47,18 +53,29 @@ func (d *Dataset) Methods() *starlarkstruct.Struct {
})
}

// checkField runs the check function if one is defined
func (d *Dataset) checkField(path ...string) error {
if d.check != nil {
return d.check(path...)
}
return nil
}

// SetMeta sets a dataset meta field
func (d *Dataset) SetMeta(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var keyx, valx starlark.Value
var (
keyx starlark.String
valx starlark.Value
)
if err := starlark.UnpackPositionalArgs("set_meta", args, kwargs, 2, &keyx, &valx); err != nil {
return nil, err
}

if keyx.Type() != "string" {
return nil, fmt.Errorf("expected key to be a string")
}
key := keyx.String()

key := string(keyx.(starlark.String))
if err := d.checkField("meta", "key"); err != nil {
return starlark.None, err
}

val, err := util.Unmarshal(valx)
if err != nil {
Expand All @@ -79,6 +96,10 @@ func (d *Dataset) SetSchema(thread *starlark.Thread, _ *starlark.Builtin, args s
return nil, err
}

if err := d.checkField("structure", "schema"); err != nil {
return starlark.None, err
}

rs := &jsonschema.RootSchema{}
if err := json.Unmarshal([]byte(valx.String()), rs); err != nil {
return starlark.None, err
Expand Down Expand Up @@ -148,6 +169,10 @@ func (d *Dataset) SetBody(thread *starlark.Thread, _ *starlark.Builtin, args sta
return starlark.None, err
}

if err := d.checkField("body"); err != nil {
return starlark.None, err
}

if raw {
if str, ok := data.(starlark.String); ok {
d.infile = cafs.NewMemfileBytes("data", []byte(string(str)))
Expand Down
29 changes: 29 additions & 0 deletions ds/dataset_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package ds

import (
"fmt"
"testing"

starlark "github.com/google/skylark"
)

func TestCheckFields(t *testing.T) {
fieldErr := fmt.Errorf("can't mutate this field")
allErrCheck := func(fields ...string) error {
return fieldErr
}
ds := NewDataset(nil, nil, allErrCheck)
thread := &starlark.Thread{}

if _, err := ds.SetBody(thread, nil, starlark.Tuple{starlark.String("data")}, nil); err != fieldErr {
t.Errorf("expected fieldErr, got: %s", err)
}

if _, err := ds.SetMeta(thread, nil, starlark.Tuple{starlark.String("key"), starlark.String("value")}, nil); err != fieldErr {
t.Errorf("expected fieldErr, got: %s", err)
}

if _, err := ds.SetSchema(thread, nil, starlark.Tuple{starlark.String("wut")}, nil); err != fieldErr {
t.Errorf("expected fieldErr, got: %s", err)
}
}
59 changes: 33 additions & 26 deletions transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ import (

// ExecOpts defines options for execution
type ExecOpts struct {
Node *p2p.QriNode
AllowFloat bool // allow floating-point numbers
AllowSet bool // allow set data type
AllowLambda bool // allow lambda expressions
AllowNestedDef bool // allow nested def statements
Secrets map[string]interface{} // passed-in secrets (eg: API keys)
Globals starlark.StringDict
Node *p2p.QriNode
AllowFloat bool // allow floating-point numbers
AllowSet bool // allow set data type
AllowLambda bool // allow lambda expressions
AllowNestedDef bool // allow nested def statements
Secrets map[string]interface{} // passed-in secrets (eg: API keys)
Globals starlark.StringDict
MutateFieldCheck func(path ...string) error
}

// AddQriNodeOpt adds a qri node to execution options
Expand All @@ -36,6 +37,13 @@ func AddQriNodeOpt(node *p2p.QriNode) func(o *ExecOpts) {
}
}

// AddMutateFieldCheck provides a checkFunc to ExecScript
func AddMutateFieldCheck(check func(path ...string) error) func(o *ExecOpts) {
return func(o *ExecOpts) {
o.MutateFieldCheck = check
}
}

// DefaultExecOpts applies default options to an ExecOpts pointer
func DefaultExecOpts(o *ExecOpts) {
o.AllowFloat = true
Expand All @@ -45,28 +53,20 @@ func DefaultExecOpts(o *ExecOpts) {
}

type transform struct {
node *p2p.QriNode
ds *dataset.Dataset
skyqri *skyqri.Module
globals starlark.StringDict
infile cafs.File
node *p2p.QriNode
ds *dataset.Dataset
skyqri *skyqri.Module
checkFunc func(path ...string) error
globals starlark.StringDict
infile cafs.File

download starlark.Iterable
}

func newTransform(node *p2p.QriNode, ds *dataset.Dataset, infile cafs.File) *transform {
return &transform{
node: node,
ds: ds,
skyqri: skyqri.NewModule(node, ds),
infile: infile,
}
}

// ExecFile executes a transformation against a starlark script file, giving back an EntryReader of resulting data
// ExecFile modifies the given dataset pointer. At bare minimum it will set transformation details, but starlark scripts can modify
// ExecScript executes a transformation against a starlark script file, giving back an EntryReader of resulting data
// ExecScript modifies the given dataset pointer. At bare minimum it will set transformation details, but starlark scripts can modify
// many parts of the dataset pointer, including meta, structure, and transform
func ExecFile(ds *dataset.Dataset, script, bodyFile cafs.File, opts ...func(o *ExecOpts)) (cafs.File, error) {
func ExecScript(ds *dataset.Dataset, script, bodyFile cafs.File, opts ...func(o *ExecOpts)) (cafs.File, error) {
var err error

o := &ExecOpts{}
Expand Down Expand Up @@ -101,7 +101,14 @@ func ExecFile(ds *dataset.Dataset, script, bodyFile cafs.File, opts ...func(o *E
tr := io.TeeReader(script, buf)
pipeScript := cafs.NewMemfileReader(script.FileName(), tr)

t := newTransform(o.Node, ds, bodyFile)
t := &transform{
node: o.Node,
ds: ds,
skyqri: skyqri.NewModule(o.Node, ds),
infile: bodyFile,
checkFunc: o.MutateFieldCheck,
}

ctx := skyctx.NewContext(ds.Transform.Config, o.Secrets)

thread := &starlark.Thread{Load: t.Loader}
Expand Down Expand Up @@ -232,7 +239,7 @@ func callTransformFunc(t *transform, thread *starlark.Thread, ctx *skyctx.Contex
}
t.print("⚙️ running transform...\n")

d := skyds.NewDataset(t.ds, t.infile)
d := skyds.NewDataset(t.ds, t.infile, t.checkFunc)
if _, err = starlark.Call(thread, transform, starlark.Tuple{d.Methods(), ctx.Struct()}, nil); err != nil {
return err
}
Expand Down
8 changes: 4 additions & 4 deletions transform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ func scriptFile(t *testing.T, path string) *cafs.Memfile {
return cafs.NewMemfileBytes(path, data)
}

func TestExecFile(t *testing.T) {
func TestExecScript(t *testing.T) {
ds := &dataset.Dataset{}
script := scriptFile(t, "testdata/tf.star")

body, err := ExecFile(ds, script, nil)
body, err := ExecScript(ds, script, nil)
if err != nil {
t.Error(err.Error())
return
Expand Down Expand Up @@ -54,15 +54,15 @@ func TestExecFile(t *testing.T) {
}
}

func TestExecFile2(t *testing.T) {
func TestExecScript2(t *testing.T) {

s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`{"foo":["bar","baz","bat"]}`))
}))

ds := &dataset.Dataset{}
script := scriptFile(t, "testdata/fetch.star")
_, err := ExecFile(ds, script, nil, func(o *ExecOpts) {
_, err := ExecScript(ds, script, nil, func(o *ExecOpts) {
o.Globals["test_server_url"] = starlark.String(s.URL)
})

Expand Down

0 comments on commit c7e65a3

Please sign in to comment.