diff --git a/pkg/builder/openapi.go b/pkg/builder/openapi.go index c33c59626..865aa1e9c 100644 --- a/pkg/builder/openapi.go +++ b/pkg/builder/openapi.go @@ -447,12 +447,34 @@ func (o *openAPI) buildParameter(restParam common.Parameter, bodySample interfac default: return ret, fmt.Errorf("unknown restful operation kind : %v", restParam.Kind()) } - openAPIType, openAPIFormat := common.OpenAPITypeFormat(restParam.DataType()) + dataType := restParam.DataType() + + // AllowMultiple indicates an array parameter. The element type is + // derived by stripping the "[]" prefix from DataType. + elementType := dataType + if restParam.AllowMultiple() { + elementType = strings.TrimPrefix(dataType, "[]") + } + + openAPIType, openAPIFormat := common.OpenAPITypeFormat(elementType) if openAPIType == "" { - return ret, fmt.Errorf("non-body Restful parameter type should be a simple type, but got : %v", restParam.DataType()) + return ret, fmt.Errorf("non-body Restful parameter type should be a simple type, but got: %v", dataType) + } + + if restParam.AllowMultiple() { + ret.Type = "array" + ret.Format = "" + ret.CollectionFormat = "multi" + ret.Items = &spec.Items{ + SimpleSchema: spec.SimpleSchema{ + Type: openAPIType, + Format: openAPIFormat, + }, + } + } else { + ret.Type = openAPIType + ret.Format = openAPIFormat } - ret.Type = openAPIType - ret.Format = openAPIFormat ret.UniqueItems = !restParam.AllowMultiple() return ret, nil } diff --git a/pkg/builder/openapi_test.go b/pkg/builder/openapi_test.go index 50e7a4b22..3b46b3988 100644 --- a/pkg/builder/openapi_test.go +++ b/pkg/builder/openapi_test.go @@ -27,6 +27,7 @@ import ( "github.com/stretchr/testify/assert" openapi "k8s.io/kube-openapi/pkg/common" + "k8s.io/kube-openapi/pkg/common/restfuladapter" "k8s.io/kube-openapi/pkg/util/jsontesting" "k8s.io/kube-openapi/pkg/validation/spec" ) @@ -653,3 +654,73 @@ func TestEscapeJsonPointerInDefinitionName(t *testing.T) { }) } } + +func TestBuildParameter(t *testing.T) { + o := &openAPI{} + + // buildParameter receives *restfuladapter.ParamAdapter at runtime. + newParam := func(dt string, allowMultiple bool) openapi.Parameter { + return &restfuladapter.ParamAdapter{ + Param: restful.QueryParameter("p", "").DataType(dt).AllowMultiple(allowMultiple), + } + } + + tests := []struct { + name string + param openapi.Parameter + wantType string + wantFormat string + wantItems bool + wantErr bool + }{ + { + name: "scalar string", + param: newParam("string", false), + wantType: "string", + }, + { + name: "scalar []byte stays scalar", + param: newParam("[]byte", false), + wantType: "string", + wantFormat: "byte", + }, + { + name: "AllowMultiple=true emits array", + param: newParam("[]string", true), + wantType: "array", + wantItems: true, + }, + { + name: "AllowMultiple=true with plain DataType emits array", + param: newParam("string", true), + wantType: "array", + wantItems: true, + }, + { + name: "[]string without AllowMultiple is a misconfiguration — errors intentionally", + param: newParam("[]string", false), + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := o.buildParameter(tt.param, nil) + if tt.wantErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + assert.Equal(t, tt.wantType, got.Type) + assert.Equal(t, tt.wantFormat, got.Format) + if tt.wantItems { + assert.NotNil(t, got.Items) + assert.Equal(t, "multi", got.CollectionFormat) + } else { + assert.Nil(t, got.Items) + assert.Empty(t, got.CollectionFormat) + } + assert.True(t, got.UniqueItems != tt.param.AllowMultiple()) + }) + } +} diff --git a/pkg/builder3/openapi.go b/pkg/builder3/openapi.go index 94d8706d8..2fbb3037f 100644 --- a/pkg/builder3/openapi.go +++ b/pkg/builder3/openapi.go @@ -419,17 +419,45 @@ func (o *openAPI) buildParameter(restParam common.Parameter) (ret *spec3.Paramet default: return ret, fmt.Errorf("unsupported restful parameter kind : %v", restParam.Kind()) } - openAPIType, openAPIFormat := common.OpenAPITypeFormat(restParam.DataType()) - if openAPIType == "" { - return ret, fmt.Errorf("non-body Restful parameter type should be a simple type, but got : %v", restParam.DataType()) + dataType := restParam.DataType() + + // AllowMultiple indicates an array parameter. The element type is + // derived by stripping the "[]" prefix from DataType. + elementType := dataType + if restParam.AllowMultiple() { + elementType = strings.TrimPrefix(dataType, "[]") } - ret.Schema = &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{openAPIType}, - Format: openAPIFormat, - UniqueItems: !restParam.AllowMultiple(), - }, + elementAPIType, elementAPIFormat := common.OpenAPITypeFormat(elementType) + if elementAPIType == "" { + return ret, fmt.Errorf("non-body Restful parameter type should be a simple type, but got: %v", dataType) + } + + if restParam.AllowMultiple() { + ret.Style = "form" + ret.Explode = true + ret.Schema = &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + UniqueItems: false, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{elementAPIType}, + Format: elementAPIFormat, + }, + }, + }, + }, + } + } else { + ret.Schema = &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{elementAPIType}, + Format: elementAPIFormat, + UniqueItems: true, + }, + } } return ret, nil } diff --git a/pkg/builder3/openapi_test.go b/pkg/builder3/openapi_test.go index cb349cdb5..8974fbd9c 100644 --- a/pkg/builder3/openapi_test.go +++ b/pkg/builder3/openapi_test.go @@ -27,6 +27,7 @@ import ( "github.com/stretchr/testify/assert" openapi "k8s.io/kube-openapi/pkg/common" + "k8s.io/kube-openapi/pkg/common/restfuladapter" "k8s.io/kube-openapi/pkg/spec3" "k8s.io/kube-openapi/pkg/util/jsontesting" "k8s.io/kube-openapi/pkg/validation/spec" @@ -581,3 +582,84 @@ func TestEscapeJsonPointerInSchemaName(t *testing.T) { }) } } + +func TestBuildParameter(t *testing.T) { + o := &openAPI{} + + // buildParameter receives *restfuladapter.ParamAdapter at runtime. + newParam := func(dt string, allowMultiple bool) openapi.Parameter { + return &restfuladapter.ParamAdapter{ + Param: restful.QueryParameter("p", "").DataType(dt).AllowMultiple(allowMultiple), + } + } + + tests := []struct { + name string + param openapi.Parameter + wantType string + wantFormat string + wantItems bool + wantStyle string + wantExplode bool + wantErr bool + }{ + { + name: "scalar string", + param: newParam("string", false), + wantType: "string", + }, + { + name: "scalar []byte stays scalar", + param: newParam("[]byte", false), + wantType: "string", + wantFormat: "byte", + }, + { + name: "AllowMultiple=true emits array", + param: newParam("[]string", true), + wantType: "array", + wantItems: true, + wantStyle: "form", + wantExplode: true, + }, + { + name: "AllowMultiple=true with plain DataType emits array", + param: newParam("string", true), + wantType: "array", + wantItems: true, + wantStyle: "form", + wantExplode: true, + }, + { + name: "[]string without AllowMultiple is a misconfiguration — errors intentionally", + param: newParam("[]string", false), + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := o.buildParameter(tt.param) + if tt.wantErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + assert.NotNil(t, got.Schema, "schema should not be nil") + assert.Equal(t, spec.StringOrArray{tt.wantType}, got.Schema.Type) + assert.Equal(t, tt.wantFormat, got.Schema.Format) + assert.True(t, got.Schema.UniqueItems != tt.param.AllowMultiple()) + if tt.wantItems { + assert.NotNil(t, got.Schema.Items, "items should not be nil") + assert.NotNil(t, got.Schema.Items.Schema, "items schema should not be nil") + assert.Equal(t, spec.StringOrArray{"string"}, got.Schema.Items.Schema.Type) + assert.Equal(t, tt.wantStyle, got.Style) + assert.Equal(t, tt.wantExplode, got.Explode) + } else { + assert.Nil(t, got.Schema.Items) + assert.Empty(t, got.Style) + assert.False(t, got.Explode) + } + }) + } +}