diff --git a/configtype/duration.go b/configtype/duration.go index 6c1e0777f8..dc8789ce05 100644 --- a/configtype/duration.go +++ b/configtype/duration.go @@ -3,6 +3,8 @@ package configtype import ( "encoding/json" "time" + + "github.com/invopop/jsonschema" ) // Duration is a wrapper around time.Duration that should be used in config @@ -19,6 +21,14 @@ func NewDuration(d time.Duration) Duration { } } +func (Duration) JSONSchema() *jsonschema.Schema { + return &jsonschema.Schema{ + Type: "string", + Pattern: `^[-+]?([0-9]*(\.[0-9]*)?[a-z]+)+$`, // copied from time.ParseDuration + Title: "CloudQuery configtype.Duration", + } +} + func (d *Duration) UnmarshalJSON(b []byte) error { var s string if err := json.Unmarshal(b, &s); err != nil { diff --git a/configtype/duration_test.go b/configtype/duration_test.go index 0fdd3c2b06..fadd3d1104 100644 --- a/configtype/duration_test.go +++ b/configtype/duration_test.go @@ -2,11 +2,15 @@ package configtype_test import ( "encoding/json" + "math/rand" "testing" "time" "github.com/cloudquery/plugin-sdk/v4/configtype" + "github.com/cloudquery/plugin-sdk/v4/plugin" "github.com/google/go-cmp/cmp" + "github.com/invopop/jsonschema" + "github.com/stretchr/testify/require" ) func TestDuration(t *testing.T) { @@ -52,3 +56,74 @@ func TestComparability(t *testing.T) { } } } + +func TestDuration_JSONSchema(t *testing.T) { + sc := (&jsonschema.Reflector{RequiredFromJSONSchemaTags: true}).Reflect(configtype.Duration{}) + schema, err := json.MarshalIndent(sc, "", " ") + require.NoError(t, err) + + validator, err := plugin.JSONSchemaValidator(string(schema)) + require.NoError(t, err) + + type testCase struct { + Name string + Spec string + Err bool + } + + for _, tc := range append([]testCase{ + { + Name: "empty", + Err: true, + Spec: `""`, + }, + { + Name: "null", + Err: true, + Spec: `null`, + }, + { + Name: "bad type", + Err: true, + Spec: `false`, + }, + { + Name: "bad format", + Err: true, + Spec: `false`, + }, + }, + func() []testCase { + rnd := rand.New(rand.NewSource(time.Now().UnixNano())) + const ( + cases = 20 + maxDur = int64(100 * time.Hour) + maxDurHalf = maxDur / 2 + ) + result := make([]testCase, cases) + for i := 0; i < cases; i++ { + val := rnd.Int63n(maxDur) - maxDurHalf + d := configtype.NewDuration(time.Duration(val)) + + data, err := d.MarshalJSON() + require.NoError(t, err) + result[i] = testCase{ + Name: string(data), + Spec: string(data), + } + } + return result + }()..., + ) { + t.Run(tc.Name, func(t *testing.T) { + var val any + err := json.Unmarshal([]byte(tc.Spec), &val) + require.NoError(t, err) + if tc.Err { + require.Error(t, validator.Validate(val)) + } else { + require.NoError(t, validator.Validate(val)) + } + }) + } +} diff --git a/scheduler/strategy_test.go b/scheduler/strategy_test.go index 3eec7d4480..9718ee563c 100644 --- a/scheduler/strategy_test.go +++ b/scheduler/strategy_test.go @@ -3,7 +3,6 @@ package scheduler_test import ( _ "embed" "encoding/json" - "reflect" "testing" "github.com/cloudquery/plugin-sdk/v4/plugin" @@ -13,7 +12,7 @@ import ( ) func TestStrategy_JSONSchema(t *testing.T) { - sc := (&jsonschema.Reflector{RequiredFromJSONSchemaTags: true}).ReflectFromType(reflect.TypeOf(scheduler.StrategyDFS)) + sc := (&jsonschema.Reflector{RequiredFromJSONSchemaTags: true}).Reflect(scheduler.StrategyDFS) schema, err := json.MarshalIndent(sc, "", " ") require.NoError(t, err)