Skip to content

Commit

Permalink
Workflow state requires "State.End" ou "State.Transition"
Browse files Browse the repository at this point in the history
Signed-off-by: André R. de Miranda <andre@galgo.tech>
  • Loading branch information
ribeiromiranda committed Mar 9, 2023
1 parent 09bb4cb commit b22bdad
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 17 deletions.
23 changes: 22 additions & 1 deletion model/states.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,29 @@ import (
"encoding/json"
"fmt"
"strings"

validator "github.com/go-playground/validator/v10"
val "github.com/serverlessworkflow/sdk-go/v2/validator"
)

func init() {
val.GetValidator().RegisterStructValidation(stateStructLevelValidation, State{})
}

func stateStructLevelValidation(structLevel validator.StructLevel) {
state := structLevel.Current().Interface().(State)

hasTransition := state.Transition != nil
isEnd := state.End != nil && state.End.Terminate

// TODO: Improve message errors
if !hasTransition && !isEnd {
structLevel.ReportError(nil, "State.Transition,State.End", "transition,end", "required", "")
} else if hasTransition && isEnd {
structLevel.ReportError(nil, "State.Transition", "transition", "required_without", "end")
}
}

// StateType ...
type StateType string

Expand Down Expand Up @@ -53,7 +74,7 @@ type BaseState struct {
// State type
Type StateType `json:"type" validate:"required"`
// States error handling and retries definitions
OnErrors []OnError `json:"onErrors,omitempty" validate:"omitempty,dive"`
OnErrors []OnError `json:"onErrors,omitempty" validate:"omitempty,dive"`
// Next transition of the workflow after the time delay
Transition *Transition `json:"transition,omitempty"`
// State data filter
Expand Down
108 changes: 108 additions & 0 deletions model/states_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Copyright 2022 The Serverless Workflow Specification Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package model

import (
"testing"

val "github.com/serverlessworkflow/sdk-go/v2/validator"
"github.com/stretchr/testify/assert"
)

var stateTransitionDefault = State{
BaseState: BaseState{
Name: "name state",
Type: StateTypeOperation,
Transition: &Transition{
NextState: "next name state",
},
},
OperationState: &OperationState{
ActionMode: "sequential",
Actions: []Action{
{},
},
},
}

var stateEndDefault = State{
BaseState: BaseState{
Name: "name state",
Type: StateTypeOperation,
End: &End{
Terminate: true,
},
},
OperationState: &OperationState{
ActionMode: "sequential",
Actions: []Action{
{},
},
},
}

func TestStateStructLevelValidation(t *testing.T) {
type testCase struct {
name string
instance State
err string
}

testCases := []testCase{
{
name: "state transition success",
instance: stateTransitionDefault,
err: ``,
},
{
name: "state end success",
instance: stateEndDefault,
err: ``,
},
{
name: "state end and transition",
instance: func() State {
s := stateTransitionDefault
s.End = stateEndDefault.End
return s
}(),
err: `Key: 'State.State.Transition' Error:Field validation for 'State.Transition' failed on the 'required_without' tag`,
},
{
name: "state without end and transition",
instance: func() State {
s := stateTransitionDefault
s.Transition = nil
return s
}(),
err: `Key: 'State.State.Transition,State.End' Error:Field validation for 'State.Transition,State.End' failed on the 'required' tag`,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := val.GetValidator().Struct(tc.instance)

if tc.err != "" {
assert.Error(t, err)
if err != nil {
assert.Equal(t, tc.err, err.Error())
}
return
}
assert.NoError(t, err)
})
}
}
10 changes: 5 additions & 5 deletions model/workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,17 +86,17 @@ func workflowStructLevelValidation(structLevel validator.StructLevel) {
}
}

// start state name exists in states list
// start state name exist in workflow states list
if wf.BaseWorkflow.Start.StateName != "" {
startExists := false
startExist := false
for _, state := range wf.States {
if state.Name == wf.BaseWorkflow.Start.StateName {
startExists = true
startExist = true
break
}
}
if !startExists {
structLevel.ReportError(reflect.ValueOf(wf.BaseWorkflow.Start.StateName), "Start", "start", "startnotexists", "")
if !startExist {
structLevel.ReportError(reflect.ValueOf(wf.BaseWorkflow.Start.StateName), "Start", "start", "startnotexist", "")
}
}
}
Expand Down
42 changes: 31 additions & 11 deletions model/workflow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,41 @@ var workflowStructDefault = Workflow{
StateName: "name state",
},
},
States: []State{{
BaseState: BaseState{
Name: "name state",
Type: StateTypeOperation,
States: []State{
{
BaseState: BaseState{
Name: "name state",
Type: StateTypeOperation,
Transition: &Transition{
NextState: "next name state",
},
},
OperationState: &OperationState{
ActionMode: "sequential",
Actions: []Action{
{},
},
},
},
OperationState: &OperationState{
ActionMode: "sequential",
Actions: []Action{
{},
{
BaseState: BaseState{
Name: "next name state",
Type: StateTypeOperation,
End: &End{
Terminate: true,
},
},
OperationState: &OperationState{
ActionMode: "sequential",
Actions: []Action{
{},
},
},
},
}},
},
}

func TestValidationAsStructLevelValidation(t *testing.T) {
func TestWorkflowStructLevelValidation(t *testing.T) {
type testCase[T any] struct {
name string
instance T
Expand Down Expand Up @@ -120,7 +140,7 @@ Key: 'Workflow.BaseWorkflow.Key' Error:Field validation for 'Key' failed on the
}
return w
}(),
err: `Key: 'Workflow.Start' Error:Field validation for 'Start' failed on the 'startnotexists' tag`,
err: `Key: 'Workflow.Start' Error:Field validation for 'Start' failed on the 'startnotexist' tag`,
},
{
name: "valid ContinueAs",
Expand Down

0 comments on commit b22bdad

Please sign in to comment.