diff --git a/README.md b/README.md index 905e77e..f9c16d0 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,13 @@ See [contribution guide](CONTRIBUTION.md) - [x] Skip timer API for testing/operation - [x] Decider trigger type: any command combination +## 1.2 +- [x] API improvements to reduce boilerplate code + +## 1.3 +- [x] Support failing workflow with results +- [x] Improve workflow uncompleted error return(canceled, failed, timeout, terminated) + ## Future - [ ] Decider trigger type: AnyCommandClosed - [ ] WaitForMoreResults in StateDecision diff --git a/gen/iwfidl/.openapi-generator/FILES b/gen/iwfidl/.openapi-generator/FILES index 5886b35..bb8eba2 100644 --- a/gen/iwfidl/.openapi-generator/FILES +++ b/gen/iwfidl/.openapi-generator/FILES @@ -36,6 +36,7 @@ docs/StateMovement.md docs/TimerCommand.md docs/TimerResult.md docs/TimerStatus.md +docs/WorkflowErrorType.md docs/WorkflowGetDataObjectsRequest.md docs/WorkflowGetDataObjectsResponse.md docs/WorkflowGetRequest.md @@ -94,6 +95,7 @@ model_state_movement.go model_timer_command.go model_timer_result.go model_timer_status.go +model_workflow_error_type.go model_workflow_get_data_objects_request.go model_workflow_get_data_objects_response.go model_workflow_get_request.go diff --git a/gen/iwfidl/README.md b/gen/iwfidl/README.md index 0377806..ebfd34a 100644 --- a/gen/iwfidl/README.md +++ b/gen/iwfidl/README.md @@ -123,6 +123,7 @@ Class | Method | HTTP request | Description - [TimerCommand](docs/TimerCommand.md) - [TimerResult](docs/TimerResult.md) - [TimerStatus](docs/TimerStatus.md) + - [WorkflowErrorType](docs/WorkflowErrorType.md) - [WorkflowGetDataObjectsRequest](docs/WorkflowGetDataObjectsRequest.md) - [WorkflowGetDataObjectsResponse](docs/WorkflowGetDataObjectsResponse.md) - [WorkflowGetRequest](docs/WorkflowGetRequest.md) diff --git a/gen/iwfidl/api/openapi.yaml b/gen/iwfidl/api/openapi.yaml index 6d04dbc..671c685 100644 --- a/gen/iwfidl/api/openapi.yaml +++ b/gen/iwfidl/api/openapi.yaml @@ -333,11 +333,12 @@ components: maximumAttempts: 7 initialIntervalSeconds: 5 maximumIntervalSeconds: 2 - backoffCoefficient: 5.637376656633329 + backoffCoefficient: 5.637377 properties: initialIntervalSeconds: type: integer backoffCoefficient: + format: float type: number maximumIntervalSeconds: type: integer @@ -357,14 +358,14 @@ components: maximumAttempts: 7 initialIntervalSeconds: 5 maximumIntervalSeconds: 2 - backoffCoefficient: 5.637376656633329 + backoffCoefficient: 5.637377 decideApiTimeoutSeconds: 1 startApiTimeoutSeconds: 6 startApiRetryPolicy: maximumAttempts: 7 initialIntervalSeconds: 5 maximumIntervalSeconds: 2 - backoffCoefficient: 5.637376656633329 + backoffCoefficient: 5.637377 dataObjectsLoadingPolicy: partialLoadingKeys: - partialLoadingKeys @@ -392,7 +393,7 @@ components: maximumAttempts: 7 initialIntervalSeconds: 5 maximumIntervalSeconds: 2 - backoffCoefficient: 5.637376656633329 + backoffCoefficient: 5.637377 searchAttributes: - stringValue: stringValue valueType: null @@ -492,14 +493,14 @@ components: maximumAttempts: 7 initialIntervalSeconds: 5 maximumIntervalSeconds: 2 - backoffCoefficient: 5.637376656633329 + backoffCoefficient: 5.637377 decideApiTimeoutSeconds: 1 startApiTimeoutSeconds: 6 startApiRetryPolicy: maximumAttempts: 7 initialIntervalSeconds: 5 maximumIntervalSeconds: 2 - backoffCoefficient: 5.637376656633329 + backoffCoefficient: 5.637377 dataObjectsLoadingPolicy: partialLoadingKeys: - partialLoadingKeys @@ -511,7 +512,7 @@ components: maximumAttempts: 7 initialIntervalSeconds: 5 maximumIntervalSeconds: 2 - backoffCoefficient: 5.637376656633329 + backoffCoefficient: 5.637377 searchAttributes: - stringValue: stringValue valueType: null @@ -717,6 +718,8 @@ components: WorkflowGetResponse: example: workflowStatus: null + errorType: null + errorMessage: errorMessage workflowRunId: workflowRunId results: - completedStateOutput: @@ -738,10 +741,21 @@ components: items: $ref: '#/components/schemas/StateCompletionOutput' type: array + errorType: + $ref: '#/components/schemas/WorkflowErrorType' + errorMessage: + type: string required: - workflowRunId - workflowStatus type: object + WorkflowErrorType: + enum: + - STATE_DECISION_FAILING_WORKFLOW_ERROR_TYPE + - STATE_API_FAIL_MAX_OUT_RETRY_ERROR_TYPE + - INVALID_USER_WORKFLOW_CODE_ERROR_TYPE + - SERVER_INTERNAL_ERROR_TYPE + type: string WorkflowStatus: enum: - RUNNING @@ -1213,14 +1227,14 @@ components: maximumAttempts: 7 initialIntervalSeconds: 5 maximumIntervalSeconds: 2 - backoffCoefficient: 5.637376656633329 + backoffCoefficient: 5.637377 decideApiTimeoutSeconds: 1 startApiTimeoutSeconds: 6 startApiRetryPolicy: maximumAttempts: 7 initialIntervalSeconds: 5 maximumIntervalSeconds: 2 - backoffCoefficient: 5.637376656633329 + backoffCoefficient: 5.637377 dataObjectsLoadingPolicy: partialLoadingKeys: - partialLoadingKeys @@ -1242,14 +1256,14 @@ components: maximumAttempts: 7 initialIntervalSeconds: 5 maximumIntervalSeconds: 2 - backoffCoefficient: 5.637376656633329 + backoffCoefficient: 5.637377 decideApiTimeoutSeconds: 1 startApiTimeoutSeconds: 6 startApiRetryPolicy: maximumAttempts: 7 initialIntervalSeconds: 5 maximumIntervalSeconds: 2 - backoffCoefficient: 5.637376656633329 + backoffCoefficient: 5.637377 dataObjectsLoadingPolicy: partialLoadingKeys: - partialLoadingKeys @@ -1325,14 +1339,14 @@ components: maximumAttempts: 7 initialIntervalSeconds: 5 maximumIntervalSeconds: 2 - backoffCoefficient: 5.637376656633329 + backoffCoefficient: 5.637377 decideApiTimeoutSeconds: 1 startApiTimeoutSeconds: 6 startApiRetryPolicy: maximumAttempts: 7 initialIntervalSeconds: 5 maximumIntervalSeconds: 2 - backoffCoefficient: 5.637376656633329 + backoffCoefficient: 5.637377 dataObjectsLoadingPolicy: partialLoadingKeys: - partialLoadingKeys @@ -1354,14 +1368,14 @@ components: maximumAttempts: 7 initialIntervalSeconds: 5 maximumIntervalSeconds: 2 - backoffCoefficient: 5.637376656633329 + backoffCoefficient: 5.637377 decideApiTimeoutSeconds: 1 startApiTimeoutSeconds: 6 startApiRetryPolicy: maximumAttempts: 7 initialIntervalSeconds: 5 maximumIntervalSeconds: 2 - backoffCoefficient: 5.637376656633329 + backoffCoefficient: 5.637377 dataObjectsLoadingPolicy: partialLoadingKeys: - partialLoadingKeys @@ -1391,14 +1405,14 @@ components: maximumAttempts: 7 initialIntervalSeconds: 5 maximumIntervalSeconds: 2 - backoffCoefficient: 5.637376656633329 + backoffCoefficient: 5.637377 decideApiTimeoutSeconds: 1 startApiTimeoutSeconds: 6 startApiRetryPolicy: maximumAttempts: 7 initialIntervalSeconds: 5 maximumIntervalSeconds: 2 - backoffCoefficient: 5.637376656633329 + backoffCoefficient: 5.637377 dataObjectsLoadingPolicy: partialLoadingKeys: - partialLoadingKeys diff --git a/gen/iwfidl/docs/WorkflowErrorType.md b/gen/iwfidl/docs/WorkflowErrorType.md new file mode 100644 index 0000000..78eefb8 --- /dev/null +++ b/gen/iwfidl/docs/WorkflowErrorType.md @@ -0,0 +1,17 @@ +# WorkflowErrorType + +## Enum + + +* `STATE_DECISION_FAILING_WORKFLOW_ERROR_TYPE` (value: `"STATE_DECISION_FAILING_WORKFLOW_ERROR_TYPE"`) + +* `STATE_API_FAIL_MAX_OUT_RETRY_ERROR_TYPE` (value: `"STATE_API_FAIL_MAX_OUT_RETRY_ERROR_TYPE"`) + +* `INVALID_USER_WORKFLOW_CODE_ERROR_TYPE` (value: `"INVALID_USER_WORKFLOW_CODE_ERROR_TYPE"`) + +* `SERVER_INTERNAL_ERROR_TYPE` (value: `"SERVER_INTERNAL_ERROR_TYPE"`) + + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/gen/iwfidl/docs/WorkflowGetResponse.md b/gen/iwfidl/docs/WorkflowGetResponse.md index 3100291..ee48e15 100644 --- a/gen/iwfidl/docs/WorkflowGetResponse.md +++ b/gen/iwfidl/docs/WorkflowGetResponse.md @@ -7,6 +7,8 @@ Name | Type | Description | Notes **WorkflowRunId** | **string** | | **WorkflowStatus** | [**WorkflowStatus**](WorkflowStatus.md) | | **Results** | Pointer to [**[]StateCompletionOutput**](StateCompletionOutput.md) | | [optional] +**ErrorType** | Pointer to [**WorkflowErrorType**](WorkflowErrorType.md) | | [optional] +**ErrorMessage** | Pointer to **string** | | [optional] ## Methods @@ -92,6 +94,56 @@ SetResults sets Results field to given value. HasResults returns a boolean if a field has been set. +### GetErrorType + +`func (o *WorkflowGetResponse) GetErrorType() WorkflowErrorType` + +GetErrorType returns the ErrorType field if non-nil, zero value otherwise. + +### GetErrorTypeOk + +`func (o *WorkflowGetResponse) GetErrorTypeOk() (*WorkflowErrorType, bool)` + +GetErrorTypeOk returns a tuple with the ErrorType field if it's non-nil, zero value otherwise +and a boolean to check if the value has been set. + +### SetErrorType + +`func (o *WorkflowGetResponse) SetErrorType(v WorkflowErrorType)` + +SetErrorType sets ErrorType field to given value. + +### HasErrorType + +`func (o *WorkflowGetResponse) HasErrorType() bool` + +HasErrorType returns a boolean if a field has been set. + +### GetErrorMessage + +`func (o *WorkflowGetResponse) GetErrorMessage() string` + +GetErrorMessage returns the ErrorMessage field if non-nil, zero value otherwise. + +### GetErrorMessageOk + +`func (o *WorkflowGetResponse) GetErrorMessageOk() (*string, bool)` + +GetErrorMessageOk returns a tuple with the ErrorMessage field if it's non-nil, zero value otherwise +and a boolean to check if the value has been set. + +### SetErrorMessage + +`func (o *WorkflowGetResponse) SetErrorMessage(v string)` + +SetErrorMessage sets ErrorMessage field to given value. + +### HasErrorMessage + +`func (o *WorkflowGetResponse) HasErrorMessage() bool` + +HasErrorMessage returns a boolean if a field has been set. + [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/gen/iwfidl/model_workflow_error_type.go b/gen/iwfidl/model_workflow_error_type.go new file mode 100644 index 0000000..6f01842 --- /dev/null +++ b/gen/iwfidl/model_workflow_error_type.go @@ -0,0 +1,115 @@ +/* +Workflow APIs + +This APIs for iwf SDKs to operate workflows + +API version: 1.0.0 +*/ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package iwfidl + +import ( + "encoding/json" + "fmt" +) + +// WorkflowErrorType the model 'WorkflowErrorType' +type WorkflowErrorType string + +// List of WorkflowErrorType +const ( + STATE_DECISION_FAILING_WORKFLOW_ERROR_TYPE WorkflowErrorType = "STATE_DECISION_FAILING_WORKFLOW_ERROR_TYPE" + STATE_API_FAIL_MAX_OUT_RETRY_ERROR_TYPE WorkflowErrorType = "STATE_API_FAIL_MAX_OUT_RETRY_ERROR_TYPE" + INVALID_USER_WORKFLOW_CODE_ERROR_TYPE WorkflowErrorType = "INVALID_USER_WORKFLOW_CODE_ERROR_TYPE" + SERVER_INTERNAL_ERROR_TYPE WorkflowErrorType = "SERVER_INTERNAL_ERROR_TYPE" +) + +// All allowed values of WorkflowErrorType enum +var AllowedWorkflowErrorTypeEnumValues = []WorkflowErrorType{ + "STATE_DECISION_FAILING_WORKFLOW_ERROR_TYPE", + "STATE_API_FAIL_MAX_OUT_RETRY_ERROR_TYPE", + "INVALID_USER_WORKFLOW_CODE_ERROR_TYPE", + "SERVER_INTERNAL_ERROR_TYPE", +} + +func (v *WorkflowErrorType) UnmarshalJSON(src []byte) error { + var value string + err := json.Unmarshal(src, &value) + if err != nil { + return err + } + enumTypeValue := WorkflowErrorType(value) + for _, existing := range AllowedWorkflowErrorTypeEnumValues { + if existing == enumTypeValue { + *v = enumTypeValue + return nil + } + } + + return fmt.Errorf("%+v is not a valid WorkflowErrorType", value) +} + +// NewWorkflowErrorTypeFromValue returns a pointer to a valid WorkflowErrorType +// for the value passed as argument, or an error if the value passed is not allowed by the enum +func NewWorkflowErrorTypeFromValue(v string) (*WorkflowErrorType, error) { + ev := WorkflowErrorType(v) + if ev.IsValid() { + return &ev, nil + } else { + return nil, fmt.Errorf("invalid value '%v' for WorkflowErrorType: valid values are %v", v, AllowedWorkflowErrorTypeEnumValues) + } +} + +// IsValid return true if the value is valid for the enum, false otherwise +func (v WorkflowErrorType) IsValid() bool { + for _, existing := range AllowedWorkflowErrorTypeEnumValues { + if existing == v { + return true + } + } + return false +} + +// Ptr returns reference to WorkflowErrorType value +func (v WorkflowErrorType) Ptr() *WorkflowErrorType { + return &v +} + +type NullableWorkflowErrorType struct { + value *WorkflowErrorType + isSet bool +} + +func (v NullableWorkflowErrorType) Get() *WorkflowErrorType { + return v.value +} + +func (v *NullableWorkflowErrorType) Set(val *WorkflowErrorType) { + v.value = val + v.isSet = true +} + +func (v NullableWorkflowErrorType) IsSet() bool { + return v.isSet +} + +func (v *NullableWorkflowErrorType) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableWorkflowErrorType(val *WorkflowErrorType) *NullableWorkflowErrorType { + return &NullableWorkflowErrorType{value: val, isSet: true} +} + +func (v NullableWorkflowErrorType) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableWorkflowErrorType) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} + diff --git a/gen/iwfidl/model_workflow_get_response.go b/gen/iwfidl/model_workflow_get_response.go index 149360e..2d80de6 100644 --- a/gen/iwfidl/model_workflow_get_response.go +++ b/gen/iwfidl/model_workflow_get_response.go @@ -19,6 +19,8 @@ type WorkflowGetResponse struct { WorkflowRunId string `json:"workflowRunId"` WorkflowStatus WorkflowStatus `json:"workflowStatus"` Results []StateCompletionOutput `json:"results,omitempty"` + ErrorType *WorkflowErrorType `json:"errorType,omitempty"` + ErrorMessage *string `json:"errorMessage,omitempty"` } // NewWorkflowGetResponse instantiates a new WorkflowGetResponse object @@ -120,6 +122,70 @@ func (o *WorkflowGetResponse) SetResults(v []StateCompletionOutput) { o.Results = v } +// GetErrorType returns the ErrorType field value if set, zero value otherwise. +func (o *WorkflowGetResponse) GetErrorType() WorkflowErrorType { + if o == nil || isNil(o.ErrorType) { + var ret WorkflowErrorType + return ret + } + return *o.ErrorType +} + +// GetErrorTypeOk returns a tuple with the ErrorType field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *WorkflowGetResponse) GetErrorTypeOk() (*WorkflowErrorType, bool) { + if o == nil || isNil(o.ErrorType) { + return nil, false + } + return o.ErrorType, true +} + +// HasErrorType returns a boolean if a field has been set. +func (o *WorkflowGetResponse) HasErrorType() bool { + if o != nil && !isNil(o.ErrorType) { + return true + } + + return false +} + +// SetErrorType gets a reference to the given WorkflowErrorType and assigns it to the ErrorType field. +func (o *WorkflowGetResponse) SetErrorType(v WorkflowErrorType) { + o.ErrorType = &v +} + +// GetErrorMessage returns the ErrorMessage field value if set, zero value otherwise. +func (o *WorkflowGetResponse) GetErrorMessage() string { + if o == nil || isNil(o.ErrorMessage) { + var ret string + return ret + } + return *o.ErrorMessage +} + +// GetErrorMessageOk returns a tuple with the ErrorMessage field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *WorkflowGetResponse) GetErrorMessageOk() (*string, bool) { + if o == nil || isNil(o.ErrorMessage) { + return nil, false + } + return o.ErrorMessage, true +} + +// HasErrorMessage returns a boolean if a field has been set. +func (o *WorkflowGetResponse) HasErrorMessage() bool { + if o != nil && !isNil(o.ErrorMessage) { + return true + } + + return false +} + +// SetErrorMessage gets a reference to the given string and assigns it to the ErrorMessage field. +func (o *WorkflowGetResponse) SetErrorMessage(v string) { + o.ErrorMessage = &v +} + func (o WorkflowGetResponse) MarshalJSON() ([]byte, error) { toSerialize := map[string]interface{}{} if true { @@ -131,6 +197,12 @@ func (o WorkflowGetResponse) MarshalJSON() ([]byte, error) { if !isNil(o.Results) { toSerialize["results"] = o.Results } + if !isNil(o.ErrorType) { + toSerialize["errorType"] = o.ErrorType + } + if !isNil(o.ErrorMessage) { + toSerialize["errorMessage"] = o.ErrorMessage + } return json.Marshal(toSerialize) } diff --git a/integ/force_fail_workflow.go b/integ/force_fail_workflow.go new file mode 100644 index 0000000..f634987 --- /dev/null +++ b/integ/force_fail_workflow.go @@ -0,0 +1,15 @@ +package integ + +import "github.com/indeedeng/iwf-golang-sdk/iwf" + +type forceFailWorkflow struct { + iwf.DefaultWorkflowType + iwf.EmptyCommunicationSchema + iwf.EmptyPersistenceSchema +} + +func (b forceFailWorkflow) GetStates() []iwf.StateDef { + return []iwf.StateDef{ + iwf.StartingStateDef(&forceFailWorkflowState1{}), + } +} diff --git a/integ/force_fail_workflow_state1.go b/integ/force_fail_workflow_state1.go new file mode 100644 index 0000000..1ca5814 --- /dev/null +++ b/integ/force_fail_workflow_state1.go @@ -0,0 +1,18 @@ +package integ + +import ( + "github.com/indeedeng/iwf-golang-sdk/iwf" +) + +type forceFailWorkflowState1 struct { + iwf.DefaultStateId + iwf.DefaultStateOptions +} + +func (b forceFailWorkflowState1) Start(ctx iwf.WorkflowContext, input iwf.Object, persistence iwf.Persistence, communication iwf.Communication) (*iwf.CommandRequest, error) { + return iwf.EmptyCommandRequest(), nil +} + +func (b forceFailWorkflowState1) Decide(ctx iwf.WorkflowContext, input iwf.Object, commandResults iwf.CommandResults, persistence iwf.Persistence, communication iwf.Communication) (*iwf.StateDecision, error) { + return iwf.ForceFailWorkflow("a failing message"), nil +} diff --git a/integ/init.go b/integ/init.go index 07c4dda..18545ca 100644 --- a/integ/init.go +++ b/integ/init.go @@ -15,6 +15,9 @@ func init() { &signalWorkflow{}, &interStateWorkflow{}, &persistenceWorkflow{}, + &forceFailWorkflow{}, + &stateApiFailWorkflow{}, + &stateApiTimeoutWorkflow{}, ) if err != nil { panic(err) diff --git a/integ/state_api_fail_workflow.go b/integ/state_api_fail_workflow.go new file mode 100644 index 0000000..4fc72a7 --- /dev/null +++ b/integ/state_api_fail_workflow.go @@ -0,0 +1,15 @@ +package integ + +import "github.com/indeedeng/iwf-golang-sdk/iwf" + +type stateApiFailWorkflow struct { + iwf.DefaultWorkflowType + iwf.EmptyCommunicationSchema + iwf.EmptyPersistenceSchema +} + +func (b stateApiFailWorkflow) GetStates() []iwf.StateDef { + return []iwf.StateDef{ + iwf.StartingStateDef(&stateApiFailWorkflowState1{}), + } +} diff --git a/integ/state_api_fail_workflow_state1.go b/integ/state_api_fail_workflow_state1.go new file mode 100644 index 0000000..6bc887b --- /dev/null +++ b/integ/state_api_fail_workflow_state1.go @@ -0,0 +1,27 @@ +package integ + +import ( + "fmt" + "github.com/indeedeng/iwf-golang-sdk/gen/iwfidl" + "github.com/indeedeng/iwf-golang-sdk/iwf" +) + +type stateApiFailWorkflowState1 struct { + iwf.DefaultStateId +} + +func (b stateApiFailWorkflowState1) Start(ctx iwf.WorkflowContext, input iwf.Object, persistence iwf.Persistence, communication iwf.Communication) (*iwf.CommandRequest, error) { + return nil, fmt.Errorf("test api failing") +} + +func (b stateApiFailWorkflowState1) Decide(ctx iwf.WorkflowContext, input iwf.Object, commandResults iwf.CommandResults, persistence iwf.Persistence, communication iwf.Communication) (*iwf.StateDecision, error) { + return iwf.ForceFailWorkflow("a failing message"), nil +} + +func (b stateApiFailWorkflowState1) GetStateOptions() *iwfidl.WorkflowStateOptions { + return &iwfidl.WorkflowStateOptions{ + StartApiRetryPolicy: &iwfidl.RetryPolicy{ + MaximumAttempts: iwfidl.PtrInt32(1), + }, + } +} diff --git a/integ/state_api_timeout_workflow.go b/integ/state_api_timeout_workflow.go new file mode 100644 index 0000000..b2ac50c --- /dev/null +++ b/integ/state_api_timeout_workflow.go @@ -0,0 +1,15 @@ +package integ + +import "github.com/indeedeng/iwf-golang-sdk/iwf" + +type stateApiTimeoutWorkflow struct { + iwf.DefaultWorkflowType + iwf.EmptyCommunicationSchema + iwf.EmptyPersistenceSchema +} + +func (b stateApiTimeoutWorkflow) GetStates() []iwf.StateDef { + return []iwf.StateDef{ + iwf.StartingStateDef(&stateApiTimeoutWorkflowState1{}), + } +} diff --git a/integ/state_api_timeout_workflow_state1.go b/integ/state_api_timeout_workflow_state1.go new file mode 100644 index 0000000..d6a6f0a --- /dev/null +++ b/integ/state_api_timeout_workflow_state1.go @@ -0,0 +1,29 @@ +package integ + +import ( + "github.com/indeedeng/iwf-golang-sdk/gen/iwfidl" + "github.com/indeedeng/iwf-golang-sdk/iwf" + "time" +) + +type stateApiTimeoutWorkflowState1 struct { + iwf.DefaultStateId +} + +func (b stateApiTimeoutWorkflowState1) Start(ctx iwf.WorkflowContext, input iwf.Object, persistence iwf.Persistence, communication iwf.Communication) (*iwf.CommandRequest, error) { + time.Sleep(time.Minute) + return nil, nil +} + +func (b stateApiTimeoutWorkflowState1) Decide(ctx iwf.WorkflowContext, input iwf.Object, commandResults iwf.CommandResults, persistence iwf.Persistence, communication iwf.Communication) (*iwf.StateDecision, error) { + return iwf.ForceFailWorkflow("a failing message"), nil +} + +func (b stateApiTimeoutWorkflowState1) GetStateOptions() *iwfidl.WorkflowStateOptions { + return &iwfidl.WorkflowStateOptions{ + StartApiRetryPolicy: &iwfidl.RetryPolicy{ + MaximumAttempts: iwfidl.PtrInt32(1), + }, + StartApiTimeoutSeconds: iwfidl.PtrInt32(1), + } +} diff --git a/integ/workflow_uncompleted_test.go b/integ/workflow_uncompleted_test.go new file mode 100644 index 0000000..4a77cca --- /dev/null +++ b/integ/workflow_uncompleted_test.go @@ -0,0 +1,126 @@ +package integ + +import ( + "context" + "fmt" + "github.com/indeedeng/iwf-golang-sdk/gen/iwfidl" + "github.com/indeedeng/iwf-golang-sdk/iwf" + "github.com/indeedeng/iwf-golang-sdk/iwf/ptr" + "github.com/stretchr/testify/assert" + "strconv" + "strings" + "testing" + "time" +) + +func TestWorkflowTimeout(t *testing.T) { + wfId := "TestWorkflowTimeout" + strconv.Itoa(int(time.Now().Unix())) + runId, err := client.StartWorkflow(context.Background(), &signalWorkflow{}, wfId, 1, nil, nil) + assert.Nil(t, err) + assert.NotEmpty(t, runId) + + err = client.GetSimpleWorkflowResult(context.Background(), wfId, "", nil) + + wErr, ok := iwf.AsWorkflowUncompletedError(err) + assert.True(t, ok) + assert.Equal(t, iwf.NewWorkflowUncompletedError(runId, iwfidl.TIMEOUT, nil, nil, nil, iwf.GetDefaultObjectEncoder()), wErr) + + out, err2 := client.GetComplexWorkflowResults(context.Background(), wfId, "") + assert.Nil(t, out) + assert.Equal(t, err, err2) + + assert.Equal(t, "workflow is not completed succesfully, closedStatus: TIMEOUT, failedErrorType(applies if failed as closedStatus):, error message:", err.Error()) +} + +func TestWorkflowCancel(t *testing.T) { + wfId := "TestWorkflowCancel" + strconv.Itoa(int(time.Now().Unix())) + runId, err := client.StartWorkflow(context.Background(), &signalWorkflow{}, wfId, 10, nil, nil) + assert.Nil(t, err) + assert.NotEmpty(t, runId) + + err = client.StopWorkflow(context.Background(), wfId, "", nil) + assert.Nil(t, err) + + err = client.GetSimpleWorkflowResult(context.Background(), wfId, "", nil) + + wErr, ok := iwf.AsWorkflowUncompletedError(err) + assert.True(t, ok) + assert.Equal(t, iwf.NewWorkflowUncompletedError(runId, iwfidl.CANCELED, nil, nil, nil, iwf.GetDefaultObjectEncoder()), wErr) + + out, err2 := client.GetComplexWorkflowResults(context.Background(), wfId, "") + assert.Nil(t, out) + assert.Equal(t, err, err2) + + assert.Equal(t, "workflow is not completed succesfully, closedStatus: CANCELED, failedErrorType(applies if failed as closedStatus):, error message:", err.Error()) +} + +func TestForceFailWorkflow(t *testing.T) { + wfId := "TestForceFailWorkflow" + strconv.Itoa(int(time.Now().Unix())) + runId, err := client.StartWorkflow(context.Background(), &forceFailWorkflow{}, wfId, 10, nil, nil) + assert.Nil(t, err) + assert.NotEmpty(t, runId) + + err = client.GetSimpleWorkflowResult(context.Background(), wfId, "", nil) + + wErr, ok := iwf.AsWorkflowUncompletedError(err) + assert.True(t, ok) + assert.Equal(t, iwf.NewWorkflowUncompletedError(runId, iwfidl.FAILED, ptr.Any(iwfidl.STATE_DECISION_FAILING_WORKFLOW_ERROR_TYPE), nil, wErr.StateResults, iwf.GetDefaultObjectEncoder()), wErr) + + out, err2 := client.GetComplexWorkflowResults(context.Background(), wfId, "") + assert.Nil(t, out) + assert.Equal(t, err, err2) + assert.Equal(t, "workflow is not completed succesfully, closedStatus: FAILED, failedErrorType(applies if failed as closedStatus):STATE_DECISION_FAILING_WORKFLOW_ERROR_TYPE, error message:", err.Error()) + + var output string + err = wErr.GetStateResult(0, &output) + assert.Nil(t, err) + assert.Equal(t, "a failing message", output) +} + +func TestStateApiFailWorkflow(t *testing.T) { + wfId := "TestStateApiFailWorkflow" + strconv.Itoa(int(time.Now().Unix())) + runId, err := client.StartWorkflow(context.Background(), &stateApiFailWorkflow{}, wfId, 10, nil, &iwf.WorkflowOptions{}) + assert.Nil(t, err) + assert.NotEmpty(t, runId) + + err = client.GetSimpleWorkflowResult(context.Background(), wfId, "", nil) + + wErr, ok := iwf.AsWorkflowUncompletedError(err) + assert.True(t, ok) + assert.Equal(t, iwf.NewWorkflowUncompletedError(runId, iwfidl.FAILED, ptr.Any(iwfidl.STATE_API_FAIL_MAX_OUT_RETRY_ERROR_TYPE), wErr.ErrorMessage, nil, iwf.GetDefaultObjectEncoder()), wErr) + + assert.True(t, strings.Contains(*wErr.ErrorMessage, "test api failing"), "must contain api failing message") + + out, err2 := client.GetComplexWorkflowResults(context.Background(), wfId, "") + assert.Nil(t, out) + assert.Equal(t, err, err2) + + assert.True(t, strings.Contains(err.Error(), "workflow is not completed succesfully, closedStatus: FAILED, failedErrorType(applies if failed as closedStatus):STATE_API_FAIL_MAX_OUT_RETRY_ERROR_TYPE, error message:statusCode: 400, responseBody: {\"error\":\"error message:test api failing")) +} + +func TestStateApiTimeoutWorkflow(t *testing.T) { + wfId := "TestStateApiTimeoutWorkflow" + strconv.Itoa(int(time.Now().Unix())) + runId, err := client.StartWorkflow(context.Background(), &stateApiTimeoutWorkflow{}, wfId, 10, nil, &iwf.WorkflowOptions{}) + assert.Nil(t, err) + assert.NotEmpty(t, runId) + + err = client.GetSimpleWorkflowResult(context.Background(), wfId, "", nil) + + wErr, ok := iwf.AsWorkflowUncompletedError(err) + assert.True(t, ok) + assert.Equal(t, iwf.NewWorkflowUncompletedError(runId, iwfidl.FAILED, ptr.Any(iwfidl.STATE_API_FAIL_MAX_OUT_RETRY_ERROR_TYPE), wErr.ErrorMessage, nil, iwf.GetDefaultObjectEncoder()), wErr) + + fmt.Println(err) + + expectedMsg := "workflow is not completed succesfully, closedStatus: FAILED, failedErrorType(applies if failed as closedStatus):STATE_API_FAIL_MAX_OUT_RETRY_ERROR_TYPE, error message:activity error (type: StateStart, scheduledEventID: 10, startedEventID: 11, identity: ): activity StartToClose timeout (type: StartToClose)" + assert.Equal(t, expectedMsg, err.Error()) + + out, err2 := client.GetComplexWorkflowResults(context.Background(), wfId, "") + assert.Nil(t, out) + assert.Equal(t, err, err2) +} + +// TODO need to support terminate operation in Stop API first +//func TestWorkflowTerminated(t *testing.T) { +// +//} \ No newline at end of file diff --git a/iwf-idl b/iwf-idl index d2a70a9..516ea1e 160000 --- a/iwf-idl +++ b/iwf-idl @@ -1 +1 @@ -Subproject commit d2a70a9a6b20bb323a2187a234e0b4d3c06f64ab +Subproject commit 516ea1e5e93f0ca7803fa787a1021f35b6f4b61c diff --git a/iwf/errors.go b/iwf/errors.go index 25192d0..5f33a0c 100644 --- a/iwf/errors.go +++ b/iwf/errors.go @@ -164,4 +164,49 @@ func IsWorkflowNotExistsError(err error) bool { return false } return apiError.Response.GetSubStatus() == iwfidl.WORKFLOW_NOT_EXISTS_SUB_STATUS +} + +type WorkflowUncompletedError struct { + RunId string + ClosedStatus iwfidl.WorkflowStatus + ErrorType *iwfidl.WorkflowErrorType + ErrorMessage *string + StateResults []iwfidl.StateCompletionOutput + Encoder ObjectEncoder +} + +func NewWorkflowUncompletedError( + runId string, closedStatus iwfidl.WorkflowStatus, errorType *iwfidl.WorkflowErrorType, + errorMessage *string, stateResults []iwfidl.StateCompletionOutput, encoder ObjectEncoder) error { + return &WorkflowUncompletedError{ + RunId: runId, + ClosedStatus: closedStatus, + ErrorType: errorType, + ErrorMessage: errorMessage, + StateResults: stateResults, + Encoder: encoder, + } +} +func (w *WorkflowUncompletedError) Error() string { + errTypeMsg := "" + message := "" + if w.ErrorType != nil { + errTypeMsg = fmt.Sprintf("%v", *w.ErrorType) + } + if w.ErrorMessage != nil { + message = fmt.Sprintf("%v", *w.ErrorMessage) + } + return fmt.Sprintf("workflow is not completed succesfully, closedStatus: %v, failedErrorType(applies if failed as closedStatus):%v, error message:%v", + w.ClosedStatus, errTypeMsg, message) +} + +// AsWorkflowUncompletedError will check if it's a WorkflowUncompletedError and convert it if so +func AsWorkflowUncompletedError(err error) (*WorkflowUncompletedError, bool) { + wErr, ok := err.(*WorkflowUncompletedError) + return wErr, ok +} + +func (w *WorkflowUncompletedError) GetStateResult(index int, resultPtr interface{}) error { + output := w.StateResults[index] + return w.Encoder.Decode(output.CompletedStateOutput, resultPtr) } \ No newline at end of file diff --git a/iwf/state_decision.go b/iwf/state_decision.go index b842d93..1aa7b26 100644 --- a/iwf/state_decision.go +++ b/iwf/state_decision.go @@ -56,6 +56,17 @@ var ForceFailingWorkflow = &StateDecision{ }, } +func ForceFailWorkflow(output interface{}) *StateDecision { + return &StateDecision{ + NextStates: []StateMovement{ + { + NextStateId: ForceFailingWorkflowStateId, + NextStateInput: output, + }, + }, + } +} + var GracefulCompletingWorkflow = GracefulCompleteWorkflow(nil) func GracefulCompleteWorkflow(output interface{}) *StateDecision { diff --git a/iwf/unregistered_client_impl.go b/iwf/unregistered_client_impl.go index 12221ab..b6eb85d 100644 --- a/iwf/unregistered_client_impl.go +++ b/iwf/unregistered_client_impl.go @@ -148,6 +148,9 @@ func (u *unregisteredClientImpl) GetSimpleWorkflowResult(ctx context.Context, wo if err := u.processError(err, httpResp); err != nil { return err } + if resp.WorkflowStatus != iwfidl.COMPLETED { + return u.processUncompletedError(resp) + } if len(resp.Results) != 1 { return NewWorkflowDefinitionError("this workflow should have one or zero state output for using this API") } @@ -165,6 +168,9 @@ func (u *unregisteredClientImpl) GetComplexWorkflowResults(ctx context.Context, if err := u.processError(err, httpResp); err != nil { return nil, err } + if resp.WorkflowStatus != iwfidl.COMPLETED { + return nil, u.processUncompletedError(resp) + } return resp.Results, nil } @@ -264,3 +270,7 @@ func (u *unregisteredClientImpl) processError(err error, httpResp *http.Response } return NewApiError(err, oerr, httpResp, resp) } + +func (u *unregisteredClientImpl) processUncompletedError(resp *iwfidl.WorkflowGetResponse) error { + return NewWorkflowUncompletedError(resp.WorkflowRunId, resp.WorkflowStatus, resp.ErrorType, resp.ErrorMessage, resp.Results, u.options.ObjectEncoder) +} diff --git a/iwf/workflow_options.go b/iwf/workflow_options.go index ed9f467..1e8706d 100644 --- a/iwf/workflow_options.go +++ b/iwf/workflow_options.go @@ -6,7 +6,6 @@ type WorkflowOptions struct { WorkflowIdReusePolicy *iwfidl.WorkflowIDReusePolicy WorkflowCronSchedule *string WorkflowRetryPolicy *iwfidl.RetryPolicy - StartStateOptions *iwfidl.WorkflowStateOptions // InitialSearchAttributes set the initial search attributes to start a workflow // key is search attribute key, value much match with PersistenceSchema of the workflow definition // For iwfidl.DATETIME , the value can be either time.Time or a string value in format of DateTimeFormat