Skip to content

Commit d4781e3

Browse files
authored
Allow the DefaultCondition on events to be string or object (#163)
fixes #157 Signed-off-by: Spolti <filippespolti@gmail.com>
1 parent d6ebe51 commit d4781e3

File tree

8 files changed

+97
-39
lines changed

8 files changed

+97
-39
lines changed

kubernetes/k8s_workflow_integration.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,24 @@ import (
2626
// github.com/serverlessworkflow/sdk-go/model/event.go:51:2: encountered struct field "" without JSON tag in type "Event"
2727
// github.com/serverlessworkflow/sdk-go/model/states.go:66:12: unsupported AST kind *ast.InterfaceType
2828

29+
// States should be objects that will be in the same array even if it belongs to
30+
// different types. An issue similar to the below will happen when trying to deploy your custom CR:
31+
// strict decoding error: unknown field "spec.states[0].dataConditions"
32+
// To make the CRD is compliant to the specs there are two options,
33+
// a flat struct with all states fields at the same level,
34+
// or use the // +kubebuilder:pruning:PreserveUnknownFields
35+
// kubebuilder validator and delegate the validation to the sdk-go validator using the admission webhook.
36+
// TODO add a webhook example
37+
2938
// ServerlessWorkflowSpec defines a base API for integration test with operator-sdk
3039
type ServerlessWorkflowSpec struct {
31-
BaseWorkflow model.BaseWorkflow `json:"inline"`
40+
BaseWorkflow model.BaseWorkflow `json:",inline"`
3241
Events []model.Event `json:"events,omitempty"`
3342
Functions []model.Function `json:"functions,omitempty"`
3443
Retries []model.Retry `json:"retries,omitempty"`
35-
States []model.State `json:"states"`
44+
// +kubebuilder:validation:MinItems=1
45+
// +kubebuilder:pruning:PreserveUnknownFields
46+
States []model.State `json:"states"`
3647
}
3748

3849
// ServerlessWorkflow ...

model/function.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ type Function struct {
5252
// <path_to_custom_script>#<custom_service_method>.
5353
// +kubebuilder:validation:Required
5454
Operation string `json:"operation" validate:"required,oneof=rest rpc expression"`
55-
// Defines the function type. Is either `rest`, `rpc`, `expression`, `graphql`, `asyncapi`, `asyncapi` or `asyncapi`.
55+
// Defines the function type. Is either `custom`, `rest`, `rpc`, `expression`, `graphql`, `asyncapi`, `asyncapi` or `asyncapi`.
5656
// Default is `rest`.
57-
// +kubebuilder:validation:Enum=rest;rpc;expression;graphql;asyncapi;asyncapi;asyncapi
57+
// +kubebuilder:validation:Enum=rest;rpc;expression;graphql;asyncapi;asyncapi;asyncapi;custom
5858
// +kubebuilder:default=rest
5959
Type FunctionType `json:"type,omitempty"`
6060
// References an auth definition name to be used to access to resource defined in the operation parameter.

model/object.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ import (
3333
//
3434
// +kubebuilder:validation:Type=object
3535
type Object struct {
36-
Type Type `json:",inline"`
37-
IntVal int32 `json:",inline"`
38-
StrVal string `json:",inline"`
39-
RawValue json.RawMessage `json:",inline"`
36+
Type Type `json:"type,inline"`
37+
IntVal int32 `json:"intVal,inline"`
38+
StrVal string `json:"strVal,inline"`
39+
RawValue json.RawMessage `json:"rawValue,inline"`
4040
}
4141

4242
type Type int64

model/retry.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type Retry struct {
3232
// Static value by which the delay increases during each attempt (ISO 8601 time format)
3333
Increment string `json:"increment,omitempty" validate:"omitempty,iso8601duration"`
3434
// Numeric value, if specified the delay between retries is multiplied by this value.
35+
// +optional
3536
Multiplier *floatstr.Float32OrString `json:"multiplier,omitempty" validate:"omitempty,min=1"`
3637
// Maximum number of retry attempts.
3738
// +kubebuilder:validation:Required

model/switch_state.go

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,36 @@ type SwitchState struct {
3636
Timeouts *SwitchStateTimeout `json:"timeouts,omitempty"`
3737
}
3838

39+
// DefaultCondition Can be either a transition or end definition
40+
type DefaultCondition struct {
41+
// Serverless workflow states can have one or more incoming and outgoing transitions (from/to other states).
42+
// Each state can define a transition definition that is used to determine which state to transition to next.
43+
// +optional
44+
Transition *Transition `json:"transition,omitempty"`
45+
// If this state an end state
46+
// +optional
47+
End *End `json:"end,omitempty"`
48+
}
49+
50+
// UnmarshalJSON ...
51+
func (e *DefaultCondition) UnmarshalJSON(data []byte) error {
52+
type defCondUnmarshal DefaultCondition
53+
54+
obj, str, err := primitiveOrStruct[string, defCondUnmarshal](data)
55+
if err != nil {
56+
return err
57+
}
58+
59+
if obj == nil {
60+
transition := &Transition{NextState: str}
61+
e.Transition = transition
62+
} else {
63+
*e = DefaultCondition(*obj)
64+
}
65+
66+
return nil
67+
}
68+
3969
func (s *SwitchState) MarshalJSON() ([]byte, error) {
4070
type Alias SwitchState
4171
custom, err := json.Marshal(&struct {
@@ -48,17 +78,6 @@ func (s *SwitchState) MarshalJSON() ([]byte, error) {
4878
return custom, err
4979
}
5080

51-
// DefaultCondition Can be either a transition or end definition
52-
type DefaultCondition struct {
53-
// Serverless workflow states can have one or more incoming and outgoing transitions (from/to other states).
54-
// Each state can define a transition definition that is used to determine which state to transition to next.
55-
// +optional
56-
Transition *Transition `json:"transition,omitempty"`
57-
// If this state an end state
58-
// +optional
59-
End *End `json:"end,omitempty"`
60-
}
61-
6281
// SwitchStateTimeout defines the specific timeout settings for switch state
6382
type SwitchStateTimeout struct {
6483
// Default workflow state execution timeout (ISO 8601 duration format)
@@ -107,5 +126,5 @@ type DataCondition struct {
107126
// Explicit transition to end
108127
End *End `json:"end" validate:"omitempty"`
109128
// Workflow transition if condition is evaluated to true
110-
Transition *Transition `json:"transition" validate:"omitempty"`
129+
Transition *Transition `json:"transition,omitempty" validate:"omitempty"`
111130
}

model/workflow.go

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ type BaseWorkflow struct {
8686
DataInputSchema *DataInputSchema `json:"dataInputSchema,omitempty"`
8787
// Serverless Workflow schema version
8888
// +kubebuilder:validation:Required
89+
// +kubebuilder:default="0.8"
8990
SpecVersion string `json:"specVersion" validate:"required"`
9091
// Secrets allow you to access sensitive information, such as passwords, OAuth tokens, ssh keys, etc,
9192
// inside your Workflow Expressions.
@@ -501,26 +502,19 @@ type Transition struct {
501502
}
502503

503504
// UnmarshalJSON ...
504-
func (t *Transition) UnmarshalJSON(data []byte) error {
505-
transitionMap := make(map[string]json.RawMessage)
506-
if err := json.Unmarshal(data, &transitionMap); err != nil {
507-
t.NextState, err = unmarshalString(data)
508-
if err != nil {
509-
return err
510-
}
511-
return nil
512-
}
505+
func (e *Transition) UnmarshalJSON(data []byte) error {
506+
type defTransitionUnmarshal Transition
513507

514-
if err := unmarshalKey("compensate", transitionMap, &t.Compensate); err != nil {
515-
return err
516-
}
517-
if err := unmarshalKey("produceEvents", transitionMap, &t.ProduceEvents); err != nil {
518-
return err
519-
}
520-
if err := unmarshalKey("nextState", transitionMap, &t.NextState); err != nil {
508+
obj, str, err := primitiveOrStruct[string, defTransitionUnmarshal](data)
509+
if err != nil {
521510
return err
522511
}
523512

513+
if obj == nil {
514+
e.NextState = str
515+
} else {
516+
*e = Transition(*obj)
517+
}
524518
return nil
525519
}
526520

parser/parser_test.go

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package parser
1616

1717
import (
1818
"encoding/json"
19+
"fmt"
1920
"os"
2021
"path/filepath"
2122
"strings"
@@ -556,6 +557,14 @@ func TestFromFile(t *testing.T) {
556557
assert.Equal(t, "PT100S", w.States[9].SleepState.Timeouts.StateExecTimeout.Total)
557558
assert.Equal(t, "PT200S", w.States[9].SleepState.Timeouts.StateExecTimeout.Single)
558559
assert.Equal(t, true, w.States[9].End.Terminate)
560+
561+
// switch state with DefaultCondition as string
562+
assert.NotEmpty(t, w.States[10].SwitchState)
563+
assert.Equal(t, "HelloStateWithDefaultConditionString", w.States[10].Name)
564+
assert.Equal(t, "${ true }", w.States[10].SwitchState.DataConditions[0].Condition)
565+
assert.Equal(t, "HandleApprovedVisa", w.States[10].SwitchState.DataConditions[0].Transition.NextState)
566+
assert.Equal(t, "SendTextForHighPriority", w.States[10].SwitchState.DefaultCondition.Transition.NextState)
567+
assert.Equal(t, true, w.States[10].End.Terminate)
559568
},
560569
},
561570
}
@@ -815,7 +824,17 @@ states:
815824
single: PT20S
816825
defaultCondition:
817826
transition:
818-
nextState: CheckCreditCallback
827+
nextState: HelloStateWithDefaultConditionString
828+
- name: HelloStateWithDefaultConditionString
829+
type: switch
830+
dataConditions:
831+
- condition: ${ true }
832+
transition:
833+
nextState: HandleApprovedVisa
834+
- condition: ${ false }
835+
transition:
836+
nextState: HandleRejectedVisa
837+
defaultCondition: SendTextForHighPriority
819838
- name: SendTextForHighPriority
820839
type: foreach
821840
inputCollection: "${ .messages }"
@@ -911,6 +930,7 @@ states:
911930
terminate: true
912931
`))
913932
assert.Nil(t, err)
933+
fmt.Println(err)
914934
assert.NotNil(t, workflow)
915935
b, err := json.Marshal(workflow)
916936

@@ -936,7 +956,10 @@ states:
936956
assert.True(t, strings.Contains(string(b), "{\"name\":\"ParallelExec\",\"type\":\"parallel\",\"transition\":{\"nextState\":\"CheckVisaStatusSwitchEventBased\"},\"branches\":[{\"name\":\"ShortDelayBranch\",\"actions\":[{\"subFlowRef\":{\"workflowId\":\"shortdelayworkflowid\",\"invoke\":\"sync\",\"onParentComplete\":\"terminate\"},\"actionDataFilter\":{\"useResults\":true}}],\"timeouts\":{\"actionExecTimeout\":\"PT5H\",\"branchExecTimeout\":\"PT6M\"}},{\"name\":\"LongDelayBranch\",\"actions\":[{\"subFlowRef\":{\"workflowId\":\"longdelayworkflowid\",\"invoke\":\"sync\",\"onParentComplete\":\"terminate\"},\"actionDataFilter\":{\"useResults\":true}}]}],\"completionType\":\"atLeast\",\"numCompleted\":13,\"timeouts\":{\"stateExecTimeout\":{\"single\":\"PT2S\",\"total\":\"PT1S\"},\"branchExecTimeout\":\"PT6M\"}}"))
937957

938958
// Switch State
939-
assert.True(t, strings.Contains(string(b), "{\"name\":\"CheckVisaStatusSwitchEventBased\",\"type\":\"switch\",\"defaultCondition\":{\"transition\":{\"nextState\":\"CheckCreditCallback\"}},\"eventConditions\":[{\"name\":\"visaApprovedEvent\",\"eventRef\":\"visaApprovedEventRef\",\"metadata\":{\"mastercard\":\"disallowed\",\"visa\":\"allowed\"},\"end\":null,\"transition\":{\"nextState\":\"HandleApprovedVisa\"}},{\"eventRef\":\"visaRejectedEvent\",\"metadata\":{\"test\":\"tested\"},\"end\":null,\"transition\":{\"nextState\":\"HandleRejectedVisa\"}}],\"dataConditions\":null,\"timeouts\":{\"stateExecTimeout\":{\"single\":\"PT20S\",\"total\":\"PT10S\"},\"eventTimeout\":\"PT10H\"}}"))
959+
assert.True(t, strings.Contains(string(b), "{\"name\":\"CheckVisaStatusSwitchEventBased\",\"type\":\"switch\",\"defaultCondition\":{\"transition\":{\"nextState\":\"HelloStateWithDefaultConditionString\"}},\"eventConditions\":[{\"name\":\"visaApprovedEvent\",\"eventRef\":\"visaApprovedEventRef\",\"metadata\":{\"mastercard\":\"disallowed\",\"visa\":\"allowed\"},\"end\":null,\"transition\":{\"nextState\":\"HandleApprovedVisa\"}},{\"eventRef\":\"visaRejectedEvent\",\"metadata\":{\"test\":\"tested\"},\"end\":null,\"transition\":{\"nextState\":\"HandleRejectedVisa\"}}],\"dataConditions\":null,\"timeouts\":{\"stateExecTimeout\":{\"single\":\"PT20S\",\"total\":\"PT10S\"},\"eventTimeout\":\"PT10H\"}}"))
960+
961+
// Switch State with string DefaultCondition
962+
assert.True(t, strings.Contains(string(b), "{\"name\":\"HelloStateWithDefaultConditionString\",\"type\":\"switch\",\"defaultCondition\":{\"transition\":{\"nextState\":\"SendTextForHighPriority\"}},\"eventConditions\":null,\"dataConditions\":[{\"condition\":\"${ true }\",\"end\":null,\"transition\":{\"nextState\":\"HandleApprovedVisa\"}},{\"condition\":\"${ false }\",\"end\":null,\"transition\":{\"nextState\":\"HandleRejectedVisa\"}}]}"))
940963

941964
// Foreach State
942965
assert.True(t, strings.Contains(string(b), "{\"name\":\"SendTextForHighPriority\",\"type\":\"foreach\",\"transition\":{\"nextState\":\"HelloInject\"},\"inputCollection\":\"${ .messages }\",\"outputCollection\":\"${ .outputMessages }\",\"iterationParam\":\"${ .this }\",\"batchSize\":45,\"actions\":[{\"name\":\"test\",\"functionRef\":{\"refName\":\"sendTextFunction\",\"arguments\":{\"message\":\"${ .singlemessage }\"},\"invoke\":\"sync\"},\"eventRef\":{\"triggerEventRef\":\"example1\",\"resultEventRef\":\"example2\",\"resultEventTimeout\":\"PT12H\",\"invoke\":\"sync\"},\"actionDataFilter\":{\"useResults\":true}}],\"mode\":\"sequential\",\"timeouts\":{\"stateExecTimeout\":{\"single\":\"PT22S\",\"total\":\"PT11S\"},\"actionExecTimeout\":\"PT11H\"}}"))
@@ -973,7 +996,7 @@ states:
973996
nextState: HandleRejectedVisa
974997
defaultCondition:
975998
transition:
976-
nextState: HandleNoVisaDecision
999+
nextState: HandleApprovedVisa
9771000
- name: HandleApprovedVisa
9781001
type: operation
9791002
actions:

parser/testdata/workflows/greetings-v08-spec.sw.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,3 +211,13 @@ states:
211211
single: PT200S
212212
end:
213213
terminate: true
214+
- name: HelloStateWithDefaultConditionString
215+
type: switch
216+
dataConditions:
217+
- condition: ${ true }
218+
transition: HandleApprovedVisa
219+
- condition: ${ false }
220+
transition:
221+
nextState: HandleRejectedVisa
222+
defaultCondition: SendTextForHighPriority
223+
end: true

0 commit comments

Comments
 (0)