Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
4 changes: 3 additions & 1 deletion pkg/simpleschema/markers.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,14 @@ const (
MarkerTypeMinimum MarkerType = "minimum"
// MarkerTypeMaximum represents the `maximum` marker.
MarkerTypeMaximum MarkerType = "maximum"
// MarkerTypeEnum represents the `enum` marker.
MarkerTypeEnum MarkerType = "enum"
)

func markerTypeFromString(s string) (MarkerType, error) {
switch MarkerType(s) {
case MarkerTypeRequired, MarkerTypeDefault, MarkerTypeDescription,
MarkerTypeMinimum, MarkerTypeMaximum:
MarkerTypeMinimum, MarkerTypeMaximum, MarkerTypeEnum:
return MarkerType(s), nil
default:
return "", fmt.Errorf("unknown marker type: %s", s)
Expand Down
35 changes: 33 additions & 2 deletions pkg/simpleschema/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package simpleschema
import (
"fmt"
"strconv"
"strings"

extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
)
Expand Down Expand Up @@ -116,7 +117,9 @@ func (tf *transformer) parseFieldSchema(key, fieldValue string, parentSchema *ex
fieldJSONSchemaProps = &preDefinedType
}

tf.applyMarkers(fieldJSONSchemaProps, markers, key, parentSchema)
if err := tf.applyMarkers(fieldJSONSchemaProps, markers, key, parentSchema); err != nil {
return nil, fmt.Errorf("failed to apply markers: %w", err)
}

return fieldJSONSchemaProps, nil
}
Expand Down Expand Up @@ -184,7 +187,7 @@ func (tf *transformer) handleSliceType(key, fieldType string) (*extv1.JSONSchema
return fieldJSONSchemaProps, nil
}

func (tf *transformer) applyMarkers(schema *extv1.JSONSchemaProps, markers []*Marker, key string, parentSchema *extv1.JSONSchemaProps) {
func (tf *transformer) applyMarkers(schema *extv1.JSONSchemaProps, markers []*Marker, key string, parentSchema *extv1.JSONSchemaProps) error {
for _, marker := range markers {
switch marker.MarkerType {
case MarkerTypeRequired:
Expand Down Expand Up @@ -212,8 +215,36 @@ func (tf *transformer) applyMarkers(schema *extv1.JSONSchemaProps, markers []*Ma
if val, err := strconv.ParseFloat(marker.Value, 64); err == nil {
schema.Maximum = &val
}
case MarkerTypeEnum:
var enumJSONValues []extv1.JSON

enumValues := strings.Split(marker.Value, ",")
for _, val := range enumValues {
val = strings.TrimSpace(val)
if val == "" {
return fmt.Errorf("empty enum values are not allowed")
}

var rawValue []byte
switch schema.Type {
case "string":
rawValue = []byte(fmt.Sprintf("%q", val))
case "integer":
if _, err := strconv.ParseInt(val, 10, 64); err != nil {
return fmt.Errorf("failed to parse integer enum value: %w", err)
}
rawValue = []byte(val)
default:
return fmt.Errorf("enum values only supported for string and integer types, got type: %s", schema.Type)
}
enumJSONValues = append(enumJSONValues, extv1.JSON{Raw: rawValue})
}
if len(enumJSONValues) > 0 {
schema.Enum = enumJSONValues
}
}
}
return nil
}

// Other functions (LoadPreDefinedTypes, transformMap) remain unchanged
Expand Down
81 changes: 81 additions & 0 deletions pkg/simpleschema/transform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,87 @@ func TestBuildOpenAPISchema(t *testing.T) {
},
wantErr: false,
},
{
name: "Schema with multiple enum types",
obj: map[string]interface{}{
"logLevel": "string | enum=\"debug,info,warn,error\" default=\"info\"",
"features": map[string]interface{}{
"logFormat": "string | enum=\"json,text,csv\" default=\"json\"",
"errorCode": "integer | enum=\"400,404,500\" default=500",
},
},
want: &extv1.JSONSchemaProps{
Type: "object",
Properties: map[string]extv1.JSONSchemaProps{
"logLevel": {
Type: "string",
Default: &extv1.JSON{Raw: []byte("\"info\"")},
Enum: []extv1.JSON{
{Raw: []byte("\"debug\"")},
{Raw: []byte("\"info\"")},
{Raw: []byte("\"warn\"")},
{Raw: []byte("\"error\"")},
},
},
"features": {
Type: "object",
Properties: map[string]extv1.JSONSchemaProps{
"logFormat": {
Type: "string",
Default: &extv1.JSON{Raw: []byte("\"json\"")},
Enum: []extv1.JSON{
{Raw: []byte("\"json\"")},
{Raw: []byte("\"text\"")},
{Raw: []byte("\"csv\"")},
},
},
"errorCode": {
Type: "integer",
Default: &extv1.JSON{Raw: []byte("500")},
Enum: []extv1.JSON{
{Raw: []byte("400")},
{Raw: []byte("404")},
{Raw: []byte("500")},
},
},
},
},
},
},
wantErr: false,
},
{
name: "invalid enum type",
obj: map[string]interface{}{
"threshold": "integer | enum=\"1,2,three\"",
},
want: nil,
wantErr: true,
},
{
name: "Invalid integer enum - empty values",
obj: map[string]interface{}{
"errorCode": "integer | enum=\"1,,3\"",
},
want: nil,
wantErr: true,
},
{
name: "Invalid integer enum - parsing failure",
obj: map[string]interface{}{
"errorCode": "integer | enum=\"1,2,3,abc\"",
},
want: nil,
wantErr: true,
},
{
name: "invalid string enum marker",
obj: map[string]interface{}{
"status": "string | enum=\"a,b,,c\"",
},
want: nil,
wantErr: true,
},
}

for _, tt := range tests {
Expand Down