From 8e1fad3ef27a079731516056ab6cb73fb5eee4b4 Mon Sep 17 00:00:00 2001 From: Yann Hamon Date: Sun, 11 May 2025 04:55:00 +0200 Subject: [PATCH] WIP - would need a way where I don't need to copy the whole validation function --- pkg/validator/validator.go | 69 +++++++++++++++++++++++++++- pkg/validator/validator_test.go | 81 +++++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+), 2 deletions(-) diff --git a/pkg/validator/validator.go b/pkg/validator/validator.go index 9e6bd64..8d4dc1e 100644 --- a/pkg/validator/validator.go +++ b/pkg/validator/validator.go @@ -292,8 +292,73 @@ func (val *v) Validate(filename string, r io.ReadCloser) []Result { // https://github.com/kubernetes/apiextensions-apiserver/blob/1ecd29f74da0639e2e6e3b8fac0c9bfd217e05eb/pkg/apis/apiextensions/v1/types_jsonschema.go#L71 func validateDuration(v any) error { // Try validation with the Go duration format - if _, err := time.ParseDuration(v.(string)); err != nil { - return err + if _, err := time.ParseDuration(v.(string)); err == nil { + return nil + } + + s, ok := v.(string) + if !ok { + return nil + } + + // must start with 'P' + s, ok = strings.CutPrefix(s, "P") + if !ok { + return fmt.Errorf("must start with P") + } + if s == "" { + return fmt.Errorf("nothing after P") + } + + // dur-week + if s, ok := strings.CutSuffix(s, "W"); ok { + if s == "" { + return fmt.Errorf("no number in week") + } + for _, ch := range s { + if ch < '0' || ch > '9' { + return fmt.Errorf("invalid week") + } + } + return nil + } + + allUnits := []string{"YMD", "HMS"} + for i, s := range strings.Split(s, "T") { + if i != 0 && s == "" { + return fmt.Errorf("no time elements") + } + if i >= len(allUnits) { + return fmt.Errorf("more than one T") + } + units := allUnits[i] + for s != "" { + digitCount := 0 + for _, ch := range s { + if ch >= '0' && ch <= '9' { + digitCount++ + } else { + break + } + } + if digitCount == 0 { + return fmt.Errorf("missing number") + } + s = s[digitCount:] + if s == "" { + return fmt.Errorf("missing unit") + } + unit := s[0] + j := strings.IndexByte(units, unit) + if j == -1 { + if strings.IndexByte(allUnits[i], unit) != -1 { + return fmt.Errorf("unit %q out of order", unit) + } + return fmt.Errorf("invalid unit %q", unit) + } + units = units[j+1:] + s = s[1:] + } } return nil diff --git a/pkg/validator/validator_test.go b/pkg/validator/validator_test.go index 8204862..50f4969 100644 --- a/pkg/validator/validator_test.go +++ b/pkg/validator/validator_test.go @@ -379,6 +379,87 @@ lastName: bar Error, []ValidationError{}, }, + { + "valid resource duration - go format", + []byte(` +kind: name +apiVersion: v1 +interval: 5s +`), + []byte(`{ + "title": "Example Schema", + "type": "object", + "properties": { + "kind": { + "type": "string" + }, + "interval": { + "type": "string", + "format": "duration" + } + }, + "required": ["interval"] +}`), + nil, + false, + false, + Valid, + []ValidationError{}, + }, + { + "valid resource duration - iso8601 format", + []byte(` +kind: name +apiVersion: v1 +interval: PT1H +`), + []byte(`{ + "title": "Example Schema", + "type": "object", + "properties": { + "kind": { + "type": "string" + }, + "interval": { + "type": "string", + "format": "duration" + } + }, + "required": ["interval"] +}`), + nil, + false, + false, + Valid, + []ValidationError{}, + }, + { + "invalid resource duration", + []byte(` +kind: name +apiVersion: v1 +interval: test +`), + []byte(`{ + "title": "Example Schema", + "type": "object", + "properties": { + "kind": { + "type": "string" + }, + "interval": { + "type": "string", + "format": "duration" + } + }, + "required": ["interval"] +}`), + nil, + false, + false, + Invalid, + []ValidationError{{Path: "/interval", Msg: "'test' is not valid duration: must start with P"}}, + }, } { val := v{ opts: Opts{