Skip to content

Commit e72f6db

Browse files
authored
fix(*): validate stateExecTimeout iso8601 (#76)
Signed-off-by: lsytj0413 <511121939@qq.com> Signed-off-by: lsytj0413 <511121939@qq.com>
1 parent b5f89fa commit e72f6db

File tree

10 files changed

+444
-46
lines changed

10 files changed

+444
-46
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ require (
66
github.com/go-playground/validator/v10 v10.11.0
77
github.com/senseyeio/duration v0.0.0-20180430131211-7c2a214ada46
88
github.com/stretchr/testify v1.7.0
9-
k8s.io/apimachinery v0.25.0
9+
k8s.io/apimachinery v0.25.1
1010
sigs.k8s.io/yaml v1.3.0
1111
)
1212

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
9393
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
9494
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
9595
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
96-
k8s.io/apimachinery v0.25.0 h1:MlP0r6+3XbkUG2itd6vp3oxbtdQLQI94fD5gCS+gnoU=
97-
k8s.io/apimachinery v0.25.0/go.mod h1:qMx9eAk0sZQGsXGu86fab8tZdffHbwUfsvzqKn4mfB0=
96+
k8s.io/apimachinery v0.25.1 h1:t0XrnmCEHVgJlR2arwO8Awp9ylluDic706WePaYCBTI=
97+
k8s.io/apimachinery v0.25.1/go.mod h1:hqqA1X0bsgsxI6dXsJ4HnNTBOmJNxyPp8dw3u2fSHwA=
9898
k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ=
9999
k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
100100
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k=

model/auth.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ type Auth struct {
9090
// UnmarshalJSON implements json.Unmarshaler
9191
func (a *AuthDefinitions) UnmarshalJSON(b []byte) error {
9292
if len(b) == 0 {
93+
// TODO: Normalize error messages
9394
return fmt.Errorf("no bytes to unmarshal")
9495
}
9596

model/sleep_state.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright 2022 The Serverless Workflow Specification Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package model
16+
17+
import (
18+
"context"
19+
"reflect"
20+
21+
validator "github.com/go-playground/validator/v10"
22+
val "github.com/serverlessworkflow/sdk-go/v2/validator"
23+
)
24+
25+
// SleepState suspends workflow execution for a given time duration.
26+
type SleepState struct {
27+
BaseState
28+
29+
// Duration (ISO 8601 duration format) to sleep
30+
Duration string `json:"duration" validate:"required"`
31+
// Timeouts State specific timeouts
32+
Timeouts *SleepStateTimeout `json:"timeouts,omitempty"`
33+
}
34+
35+
// SleepStateTimeout defines timeout settings for sleep state
36+
type SleepStateTimeout struct {
37+
StateExecTimeout StateExecTimeout `json:"stateExecTimeout,omitempty"`
38+
}
39+
40+
// SleepStateStructLevelValidation custom validator for SleepState
41+
func SleepStateStructLevelValidation(_ context.Context, structLevel validator.StructLevel) {
42+
sleepState := structLevel.Current().Interface().(SleepState)
43+
44+
err := validateISO8601TimeDuration(sleepState.Duration)
45+
if err != nil {
46+
structLevel.ReportError(reflect.ValueOf(sleepState.Duration), "Duration", "duration", "reqiso8601duration", "")
47+
}
48+
}
49+
50+
func init() {
51+
val.GetValidator().RegisterStructValidationCtx(
52+
SleepStateStructLevelValidation,
53+
SleepState{},
54+
)
55+
}

model/sleep_state_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright 2022 The Serverless Workflow Specification Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package model
16+
17+
import (
18+
"testing"
19+
20+
"github.com/stretchr/testify/assert"
21+
22+
val "github.com/serverlessworkflow/sdk-go/v2/validator"
23+
)
24+
25+
func TestSleepStateStructLevelValidation(t *testing.T) {
26+
type testCase struct {
27+
desp string
28+
state SleepState
29+
err string
30+
}
31+
testCases := []testCase{
32+
{
33+
desp: "normal duration",
34+
state: SleepState{
35+
BaseState: BaseState{
36+
Name: "1",
37+
Type: "sleep",
38+
},
39+
Duration: "PT10S",
40+
},
41+
err: ``,
42+
},
43+
{
44+
desp: "invalid duration",
45+
state: SleepState{
46+
BaseState: BaseState{
47+
Name: "1",
48+
Type: "sleep",
49+
},
50+
Duration: "T10S",
51+
},
52+
err: `Key: 'SleepState.Duration' Error:Field validation for 'Duration' failed on the 'reqiso8601duration' tag`,
53+
},
54+
}
55+
56+
for _, tc := range testCases {
57+
t.Run(tc.desp, func(t *testing.T) {
58+
err := val.GetValidator().Struct(tc.state)
59+
60+
if tc.err != "" {
61+
assert.Error(t, err)
62+
assert.Regexp(t, tc.err, err)
63+
return
64+
}
65+
66+
assert.NoError(t, err)
67+
})
68+
}
69+
}

model/state_exec_timeout.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Copyright 2022 The Serverless Workflow Specification Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package model
16+
17+
import (
18+
"bytes"
19+
"context"
20+
"encoding/json"
21+
"fmt"
22+
"reflect"
23+
24+
validator "github.com/go-playground/validator/v10"
25+
val "github.com/serverlessworkflow/sdk-go/v2/validator"
26+
)
27+
28+
// StateExecTimeout defines workflow state execution timeout
29+
type StateExecTimeout struct {
30+
// Single state execution timeout, not including retries (ISO 8601 duration format)
31+
Single string `json:"single,omitempty"`
32+
// Total state execution timeout, including retries (ISO 8601 duration format)
33+
Total string `json:"total" validate:"required"`
34+
}
35+
36+
// just define another type to unmarshal object, so the UnmarshalJSON will not called recursively
37+
type stateExecTimeoutForUnmarshal StateExecTimeout
38+
39+
// UnmarshalJSON unmarshal StateExecTimeout object from json bytes
40+
func (s *StateExecTimeout) UnmarshalJSON(data []byte) error {
41+
// We must trim the leading space, because we use first byte to detect data's type
42+
data = bytes.TrimSpace(data)
43+
if len(data) == 0 {
44+
// TODO: Normalize error messages
45+
return fmt.Errorf("no bytes to unmarshal")
46+
}
47+
48+
var err error
49+
switch data[0] {
50+
case '"':
51+
s.Total, err = unmarshalString(data)
52+
return err
53+
case '{':
54+
var v stateExecTimeoutForUnmarshal
55+
err = json.Unmarshal(data, &v)
56+
if err != nil {
57+
// TODO: replace the error message with correct type's name
58+
return err
59+
}
60+
61+
*s = StateExecTimeout(v)
62+
return nil
63+
}
64+
65+
return fmt.Errorf("stateExecTimeout value '%s' not support, it must be an object or string", string(data))
66+
}
67+
68+
// StateExecTimeoutStructLevelValidation custom validator for StateExecTimeout
69+
func StateExecTimeoutStructLevelValidation(_ context.Context, structLevel validator.StructLevel) {
70+
timeoutObj := structLevel.Current().Interface().(StateExecTimeout)
71+
72+
// TODO: use Custom Validation Functions tags for iso8601duration
73+
err := validateISO8601TimeDuration(timeoutObj.Total)
74+
if err != nil {
75+
structLevel.ReportError(reflect.ValueOf(timeoutObj.Total), "Total", "total", "reqiso8601duration", "")
76+
}
77+
78+
if timeoutObj.Single != "" {
79+
err = validateISO8601TimeDuration(timeoutObj.Single)
80+
if err != nil {
81+
structLevel.ReportError(reflect.ValueOf(timeoutObj.Single), "Single", "single", "reqiso8601duration", "")
82+
}
83+
}
84+
}
85+
86+
func init() {
87+
val.GetValidator().RegisterStructValidationCtx(
88+
StateExecTimeoutStructLevelValidation,
89+
StateExecTimeout{},
90+
)
91+
}

0 commit comments

Comments
 (0)