Skip to content

Allow the DefaultCondition on events to be string or object #163

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions kubernetes/k8s_workflow_integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,24 @@ import (
// github.com/serverlessworkflow/sdk-go/model/event.go:51:2: encountered struct field "" without JSON tag in type "Event"
// github.com/serverlessworkflow/sdk-go/model/states.go:66:12: unsupported AST kind *ast.InterfaceType

// States should be objects that will be in the same array even if it belongs to
// different types. An issue similar to the below will happen when trying to deploy your custom CR:
// strict decoding error: unknown field "spec.states[0].dataConditions"
// To make the CRD is compliant to the specs there are two options,
// a flat struct with all states fields at the same level,
// or use the // +kubebuilder:pruning:PreserveUnknownFields
// kubebuilder validator and delegate the validation to the sdk-go validator using the admission webhook.
// TODO add a webhook example

// ServerlessWorkflowSpec defines a base API for integration test with operator-sdk
type ServerlessWorkflowSpec struct {
BaseWorkflow model.BaseWorkflow `json:"inline"`
BaseWorkflow model.BaseWorkflow `json:",inline"`
Events []model.Event `json:"events,omitempty"`
Functions []model.Function `json:"functions,omitempty"`
Retries []model.Retry `json:"retries,omitempty"`
States []model.State `json:"states"`
// +kubebuilder:validation:MinItems=1
// +kubebuilder:pruning:PreserveUnknownFields
States []model.State `json:"states"`
}

// ServerlessWorkflow ...
Expand Down
4 changes: 2 additions & 2 deletions model/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ type Function struct {
// <path_to_custom_script>#<custom_service_method>.
// +kubebuilder:validation:Required
Operation string `json:"operation" validate:"required,oneof=rest rpc expression"`
// Defines the function type. Is either `rest`, `rpc`, `expression`, `graphql`, `asyncapi`, `asyncapi` or `asyncapi`.
// Defines the function type. Is either `custom`, `rest`, `rpc`, `expression`, `graphql`, `asyncapi`, `asyncapi` or `asyncapi`.
// Default is `rest`.
// +kubebuilder:validation:Enum=rest;rpc;expression;graphql;asyncapi;asyncapi;asyncapi
// +kubebuilder:validation:Enum=rest;rpc;expression;graphql;asyncapi;asyncapi;asyncapi;custom
// +kubebuilder:default=rest
Type FunctionType `json:"type,omitempty"`
// References an auth definition name to be used to access to resource defined in the operation parameter.
Expand Down
8 changes: 4 additions & 4 deletions model/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ import (
//
// +kubebuilder:validation:Type=object
type Object struct {
Type Type `json:",inline"`
IntVal int32 `json:",inline"`
StrVal string `json:",inline"`
RawValue json.RawMessage `json:",inline"`
Type Type `json:"type,inline"`
IntVal int32 `json:"intVal,inline"`
StrVal string `json:"strVal,inline"`
RawValue json.RawMessage `json:"rawValue,inline"`
}

type Type int64
Expand Down
1 change: 1 addition & 0 deletions model/retry.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type Retry struct {
// Static value by which the delay increases during each attempt (ISO 8601 time format)
Increment string `json:"increment,omitempty" validate:"omitempty,iso8601duration"`
// Numeric value, if specified the delay between retries is multiplied by this value.
// +optional
Multiplier *floatstr.Float32OrString `json:"multiplier,omitempty" validate:"omitempty,min=1"`
// Maximum number of retry attempts.
// +kubebuilder:validation:Required
Expand Down
43 changes: 31 additions & 12 deletions model/switch_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,36 @@ type SwitchState struct {
Timeouts *SwitchStateTimeout `json:"timeouts,omitempty"`
}

// DefaultCondition Can be either a transition or end definition
type DefaultCondition struct {
// Serverless workflow states can have one or more incoming and outgoing transitions (from/to other states).
// Each state can define a transition definition that is used to determine which state to transition to next.
// +optional
Transition *Transition `json:"transition,omitempty"`
// If this state an end state
// +optional
End *End `json:"end,omitempty"`
}

// UnmarshalJSON ...
func (e *DefaultCondition) UnmarshalJSON(data []byte) error {
type defCondUnmarshal DefaultCondition

obj, str, err := primitiveOrStruct[string, defCondUnmarshal](data)
if err != nil {
return err
}

if obj == nil {
transition := &Transition{NextState: str}
e.Transition = transition
} else {
*e = DefaultCondition(*obj)
}

return nil
}

func (s *SwitchState) MarshalJSON() ([]byte, error) {
type Alias SwitchState
custom, err := json.Marshal(&struct {
Expand All @@ -48,17 +78,6 @@ func (s *SwitchState) MarshalJSON() ([]byte, error) {
return custom, err
}

// DefaultCondition Can be either a transition or end definition
type DefaultCondition struct {
// Serverless workflow states can have one or more incoming and outgoing transitions (from/to other states).
// Each state can define a transition definition that is used to determine which state to transition to next.
// +optional
Transition *Transition `json:"transition,omitempty"`
// If this state an end state
// +optional
End *End `json:"end,omitempty"`
}

// SwitchStateTimeout defines the specific timeout settings for switch state
type SwitchStateTimeout struct {
// Default workflow state execution timeout (ISO 8601 duration format)
Expand Down Expand Up @@ -107,5 +126,5 @@ type DataCondition struct {
// Explicit transition to end
End *End `json:"end" validate:"omitempty"`
// Workflow transition if condition is evaluated to true
Transition *Transition `json:"transition" validate:"omitempty"`
Transition *Transition `json:"transition,omitempty" validate:"omitempty"`
}
26 changes: 10 additions & 16 deletions model/workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ type BaseWorkflow struct {
DataInputSchema *DataInputSchema `json:"dataInputSchema,omitempty"`
// Serverless Workflow schema version
// +kubebuilder:validation:Required
// +kubebuilder:default="0.8"
SpecVersion string `json:"specVersion" validate:"required"`
// Secrets allow you to access sensitive information, such as passwords, OAuth tokens, ssh keys, etc,
// inside your Workflow Expressions.
Expand Down Expand Up @@ -501,26 +502,19 @@ type Transition struct {
}

// UnmarshalJSON ...
func (t *Transition) UnmarshalJSON(data []byte) error {
transitionMap := make(map[string]json.RawMessage)
if err := json.Unmarshal(data, &transitionMap); err != nil {
t.NextState, err = unmarshalString(data)
if err != nil {
return err
}
return nil
}
func (e *Transition) UnmarshalJSON(data []byte) error {
type defTransitionUnmarshal Transition

if err := unmarshalKey("compensate", transitionMap, &t.Compensate); err != nil {
return err
}
if err := unmarshalKey("produceEvents", transitionMap, &t.ProduceEvents); err != nil {
return err
}
if err := unmarshalKey("nextState", transitionMap, &t.NextState); err != nil {
obj, str, err := primitiveOrStruct[string, defTransitionUnmarshal](data)
if err != nil {
return err
}

if obj == nil {
e.NextState = str
} else {
*e = Transition(*obj)
}
return nil
}

Expand Down
29 changes: 26 additions & 3 deletions parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package parser

import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -556,6 +557,14 @@ func TestFromFile(t *testing.T) {
assert.Equal(t, "PT100S", w.States[9].SleepState.Timeouts.StateExecTimeout.Total)
assert.Equal(t, "PT200S", w.States[9].SleepState.Timeouts.StateExecTimeout.Single)
assert.Equal(t, true, w.States[9].End.Terminate)

// switch state with DefaultCondition as string
assert.NotEmpty(t, w.States[10].SwitchState)
assert.Equal(t, "HelloStateWithDefaultConditionString", w.States[10].Name)
assert.Equal(t, "${ true }", w.States[10].SwitchState.DataConditions[0].Condition)
assert.Equal(t, "HandleApprovedVisa", w.States[10].SwitchState.DataConditions[0].Transition.NextState)
assert.Equal(t, "SendTextForHighPriority", w.States[10].SwitchState.DefaultCondition.Transition.NextState)
assert.Equal(t, true, w.States[10].End.Terminate)
},
},
}
Expand Down Expand Up @@ -815,7 +824,17 @@ states:
single: PT20S
defaultCondition:
transition:
nextState: CheckCreditCallback
nextState: HelloStateWithDefaultConditionString
- name: HelloStateWithDefaultConditionString
type: switch
dataConditions:
- condition: ${ true }
transition:
nextState: HandleApprovedVisa
- condition: ${ false }
transition:
nextState: HandleRejectedVisa
defaultCondition: SendTextForHighPriority
- name: SendTextForHighPriority
type: foreach
inputCollection: "${ .messages }"
Expand Down Expand Up @@ -911,6 +930,7 @@ states:
terminate: true
`))
assert.Nil(t, err)
fmt.Println(err)
assert.NotNil(t, workflow)
b, err := json.Marshal(workflow)

Expand All @@ -936,7 +956,10 @@ states:
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\"}}"))

// Switch State
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\"}}"))
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\"}}"))

// Switch State with string DefaultCondition
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\"}}]}"))

// Foreach State
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\"}}"))
Expand Down Expand Up @@ -973,7 +996,7 @@ states:
nextState: HandleRejectedVisa
defaultCondition:
transition:
nextState: HandleNoVisaDecision
nextState: HandleApprovedVisa
- name: HandleApprovedVisa
type: operation
actions:
Expand Down
10 changes: 10 additions & 0 deletions parser/testdata/workflows/greetings-v08-spec.sw.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -211,3 +211,13 @@ states:
single: PT200S
end:
terminate: true
- name: HelloStateWithDefaultConditionString
type: switch
dataConditions:
- condition: ${ true }
transition: HandleApprovedVisa
- condition: ${ false }
transition:
nextState: HandleRejectedVisa
defaultCondition: SendTextForHighPriority
end: true