Skip to content

Commit

Permalink
Add structured export of bloblang plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeffail committed Sep 4, 2021
1 parent 1db1075 commit b24bb5d
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 124 deletions.
32 changes: 16 additions & 16 deletions internal/bloblang/query/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package query
// ExampleSpec provides a mapping example and some input/output results to
// display.
type ExampleSpec struct {
Mapping string
Summary string
Results [][2]string
Mapping string `json:"mapping"`
Summary string `json:"summary"`
Results [][2]string `json:"results"`
}

// NewExampleSpec creates a new example spec.
Expand Down Expand Up @@ -53,26 +53,26 @@ var (
// FunctionSpec describes a Bloblang function.
type FunctionSpec struct {
// The release status of the function.
Status Status
Status Status `json:"status"`

// A category to place the function within.
Category FunctionCategory
Category FunctionCategory `json:"category"`

// Name of the function (as it appears in config).
Name string
Name string `json:"name"`

// Description of the functions purpose (in markdown).
Description string
Description string `json:"description"`

// Params defines the expected arguments of the function.
Params Params
Params Params `json:"params"`

// Examples shows general usage for the function.
Examples []ExampleSpec
Examples []ExampleSpec `json:"examples,omitempty"`

// Impure indicates that a function accesses or interacts with the outter
// environment, and is therefore unsafe to execute in shared environments.
Impure bool
Impure bool `json:"impure"`
}

// NewFunctionSpec creates a new function spec.
Expand Down Expand Up @@ -157,22 +157,22 @@ type MethodCatSpec struct {
// MethodSpec describes a Bloblang method.
type MethodSpec struct {
// The release status of the function.
Status Status
Status Status `json:"status"`

// Name of the method (as it appears in config).
Name string
Name string `json:"name"`

// Description of the method purpose (in markdown).
Description string
Description string `json:"description"`

// Params defines the expected arguments of the method.
Params Params
Params Params `json:"params"`

// Examples shows general usage for the method.
Examples []ExampleSpec
Examples []ExampleSpec `json:"examples,omitempty"`

// Categories that this method fits within.
Categories []MethodCatSpec
Categories []MethodCatSpec `json:"categories"`
}

// NewMethodSpec creates a new method spec.
Expand Down
17 changes: 1 addition & 16 deletions internal/bloblang/query/function_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,21 +54,11 @@ func (f *FunctionSet) Docs() []FunctionSpec {
return specSlice
}

// List returns a slice of function names in alphabetical order.
func (f *FunctionSet) List() []string {
functionNames := make([]string, 0, len(f.constructors))
for k := range f.constructors {
functionNames = append(functionNames, k)
}
sort.Strings(functionNames)
return functionNames
}

// Params attempts to obtain an argument specification for a given function.
func (f *FunctionSet) Params(name string) (Params, error) {
spec, exists := f.specs[name]
if !exists {
return OldStyleParams(), badFunctionErr(name)
return VariadicParams(), badFunctionErr(name)
}
return spec.Params, nil
}
Expand Down Expand Up @@ -174,11 +164,6 @@ func FunctionDocs() []FunctionSpec {
return AllFunctions.Docs()
}

// ListFunctions returns a slice of function names, sorted alphabetically.
func ListFunctions() []string {
return AllFunctions.List()
}

//------------------------------------------------------------------------------

func wrapCtorWithDynamicArgs(name string, args *ParsedParams, fn FunctionCtor) (Function, error) {
Expand Down
22 changes: 16 additions & 6 deletions internal/bloblang/query/function_set_test.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
package query

import (
"sort"
"testing"

"github.com/stretchr/testify/assert"
)

func listFunctions(f *FunctionSet) []string {
functionNames := make([]string, 0, len(f.constructors))
for k := range f.constructors {
functionNames = append(functionNames, k)
}
sort.Strings(functionNames)
return functionNames
}

func TestFunctionSetWithout(t *testing.T) {
setOne := AllFunctions
setTwo := setOne.Without("uuid_v4")

assert.Contains(t, setOne.List(), "uuid_v4")
assert.NotContains(t, setTwo.List(), "uuid_v4")
assert.Contains(t, listFunctions(setOne), "uuid_v4")
assert.NotContains(t, listFunctions(setTwo), "uuid_v4")

_, err := setOne.Init("uuid_v4", nil)
assert.NoError(t, err)
Expand All @@ -27,11 +37,11 @@ func TestFunctionSetOnlyPure(t *testing.T) {
setOne := AllFunctions
setTwo := setOne.OnlyPure()

assert.Contains(t, setOne.List(), "env")
assert.NotContains(t, setTwo.List(), "env")
assert.Contains(t, listFunctions(setOne), "env")
assert.NotContains(t, listFunctions(setTwo), "env")

assert.Contains(t, setOne.List(), "file")
assert.NotContains(t, setTwo.List(), "file")
assert.Contains(t, listFunctions(setOne), "file")
assert.NotContains(t, listFunctions(setTwo), "file")
}

func TestFunctionBadName(t *testing.T) {
Expand Down
17 changes: 1 addition & 16 deletions internal/bloblang/query/method_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,21 +50,11 @@ func (m *MethodSet) Docs() []MethodSpec {
return specSlice
}

// List returns a slice of method names in alphabetical order.
func (m *MethodSet) List() []string {
methodNames := make([]string, 0, len(m.constructors))
for k := range m.constructors {
methodNames = append(methodNames, k)
}
sort.Strings(methodNames)
return methodNames
}

// Params attempts to obtain an argument specification for a given method type.
func (m *MethodSet) Params(name string) (Params, error) {
spec, exists := m.specs[name]
if !exists {
return OldStyleParams(), badMethodErr(name)
return VariadicParams(), badMethodErr(name)
}
return spec.Params, nil
}
Expand Down Expand Up @@ -132,11 +122,6 @@ func InitMethodHelper(name string, target Function, args ...interface{}) (Functi
return AllMethods.Init(name, target, parsedArgs)
}

// ListMethods returns a slice of method names, sorted alphabetically.
func ListMethods() []string {
return AllMethods.List()
}

// MethodDocs returns a slice of specs, one for each method.
func MethodDocs() []MethodSpec {
return AllMethods.Docs()
Expand Down
14 changes: 12 additions & 2 deletions internal/bloblang/query/method_set_test.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
package query

import (
"sort"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func listMethods(m *MethodSet) []string {
methodNames := make([]string, 0, len(m.constructors))
for k := range m.constructors {
methodNames = append(methodNames, k)
}
sort.Strings(methodNames)
return methodNames
}

func TestMethodSetWithout(t *testing.T) {
setOne := AllMethods
setTwo := setOne.Without("explode")

assert.Contains(t, setOne.List(), "explode")
assert.NotContains(t, setTwo.List(), "explode")
assert.Contains(t, listMethods(setOne), "explode")
assert.NotContains(t, listMethods(setTwo), "explode")

explodeParamSpec, _ := setOne.Params("explode")
explodeParams, err := explodeParamSpec.PopulateNameless("foo.bar")
Expand Down
42 changes: 12 additions & 30 deletions internal/bloblang/query/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ import (

// ParamDefinition describes a single parameter for a function or method.
type ParamDefinition struct {
Name string
Description string
ValueType ValueType
Name string `json:"name"`
Description string `json:"description"`
ValueType ValueType `json:"type"`

castScalarsToLiteral bool

// IsOptional is implicit when there's a DefaultValue. However, there are
// times when a parameter is used to change behaviour without having a
// default.
IsOptional bool
DefaultValue *interface{}
IsOptional bool `json:"is_optional"`
DefaultValue *interface{} `json:"default,omitempty"`
}

func (d ParamDefinition) validate() error {
Expand Down Expand Up @@ -164,9 +164,8 @@ func (d ParamDefinition) parseArgValue(v interface{}) (interface{}, error) {

// Params defines the expected arguments of a function or method.
type Params struct {
oldStyle bool
variadic bool
Definitions []ParamDefinition
Variadic bool `json:"variadic"`
Definitions []ParamDefinition `json:"named,omitempty"`

// Used by parsed param frames, we instantiate this here so that it's
// allocated only once at parse time rather than execution time.
Expand All @@ -184,18 +183,7 @@ func NewParams() Params {
// nameless arguments are considered valid.
func VariadicParams() Params {
return Params{
variadic: true,
nameToIndex: map[string]int{},
}
}

// OldStyleParams returns a parameters spec that simply passes through arguments
// for the old style constructors to chomp on.
//
// TODO: Eventually phase this out.
func OldStyleParams() Params {
return Params{
oldStyle: true,
Variadic: true,
nameToIndex: map[string]int{},
}
}
Expand Down Expand Up @@ -240,10 +228,7 @@ func (p Params) PopulateNamed(args map[string]interface{}) (*ParsedParams, error
}

func (p Params) validate() error {
if p.oldStyle && len(p.Definitions) > 0 {
return errors.New("cannot add named parameters to an old style constructor")
}
if p.variadic && len(p.Definitions) > 0 {
if p.Variadic && len(p.Definitions) > 0 {
return errors.New("cannot add named parameters to a variadic parameter definition")
}

Expand All @@ -267,10 +252,7 @@ type dynamicArgIndex struct {
}

func (p Params) gatherDynamicArgs(args []interface{}) (dynArgs []dynamicArgIndex) {
if p.oldStyle {
return nil
}
if p.variadic {
if p.Variadic {
for i, arg := range args {
if fn, isFn := arg.(Function); isFn {
dynArgs = append(dynArgs, dynamicArgIndex{index: i, fn: fn})
Expand Down Expand Up @@ -300,7 +282,7 @@ func expandLiteralArgs(args []interface{}) {
// processNameless attempts to validate a list of unnamed arguments, and
// populates elements with default values if they are omitted.
func (p Params) processNameless(args []interface{}) ([]interface{}, error) {
if p.oldStyle || p.variadic {
if p.Variadic {
expandLiteralArgs(args)
return args, nil
}
Expand Down Expand Up @@ -358,7 +340,7 @@ func (p Params) processNameless(args []interface{}) ([]interface{}, error) {
// processNamed attempts to validate a map of named arguments, and populates
// elements with default values if they are omitted.
func (p Params) processNamed(args map[string]interface{}) ([]interface{}, error) {
if p.oldStyle || p.variadic {
if p.Variadic {
return nil, errors.New("named arguments are not supported")
}

Expand Down
32 changes: 0 additions & 32 deletions internal/bloblang/query/params_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,6 @@ func TestParamsValidation(t *testing.T) {
Add(ParamArray("sixth", "")).
Add(ParamObject("seventh", "")),
},
{
name: "old style with fields",
params: OldStyleParams().
Add(ParamString("first", "")).
Add(ParamInt64("second", "").Default(5)).
Add(ParamBool("third", "").Default(true)),
errContains: "cannot add named parameters to an old style",
},
{
name: "variadic with fields",
params: VariadicParams().
Expand Down Expand Up @@ -205,16 +197,6 @@ func TestParamsNameless(t *testing.T) {
"foo", int64(7), false,
},
},
{
name: "old style args expanded",
params: OldStyleParams(),
input: []interface{}{
"foo", NewLiteralFunction("testing", int64(7)), false,
},
output: []interface{}{
"foo", int64(7), false,
},
},
{
name: "variadic args expanded",
params: VariadicParams(),
Expand Down Expand Up @@ -390,20 +372,6 @@ func TestParamsNamed(t *testing.T) {
}
}

func TestOldStyleParams(t *testing.T) {
p := Params{oldStyle: true}

exp := []interface{}{
"foo", 20, 34.5, true,
}
res, err := p.processNameless(exp)
require.NoError(t, err)
assert.Equal(t, exp, res)

_, err = p.processNamed(nil)
require.Error(t, err)
}

func TestDynamicArgs(t *testing.T) {
p := NewParams().
Add(ParamString("foo", "")).
Expand Down
Loading

0 comments on commit b24bb5d

Please sign in to comment.