Skip to content

Commit

Permalink
Merge pull request #122 from jexia/feat/template-strings
Browse files Browse the repository at this point in the history
Introducing template strings and strconcat function
  • Loading branch information
jeroenrinzema authored Sep 4, 2020
2 parents 405df6b + 81e84fd commit 454cece
Show file tree
Hide file tree
Showing 8 changed files with 262 additions and 6 deletions.
2 changes: 2 additions & 0 deletions cmd/semaphore/daemon/config/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package config
import (
"github.com/jexia/semaphore"
"github.com/jexia/semaphore/cmd/semaphore/daemon/providers"
"github.com/jexia/semaphore/cmd/semaphore/functions"
"github.com/jexia/semaphore/cmd/semaphore/middleware"
"github.com/jexia/semaphore/pkg/broker"
"github.com/jexia/semaphore/pkg/broker/logger"
Expand Down Expand Up @@ -119,6 +120,7 @@ func NewCore(ctx *broker.Context, flags *Daemon) (semaphore.Options, error) {
semaphore.WithCaller(micro.NewCaller("micro-grpc", microGRPC.NewService())),
semaphore.WithCaller(grpc.NewCaller()),
semaphore.WithCaller(http.NewCaller()),
semaphore.WithFunctions(functions.Default),
}

for _, path := range flags.Files {
Expand Down
43 changes: 43 additions & 0 deletions cmd/semaphore/functions/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package functions

import (
"fmt"

"github.com/jexia/semaphore/pkg/prettyerr"
"github.com/jexia/semaphore/pkg/specs"
"github.com/jexia/semaphore/pkg/specs/types"
)

type wrapErr struct {
Inner error
}

func (i wrapErr) Unwrap() error {
return i.Inner
}

// ErrInvalidArgument is thrown when a given argument is invalid
type ErrInvalidArgument struct {
wrapErr
Function string
Property *specs.Property
Expected types.Type
}

// Error returns a description of the given error as a string
func (e ErrInvalidArgument) Error() string {
return fmt.Sprintf("invalid argument %s in %s expected %s", e.Property.Type, e.Function, e.Expected)
}

// Prettify returns the prettified version of the given error
func (e ErrInvalidArgument) Prettify() prettyerr.Error {
return prettyerr.Error{
Code: "InvalidArgument",
Message: e.Error(),
Details: map[string]interface{}{
"Function": e.Function,
"Type": e.Property.Type,
"Expected": e.Expected,
},
}
}
8 changes: 8 additions & 0 deletions cmd/semaphore/functions/functions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package functions

import "github.com/jexia/semaphore/pkg/functions"

// Default represents the default functions collection
var Default = functions.Custom{
"strconcat": Strconcat,
}
60 changes: 60 additions & 0 deletions cmd/semaphore/functions/strconcat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package functions

import (
"strings"

"github.com/jexia/semaphore/pkg/functions"
"github.com/jexia/semaphore/pkg/references"
"github.com/jexia/semaphore/pkg/specs"
"github.com/jexia/semaphore/pkg/specs/labels"
"github.com/jexia/semaphore/pkg/specs/types"
)

// Strconcat compiles the given arguments and constructs a new executable
// function for the given arguments.
func Strconcat(args ...*specs.Property) (*specs.Property, functions.Exec, error) {
result := &specs.Property{
Name: "concat",
Type: types.String,
Label: labels.Optional,
}

for _, arg := range args {
if arg.Type != types.String {
return nil, nil, ErrInvalidArgument{
Property: arg,
Expected: types.String,
Function: "strconcat",
}
}
}

handle := func(store references.Store) error {
result := strings.Builder{}

for _, arg := range args {
var value string

if arg.Default != nil {
value = arg.Default.(string)
}

if arg.Reference != nil {
ref := store.Load(arg.Reference.Resource, arg.Reference.Path)
if ref != nil {
value = ref.Value.(string)
}
}

_, err := result.WriteString(value)
if err != nil {
return err
}
}

store.StoreValue("", ".", result.String())
return nil
}

return result, handle, nil
}
17 changes: 12 additions & 5 deletions pkg/lookup/lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,11 @@ func GetAvailableResources(flow specs.FlowInterface, breakpoint string) map[stri
}

if breakpoint == template.OutputResource {
references[template.ErrorResource] = ReferenceMap{
template.ResponseResource: OnErrLookup(template.OutputResource, flow.GetOnError()),
}

if flow.GetOnError() != nil {
references[template.ErrorResource][template.ParamsResource] = ParamsLookup(flow.GetOnError().Params, flow, "")
references[template.ErrorResource] = ReferenceMap{
template.ResponseResource: OnErrLookup(template.OutputResource, flow.GetOnError()),
template.ParamsResource: ParamsLookup(flow.GetOnError().Params, flow, ""),
}
}
}

Expand Down Expand Up @@ -138,6 +137,14 @@ func GetAvailableResources(flow specs.FlowInterface, breakpoint string) map[stri
}
}

if flow.GetOutput() != nil {
if flow.GetOutput().Stack != nil {
for key, returns := range flow.GetOutput().Stack {
references[template.StackResource][key] = PropertyLookup(returns)
}
}
}

return references
}

