Skip to content

Commit b1933e7

Browse files
author
André R. de Miranda
authored
Support for validating structs created by builder (#198)
* Support to validate any struct Signed-off-by: André R. de Miranda <[email protected]> * Use custom messages in validation Signed-off-by: André R. de Miranda <[email protected]> --------- Signed-off-by: André R. de Miranda <[email protected]>
1 parent 48c5d8e commit b1933e7

File tree

5 files changed

+82
-38
lines changed

5 files changed

+82
-38
lines changed

builder/builder.go

+17-9
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,12 @@ func New() *model.WorkflowBuilder {
2727
return model.NewWorkflowBuilder()
2828
}
2929

30-
func Object(builder *model.WorkflowBuilder) (*model.Workflow, error) {
31-
workflow := builder.Build()
32-
ctx := model.NewValidatorContext(&workflow)
33-
if err := val.GetValidator().StructCtx(ctx, workflow); err != nil {
30+
func Yaml(builder *model.WorkflowBuilder) ([]byte, error) {
31+
data, err := Json(builder)
32+
if err != nil {
3433
return nil, err
3534
}
36-
return &workflow, nil
35+
return yaml.JSONToYAML(data)
3736
}
3837

3938
func Json(builder *model.WorkflowBuilder) ([]byte, error) {
@@ -44,10 +43,19 @@ func Json(builder *model.WorkflowBuilder) ([]byte, error) {
4443
return json.Marshal(workflow)
4544
}
4645

47-
func Yaml(builder *model.WorkflowBuilder) ([]byte, error) {
48-
data, err := Json(builder)
49-
if err != nil {
46+
func Object(builder *model.WorkflowBuilder) (*model.Workflow, error) {
47+
workflow := builder.Build()
48+
ctx := model.NewValidatorContext(&workflow)
49+
if err := val.GetValidator().StructCtx(ctx, workflow); err != nil {
5050
return nil, err
5151
}
52-
return yaml.JSONToYAML(data)
52+
return &workflow, nil
53+
}
54+
55+
func Validate(object interface{}) error {
56+
ctx := model.NewValidatorContext(object)
57+
if err := val.GetValidator().StructCtx(ctx, object); err != nil {
58+
return val.WorkflowError(err)
59+
}
60+
return nil
5361
}

builder/builder_test.go

+20-1
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ package builder
1717
import (
1818
"testing"
1919

20-
"github.com/serverlessworkflow/sdk-go/v2/model"
2120
"github.com/stretchr/testify/assert"
21+
22+
"github.com/serverlessworkflow/sdk-go/v2/model"
23+
val "github.com/serverlessworkflow/sdk-go/v2/validator"
2224
)
2325

2426
func prepareBuilder() *model.WorkflowBuilder {
@@ -43,6 +45,23 @@ func prepareBuilder() *model.WorkflowBuilder {
4345
return builder
4446
}
4547

48+
func TestValidate(t *testing.T) {
49+
state1 := model.NewStateBuilder().
50+
Name("state").
51+
Type(model.StateTypeInject)
52+
state1.End().Terminate(true)
53+
err := Validate(state1)
54+
assert.NoError(t, err)
55+
56+
state2 := model.NewStateBuilder().
57+
Type(model.StateTypeInject)
58+
state2.End().Terminate(true)
59+
err = Validate(state2.Build())
60+
if assert.Error(t, err) {
61+
assert.Equal(t, "state.name is required", err.(val.WorkflowErrors)[0].Error())
62+
}
63+
}
64+
4665
func TestObject(t *testing.T) {
4766
workflow, err := Object(prepareBuilder())
4867
if assert.NoError(t, err) {

model/workflow_validator.go

+40-24
Original file line numberDiff line numberDiff line change
@@ -75,68 +75,84 @@ func (c *ValidatorContext) init(workflow *Workflow) {
7575
}
7676

7777
func (c *ValidatorContext) ExistState(name string) bool {
78+
if c.States == nil {
79+
return true
80+
}
7881
_, ok := c.States[name]
7982
return ok
8083
}
8184

8285
func (c *ValidatorContext) ExistFunction(name string) bool {
86+
if c.Functions == nil {
87+
return true
88+
}
8389
_, ok := c.Functions[name]
8490
return ok
8591
}
8692

8793
func (c *ValidatorContext) ExistEvent(name string) bool {
94+
if c.Events == nil {
95+
return true
96+
}
8897
_, ok := c.Events[name]
8998
return ok
9099
}
91100

92101
func (c *ValidatorContext) ExistRetry(name string) bool {
102+
if c.Retries == nil {
103+
return true
104+
}
93105
_, ok := c.Retries[name]
94106
return ok
95107
}
96108

97109
func (c *ValidatorContext) ExistError(name string) bool {
110+
if c.Errors == nil {
111+
return true
112+
}
98113
_, ok := c.Errors[name]
99114
return ok
100115
}
101116

102-
func NewValidatorContext(workflow *Workflow) context.Context {
103-
for i := range workflow.States {
104-
s := &workflow.States[i]
105-
if s.BaseState.Transition != nil {
106-
s.BaseState.Transition.stateParent = s
107-
}
108-
for _, onError := range s.BaseState.OnErrors {
109-
if onError.Transition != nil {
110-
onError.Transition.stateParent = s
111-
}
112-
}
113-
if s.Type == StateTypeSwitch {
114-
if s.SwitchState.DefaultCondition.Transition != nil {
115-
s.SwitchState.DefaultCondition.Transition.stateParent = s
117+
func NewValidatorContext(object any) context.Context {
118+
contextValue := ValidatorContext{}
119+
120+
if workflow, ok := object.(*Workflow); ok {
121+
for i := range workflow.States {
122+
s := &workflow.States[i]
123+
if s.BaseState.Transition != nil {
124+
s.BaseState.Transition.stateParent = s
116125
}
117-
for _, e := range s.SwitchState.EventConditions {
118-
if e.Transition != nil {
119-
e.Transition.stateParent = s
126+
for _, onError := range s.BaseState.OnErrors {
127+
if onError.Transition != nil {
128+
onError.Transition.stateParent = s
120129
}
121130
}
122-
for _, d := range s.SwitchState.DataConditions {
123-
if d.Transition != nil {
124-
d.Transition.stateParent = s
131+
if s.Type == StateTypeSwitch {
132+
if s.SwitchState.DefaultCondition.Transition != nil {
133+
s.SwitchState.DefaultCondition.Transition.stateParent = s
134+
}
135+
for _, e := range s.SwitchState.EventConditions {
136+
if e.Transition != nil {
137+
e.Transition.stateParent = s
138+
}
139+
}
140+
for _, d := range s.SwitchState.DataConditions {
141+
if d.Transition != nil {
142+
d.Transition.stateParent = s
143+
}
125144
}
126145
}
127146
}
147+
contextValue.init(workflow)
128148
}
129149

130-
contextValue := ValidatorContext{}
131-
contextValue.init(workflow)
132-
133150
return context.WithValue(context.Background(), ValidatorContextValue, contextValue)
134151
}
135152

136153
func init() {
137154
// TODO: create states graph to complex check
138155

139-
// val.GetValidator().RegisterStructValidationCtx(val.ValidationWrap(nil, workflowStructLevelValidation), Workflow{})
140156
val.GetValidator().RegisterStructValidationCtx(ValidationWrap(onErrorStructLevelValidationCtx), OnError{})
141157
val.GetValidator().RegisterStructValidationCtx(ValidationWrap(transitionStructLevelValidationCtx), Transition{})
142158
val.GetValidator().RegisterStructValidationCtx(ValidationWrap(startStructLevelValidationCtx), Start{})

parser/parser.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func FromJSONSource(source []byte) (workflow *model.Workflow, err error) {
5353

5454
ctx := model.NewValidatorContext(workflow)
5555
if err := val.GetValidator().StructCtx(ctx, workflow); err != nil {
56-
return nil, err
56+
return nil, val.WorkflowError(err)
5757
}
5858
return workflow, nil
5959
}

parser/parser_test.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -1016,7 +1016,7 @@ states:
10161016
// Make sure that the Action FunctionRef is unmarshalled correctly
10171017
assert.Equal(t, model.FromString("${ .singlemessage }"), workflow.States[5].ForEachState.Actions[0].FunctionRef.Arguments["message"])
10181018
assert.Equal(t, "sendTextFunction", workflow.States[5].ForEachState.Actions[0].FunctionRef.RefName)
1019-
assert.Nil(t, err)
1019+
assert.NoError(t, err)
10201020

10211021
})
10221022

@@ -1063,8 +1063,9 @@ states:
10631063
end:
10641064
terminate: true
10651065
`))
1066-
assert.Error(t, err)
1067-
assert.Regexp(t, `validation for \'DataConditions\' failed on the \'required\' tag`, err)
1066+
if assert.Error(t, err) {
1067+
assert.Equal(t, `workflow.states[0].switchState.dataConditions is required`, err.Error())
1068+
}
10681069
assert.Nil(t, workflow)
10691070
})
10701071

0 commit comments

Comments
 (0)