mirror of
https://github.com/kubevela/kubevela.git
synced 2026-02-14 18:10:21 +00:00
Feat: set right default value for oneOf schema if set (#5677)
This commit is contained in:
@@ -31,6 +31,7 @@ import (
|
||||
"cuelang.org/go/cue"
|
||||
"cuelang.org/go/encoding/openapi"
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/kubevela/pkg/util/slices"
|
||||
"github.com/kubevela/workflow/pkg/cue/model/value"
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
@@ -325,6 +326,10 @@ func (g *Generator) GenOpenAPISchema(val *value.Value) error {
|
||||
g.completeOpenAPISchema(doc)
|
||||
openapiSchema, err := doc.MarshalJSON()
|
||||
g.openapiSchema = openapiSchema
|
||||
if g.meta.Verbose {
|
||||
fmt.Println("OpenAPI schema:")
|
||||
fmt.Println(string(g.openapiSchema))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -414,10 +419,10 @@ func (g *Generator) GenerateCode() (err error) {
|
||||
func completeFreeFormSchema(schema *openapi3.SchemaRef) {
|
||||
v := schema.Value
|
||||
if v.OneOf == nil && v.AnyOf == nil && v.AllOf == nil && v.Properties == nil {
|
||||
if v.Type == "object" {
|
||||
if v.Type == openapi3.TypeObject {
|
||||
schema.Value.AdditionalProperties = &openapi3.SchemaRef{
|
||||
Value: &openapi3.Schema{
|
||||
Type: "object",
|
||||
Type: openapi3.TypeObject,
|
||||
Nullable: true,
|
||||
},
|
||||
}
|
||||
@@ -431,24 +436,35 @@ func completeFreeFormSchema(schema *openapi3.SchemaRef) {
|
||||
}
|
||||
}
|
||||
|
||||
// fixSchemaWithOneAnyAllOf move properties in the root schema to sub schema in OneOf, AnyOf, AllOf
|
||||
// See https://github.com/OpenAPITools/openapi-generator/issues/14250
|
||||
func fixSchemaWithOneAnyAllOf(schema *openapi3.SchemaRef) {
|
||||
// fixSchemaWithOneOf do some fix for schema with OneOf.
|
||||
// 1. move properties in the root schema to sub schema in OneOf. See https://github.com/OpenAPITools/openapi-generator/issues/14250
|
||||
// 2. move default value to sub schema in OneOf.
|
||||
// 3. remove duplicated type in OneOf.
|
||||
func fixSchemaWithOneOf(schema *openapi3.SchemaRef) {
|
||||
var schemaNeedFix []*openapi3.Schema
|
||||
refs := []openapi3.SchemaRefs{schema.Value.OneOf, schema.Value.AnyOf, schema.Value.AllOf}
|
||||
for _, ref := range refs {
|
||||
for _, s := range ref {
|
||||
completeSchemas(s.Value.Properties)
|
||||
// If the schema is without type or ref. It may need to be fixed.
|
||||
// Cases can be:
|
||||
// 1. A non-ref sub-schema maybe have no properties and the needed properties is in the root schema.
|
||||
// 2. A sub-schema maybe have no type and the needed type is in the root schema.
|
||||
// In both cases, we need to complete the sub-schema with the properties or type in the root schema if any of them is missing.
|
||||
if s.Ref == "" || s.Value.Type == "" {
|
||||
schemaNeedFix = append(schemaNeedFix, s.Value)
|
||||
}
|
||||
|
||||
oneOf := schema.Value.OneOf
|
||||
typeSet := make(map[string]struct{})
|
||||
duplicateIndex := make([]int, 0)
|
||||
// If the schema have default value, it should be moved to sub-schema with right type.
|
||||
defaultValue := schema.Value.Default
|
||||
schema.Value.Default = nil
|
||||
for _, s := range oneOf {
|
||||
completeSchemas(s.Value.Properties)
|
||||
if defaultValueMatchOneOfItem(s.Value, defaultValue) {
|
||||
s.Value.Default = defaultValue
|
||||
}
|
||||
|
||||
// If the schema is without type or ref. It may need to be fixed.
|
||||
// Cases can be:
|
||||
// 1. A non-ref sub-schema maybe have no properties and the needed properties is in the root schema.
|
||||
// 2. A sub-schema maybe have no type and the needed type is in the root schema.
|
||||
// In both cases, we need to complete the sub-schema with the properties or type in the root schema if any of them is missing.
|
||||
if s.Value.Properties == nil || s.Value.Type == "" {
|
||||
schemaNeedFix = append(schemaNeedFix, s.Value)
|
||||
}
|
||||
}
|
||||
|
||||
if schemaNeedFix == nil {
|
||||
return // no non-ref schema found
|
||||
}
|
||||
@@ -461,12 +477,34 @@ func fixSchemaWithOneAnyAllOf(schema *openapi3.SchemaRef) {
|
||||
}
|
||||
}
|
||||
schema.Value.Properties = nil
|
||||
|
||||
// remove duplicated type
|
||||
for i, s := range oneOf {
|
||||
if s.Value.Type == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := typeSet[s.Value.Type]; ok && s.Value.Type != openapi3.TypeObject {
|
||||
duplicateIndex = append(duplicateIndex, i)
|
||||
} else {
|
||||
typeSet[s.Value.Type] = struct{}{}
|
||||
}
|
||||
}
|
||||
if len(duplicateIndex) > 0 {
|
||||
newRefs := make(openapi3.SchemaRefs, 0, len(oneOf)-len(duplicateIndex))
|
||||
for i, s := range oneOf {
|
||||
if !slices.Contains(duplicateIndex, i) {
|
||||
newRefs = append(newRefs, s)
|
||||
}
|
||||
}
|
||||
schema.Value.OneOf = newRefs
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func completeSchema(key string, schema *openapi3.SchemaRef) {
|
||||
schema.Value.Title = key
|
||||
if schema.Value.OneOf != nil || schema.Value.AnyOf != nil || schema.Value.AllOf != nil {
|
||||
fixSchemaWithOneAnyAllOf(schema)
|
||||
if schema.Value.OneOf != nil {
|
||||
fixSchemaWithOneOf(schema)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -476,9 +514,9 @@ func completeSchema(key string, schema *openapi3.SchemaRef) {
|
||||
// schema.Value.Required = []string{}
|
||||
|
||||
switch schema.Value.Type {
|
||||
case "object":
|
||||
case openapi3.TypeObject:
|
||||
completeSchemas(schema.Value.Properties)
|
||||
case "array":
|
||||
case openapi3.TypeArray:
|
||||
completeSchema(key, schema.Value.Items)
|
||||
}
|
||||
|
||||
@@ -509,3 +547,63 @@ func newModifierOnLanguage(lang string, generator *Generator) Modifier {
|
||||
panic("unsupported language: " + lang)
|
||||
}
|
||||
}
|
||||
|
||||
// getValueType returns the cue type of the value
|
||||
func getValueType(i interface{}) CUEType {
|
||||
if i == nil {
|
||||
return ""
|
||||
}
|
||||
switch i.(type) {
|
||||
case string:
|
||||
return "string"
|
||||
case int:
|
||||
return "integer"
|
||||
case float64, float32:
|
||||
return "number"
|
||||
case bool:
|
||||
return "boolean"
|
||||
case map[string]interface{}:
|
||||
return "object"
|
||||
case []interface{}:
|
||||
return "array"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// CUEType is the possible types in CUE
|
||||
type CUEType string
|
||||
|
||||
func (t CUEType) fit(schema *openapi3.Schema) bool {
|
||||
openapiType := schema.Type
|
||||
switch t {
|
||||
case "string":
|
||||
return openapiType == "string"
|
||||
case "integer":
|
||||
return openapiType == "integer" || openapiType == "number"
|
||||
case "number":
|
||||
return openapiType == "number"
|
||||
case "boolean":
|
||||
return openapiType == "boolean"
|
||||
case "array":
|
||||
return openapiType == "array"
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// defaultValueMatchOneOfItem checks if the default value matches one of the items in the oneOf schema.
|
||||
func defaultValueMatchOneOfItem(item *openapi3.Schema, defaultValue interface{}) bool {
|
||||
if item.Default != nil {
|
||||
return false
|
||||
}
|
||||
defaultValueType := getValueType(defaultValue)
|
||||
// let's skip the case that default value is object because it's hard to match now.
|
||||
if defaultValueType == "" || defaultValueType == openapi3.TypeObject {
|
||||
return false
|
||||
}
|
||||
if defaultValueType != "" && defaultValueType.fit(item) && item.Default == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
@@ -118,3 +119,91 @@ var _ = Describe("Test Generating SDK", func() {
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
var _ = Describe("FixSchemaWithOneAnyAllOf", func() {
|
||||
var (
|
||||
schema *openapi3.SchemaRef
|
||||
)
|
||||
|
||||
It("should set default value to right sub-schema", func() {
|
||||
By(`cpu?: *1 | number | string`)
|
||||
schema = &openapi3.SchemaRef{
|
||||
Ref: "",
|
||||
Value: &openapi3.Schema{
|
||||
Default: 1,
|
||||
OneOf: openapi3.SchemaRefs{
|
||||
{
|
||||
Value: &openapi3.Schema{
|
||||
Type: "number",
|
||||
},
|
||||
},
|
||||
{
|
||||
Value: &openapi3.Schema{
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
fixSchemaWithOneOf(schema)
|
||||
|
||||
Expect(schema.Value.OneOf[0].Value.Default).To(Equal(1))
|
||||
Expect(schema.Value.OneOf[1].Value.Default).To(BeNil())
|
||||
Expect(schema.Value.Default).To(BeNil())
|
||||
})
|
||||
|
||||
It("should remove duplicated type in oneOf", func() {
|
||||
By(`language: "go" | "java" | "python" | "node" | "ruby" | string`)
|
||||
By(`image: language | string`)
|
||||
schema = &openapi3.SchemaRef{
|
||||
Value: &openapi3.Schema{
|
||||
Type: "string",
|
||||
Title: "image",
|
||||
OneOf: openapi3.SchemaRefs{
|
||||
{
|
||||
Value: &openapi3.Schema{
|
||||
Type: "string",
|
||||
Enum: []interface{}{"go", "java", "python", "node", "ruby"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Value: &openapi3.Schema{
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
fixSchemaWithOneOf(schema)
|
||||
|
||||
Expect(schema.Value.OneOf).To(HaveLen(1))
|
||||
Expect(schema.Value.OneOf[0].Value.Type).To(Equal("string"))
|
||||
Expect(schema.Value.OneOf[0].Value.Enum).To(Equal([]interface{}{"go", "java", "python", "node", "ruby"}))
|
||||
})
|
||||
|
||||
It("should both move type and remove duplicated type in oneOf", func() {
|
||||
schema = &openapi3.SchemaRef{
|
||||
Value: &openapi3.Schema{
|
||||
Type: "string",
|
||||
Title: "image",
|
||||
OneOf: openapi3.SchemaRefs{
|
||||
{
|
||||
Value: &openapi3.Schema{
|
||||
Enum: []interface{}{"go", "java", "python", "node", "ruby"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Value: &openapi3.Schema{
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fixSchemaWithOneOf(schema)
|
||||
Expect(schema.Value.OneOf).To(HaveLen(1))
|
||||
Expect(schema.Value.OneOf[0].Value.Type).To(Equal("string"))
|
||||
Expect(schema.Value.OneOf[0].Value.Enum).To(Equal([]interface{}{"go", "java", "python", "node", "ruby"}))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -5,15 +5,27 @@ type {{classname}} struct {
|
||||
{{/oneOf}}
|
||||
}
|
||||
|
||||
{{#oneOf}}
|
||||
// {{{.}}}As{{classname}} is a convenience function that returns {{{.}}} wrapped in {{classname}}
|
||||
func {{#lambda.type-to-name}}{{{.}}}{{/lambda.type-to-name}}As{{classname}}(v *{{{.}}}) {{classname}} {
|
||||
return {{classname}}{
|
||||
{{#lambda.type-to-name}}{{{.}}}{{/lambda.type-to-name}}: v,
|
||||
}
|
||||
}
|
||||
{{#composedSchemas}}
|
||||
{{#oneOf}}
|
||||
// {{#lambda.type-to-name}}{{datatype}}{{/lambda.type-to-name}}As{{classname}} is is a convenience function that returns {{datatype}} wrapped in {{classname}}
|
||||
func {{#lambda.type-to-name}}{{datatype}}{{/lambda.type-to-name}}As{{classname}}(v *{{datatype}}) {{classname}} {
|
||||
return {{classname}}{
|
||||
{{#lambda.type-to-name}}{{#lambda.type-to-name}}{{datatype}}{{/lambda.type-to-name}}{{/lambda.type-to-name}}: v,
|
||||
}
|
||||
}
|
||||
|
||||
{{#defaultValue}}
|
||||
// {{#lambda.type-to-name}}{{datatype}}{{/lambda.type-to-name}}As{{classname}}OrDefault returns {{datatype}} wrapped in {{classname}} if not nil, or a default value if nil
|
||||
func {{#lambda.type-to-name}}{{datatype}}{{/lambda.type-to-name}}As{{classname}}OrDefault(v *{{datatype}}) {{classname}} {
|
||||
if v == nil {
|
||||
return {{#lambda.type-to-name}}{{datatype}}{{/lambda.type-to-name}}As{{classname}}(utils.Ptr{{#lambda.type-to-name}}{{datatype}}{{/lambda.type-to-name}}({{datatype}}({{defaultValue}})))
|
||||
}
|
||||
return {{#lambda.type-to-name}}{{datatype}}{{/lambda.type-to-name}}As{{classname}}(v)
|
||||
}
|
||||
{{/defaultValue}}
|
||||
{{/oneOf}}
|
||||
{{/composedSchemas}}
|
||||
|
||||
{{/oneOf}}
|
||||
|
||||
// Validate validates this {{classname}}
|
||||
func (o *{{classname}}) Validate() error{
|
||||
|
||||
Reference in New Issue
Block a user