Expand Down
43 changes: 43 additions & 0 deletions pkg/lookup/lookup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,32 @@ func NewMockFlow(name string) *specs.Flow {
Input: &specs.ParameterMap{
Property: NewInputMockProperty(),
},
OnError: &specs.OnError{
Response: &specs.ParameterMap{
Property: NewResultMockProperty(),
Params: map[string]*specs.Property{
"message": {
Path: "message",
Default: "hello world",
Type: types.String,
Label: labels.Optional,
},
"name": {
Path: "message",
Default: "hello world",
Type: types.String,
Label: labels.Optional,
},
"reference": {
Path: "reference",
Reference: &specs.PropertyReference{
Resource: name,
Path: "message",
},
},
},
},
},
Nodes: specs.NodeList{
NewMockCall("first"),
NewMockCall("second"),
Expand Down Expand Up @@ -508,6 +534,23 @@ func TestGetAvailableResources(t *testing.T) {
result := GetAvailableResources(flow, "output")
return expected, result
},
"output only": func() ([]string, map[string]ReferenceMap) {
flow := NewMockFlow("first")

flow.OnError = nil
flow.Input = nil
flow.Nodes = nil
flow.Output = &specs.ParameterMap{
Stack: map[string]*specs.Property{
"hash": NewResultMockProperty(),
},
}

expected := []string{template.StackResource}

result := GetAvailableResources(flow, "output")
return expected, result
},
"stack lookup request": func() ([]string, map[string]ReferenceMap) {
flow := NewMockFlow("first")
expected := []string{template.StackResource, template.ErrorResource, "input", "first", "second", "third"}
Expand Down
16 changes: 16 additions & 0 deletions pkg/specs/template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@ import (
"github.com/jexia/semaphore/pkg/broker/logger"
"github.com/jexia/semaphore/pkg/broker/trace"
"github.com/jexia/semaphore/pkg/specs"
"github.com/jexia/semaphore/pkg/specs/labels"
"github.com/jexia/semaphore/pkg/specs/types"
"go.uber.org/zap"
)

var (
// ReferencePattern is the matching pattern for references
ReferencePattern = regexp.MustCompile(`^[a-zA-Z0-9_\-\.]*:[a-zA-Z0-9\^\&\%\$@_\-\.]*$`)
// StringPattern is the matching pattern for strings
StringPattern = regexp.MustCompile(`^\'(.+)\'$`)
)

const (
Expand Down Expand Up @@ -117,6 +121,18 @@ func ParseContent(path string, name string, content string) (*specs.Property, er
return ParseReference(path, name, content)
}

if StringPattern.MatchString(content) {
matched := StringPattern.FindStringSubmatch(content)

return &specs.Property{
Name: name,
Path: path,
Type: types.String,
Label: labels.Optional,
Default: matched[1],
}, nil
}

return &specs.Property{
Name: name,
Path: path,
Expand Down
79 changes: 78 additions & 1 deletion pkg/specs/template/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"github.com/jexia/semaphore/pkg/broker"
"github.com/jexia/semaphore/pkg/broker/logger"
"github.com/jexia/semaphore/pkg/specs"
"github.com/jexia/semaphore/pkg/specs/labels"
"github.com/jexia/semaphore/pkg/specs/types"
)

func CompareProperties(t *testing.T, left specs.Property, right specs.Property) {
Expand Down Expand Up @@ -55,6 +57,81 @@ func TestGetTemplateContent(t *testing.T) {
}
}

func TestParseTemplateContent(t *testing.T) {
name := ""
path := "message"

tests := map[string]specs.Property{
"'prefix'": {
Name: name,
Path: path,
Type: types.String,
Label: labels.Optional,
Default: "prefix",
},
"'edge''": {
Name: name,
Path: path,
Type: types.String,
Label: labels.Optional,
Default: "edge'",
},
"input:message": {
Name: name,
Path: path,
Reference: &specs.PropertyReference{
Resource: "input",
Path: "message",
},
},
"input:user-id": {
Name: name,
Path: path,
Reference: &specs.PropertyReference{
Resource: "input",
Path: "user-id",
},
},
"input.header:Authorization": {
Name: name,
Path: path,
Reference: &specs.PropertyReference{
Resource: "input.header",
Path: "authorization",
},
},
"input.header:User-Id": {
Name: name,
Path: path,
Reference: &specs.PropertyReference{
Resource: "input.header",
Path: "user-id",
},
},
"input.header:": {
Path: path,
Reference: &specs.PropertyReference{
Resource: "input.header",
},
},
}

for input, expected := range tests {
t.Run(input, func(t *testing.T) {
property, err := ParseContent(path, name, input)
if err != nil {
t.Fatal(err)
}

if property.Path != expected.Path {
t.Errorf("unexpected path '%s', expected '%s'", property.Path, expected.Path)
}

CompareProperties(t, *property, expected)
})
}
}

func TestParseReference(t *testing.T) {
name := ""
path := "message"
Expand Down Expand Up @@ -166,7 +243,7 @@ func TestUnknownReferencePattern(t *testing.T) {
}
}

func TestParseTemplate(t *testing.T) {
func TestParseReferenceTemplates(t *testing.T) {
name := ""

tests := map[string]specs.Property{
Expand Down

0 comments on commit 454cece

Please sign in to comment.