github.com/oam-dev/kubevela@v1.9.11/pkg/definition/gen_sdk/gen_sdk.go (about) 1 /* 2 Copyright 2021 The KubeVela Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package gen_sdk 18 19 import ( 20 "bytes" 21 "fmt" 22 "io" 23 "io/fs" 24 "os" 25 "os/exec" 26 "path" 27 "path/filepath" 28 "reflect" 29 "runtime" 30 "runtime/debug" 31 "strings" 32 33 "cuelang.org/go/cue" 34 "cuelang.org/go/encoding/openapi" 35 "github.com/getkin/kin-openapi/openapi3" 36 "github.com/kubevela/pkg/util/slices" 37 "github.com/kubevela/workflow/pkg/cue/model/value" 38 "github.com/pkg/errors" 39 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 40 "k8s.io/client-go/rest" 41 "k8s.io/klog/v2" 42 43 velacue "github.com/oam-dev/kubevela/pkg/cue" 44 "github.com/oam-dev/kubevela/pkg/definition" 45 "github.com/oam-dev/kubevela/pkg/stdlib" 46 "github.com/oam-dev/kubevela/pkg/utils/common" 47 "github.com/oam-dev/kubevela/pkg/utils/system" 48 ) 49 50 type byteHandler func([]byte) []byte 51 52 var ( 53 defaultAPIDir = map[string]string{ 54 "go": "pkg/apis", 55 } 56 // LangArgsRegistry is used to store the argument info 57 LangArgsRegistry = map[string]map[langArgKey]LangArg{} 58 ) 59 60 // GenMeta stores the metadata for generator. 61 type GenMeta struct { 62 config *rest.Config 63 name string 64 kind string 65 66 Output string 67 APIDirectory string 68 IsSubModule bool 69 Lang string 70 Package string 71 Template string 72 File []string 73 InitSDK bool 74 Verbose bool 75 76 LangArgs LanguageArgs 77 78 cuePaths []string 79 templatePath string 80 packageFunc byteHandler 81 } 82 83 // Generator is used to generate SDK code from CUE template for one language. 84 type Generator struct { 85 meta *GenMeta 86 def definition.Definition 87 openapiSchema []byte 88 // defModifiers are the modifiers for each definition. 89 defModifiers []Modifier 90 // moduleModifiers are the modifiers for the whole module. It will be executed after generating all definitions. 91 moduleModifiers []Modifier 92 } 93 94 // LanguageArgs is used to store the arguments for the language. 95 type LanguageArgs interface { 96 Get(key langArgKey) string 97 Set(key langArgKey, value string) 98 } 99 100 // langArgKey is language argument key. 101 type langArgKey string 102 103 // LangArg is language-specific argument. 104 type LangArg struct { 105 Name langArgKey 106 Desc string 107 Default string 108 } 109 110 // registerLangArg should be called in init() function of each language. 111 func registerLangArg(lang string, arg ...LangArg) { 112 if _, ok := LangArgsRegistry[lang]; !ok { 113 LangArgsRegistry[lang] = map[langArgKey]LangArg{} 114 } 115 for _, a := range arg { 116 LangArgsRegistry[lang][a.Name] = a 117 } 118 } 119 120 // NewLanguageArgs parses the language arguments and returns a LanguageArgs. 121 func NewLanguageArgs(lang string, langArgs []string) (LanguageArgs, error) { 122 availableArgs := LangArgsRegistry[lang] 123 res := languageArgs{} 124 for _, arg := range langArgs { 125 parts := strings.Split(arg, "=") 126 if len(parts) != 2 { 127 return nil, errors.Errorf("argument %s is not in the format of key=value", arg) 128 } 129 if _, ok := availableArgs[langArgKey(parts[0])]; !ok { 130 return nil, errors.Errorf("argument %s is not supported for language %s", parts[0], lang) 131 } 132 res.Set(langArgKey(parts[0]), parts[1]) 133 } 134 for k, v := range availableArgs { 135 if res.Get(k) == "" { 136 res.Set(k, v.Default) 137 } 138 } 139 return res, nil 140 } 141 142 type languageArgs map[string]string 143 144 func (l languageArgs) Get(key langArgKey) string { 145 return l[string(key)] 146 } 147 148 func (l languageArgs) Set(key langArgKey, value string) { 149 l[string(key)] = value 150 } 151 152 // Modifier is used to modify the generated code. 153 type Modifier interface { 154 Modify() error 155 Name() string 156 } 157 158 // Init initializes the generator. 159 // It will validate the param, analyze the CUE files, read them to memory, mkdir for output. 160 func (meta *GenMeta) Init(c common.Args, langArgs []string) (err error) { 161 meta.config, err = c.GetConfig() 162 if err != nil { 163 klog.Info("No kubeconfig found, skipping") 164 } 165 err = stdlib.SetupBuiltinImports() 166 if err != nil { 167 return err 168 } 169 if _, ok := SupportedLangs[meta.Lang]; !ok { 170 return fmt.Errorf("language %s is not supported", meta.Lang) 171 } 172 173 // Init arguments 174 if meta.APIDirectory == "" { 175 meta.APIDirectory = defaultAPIDir[meta.Lang] 176 } 177 178 meta.LangArgs, err = NewLanguageArgs(meta.Lang, langArgs) 179 if err != nil { 180 return err 181 } 182 packageFuncs := map[string]byteHandler{ 183 "go": func(b []byte) []byte { 184 return bytes.ReplaceAll(b, []byte(PackagePlaceHolder), []byte(meta.Package)) 185 }, 186 } 187 188 meta.packageFunc = packageFuncs[meta.Lang] 189 190 // Analyze the all cue files from meta.File. It can be file or directory. If directory is given, it will recursively 191 // analyze all cue files in the directory. 192 for _, f := range meta.File { 193 info, err := os.Stat(f) 194 if err != nil { 195 return err 196 } 197 if info.IsDir() { 198 err = filepath.Walk(f, func(path string, info os.FileInfo, err error) error { 199 if err != nil { 200 return err 201 } 202 if !info.IsDir() && strings.HasSuffix(path, ".cue") { 203 meta.cuePaths = append(meta.cuePaths, path) 204 } 205 return nil 206 }) 207 if err != nil { 208 return err 209 } 210 } else if strings.HasSuffix(f, ".cue") { 211 meta.cuePaths = append(meta.cuePaths, f) 212 } 213 214 } 215 return os.MkdirAll(meta.Output, 0750) 216 } 217 218 // CreateScaffold will create a scaffold for the given language. 219 // It will copy all files from embedded scaffold/{meta.Lang} to meta.Output. 220 func (meta *GenMeta) CreateScaffold() error { 221 if !meta.InitSDK { 222 return nil 223 } 224 klog.Info("Flag --init is set, creating scaffold...") 225 langDirPrefix := fmt.Sprintf("%s/%s", ScaffoldDir, meta.Lang) 226 err := fs.WalkDir(Scaffold, ScaffoldDir, func(_path string, d fs.DirEntry, err error) error { 227 if err != nil { 228 return err 229 } 230 if d.IsDir() { 231 if !strings.HasPrefix(_path, langDirPrefix) && _path != ScaffoldDir { 232 return fs.SkipDir 233 } 234 return nil 235 } 236 fileContent, err := Scaffold.ReadFile(_path) 237 if err != nil { 238 return err 239 } 240 fileContent = meta.packageFunc(fileContent) 241 fileName := path.Join(meta.Output, strings.TrimPrefix(_path, langDirPrefix)) 242 // go.mod_ is a special file name, it will be renamed to go.mod. Go will ignore directory containing go.mod during the build process. 243 fileName = strings.ReplaceAll(fileName, "go.mod_", "go.mod") 244 fileDir := path.Dir(fileName) 245 if err = os.MkdirAll(fileDir, 0750); err != nil { 246 return err 247 } 248 return os.WriteFile(fileName, fileContent, 0600) 249 }) 250 return err 251 } 252 253 // PrepareGeneratorAndTemplate will make a copy of the embedded openapi-generator-cli and templates/{meta.Lang} to local 254 func (meta *GenMeta) PrepareGeneratorAndTemplate() error { 255 var err error 256 ogImageName := "openapitools/openapi-generator-cli" 257 ogImageTag := "v6.3.0" 258 ogImage := fmt.Sprintf("%s:%s", ogImageName, ogImageTag) 259 homeDir, err := system.GetVelaHomeDir() 260 if err != nil { 261 return err 262 } 263 sdkDir := path.Join(homeDir, "sdk") 264 if err = os.MkdirAll(sdkDir, 0750); err != nil { 265 return err 266 } 267 268 // nolint:gosec 269 output, err := exec.Command("docker", "image", "ls", ogImage).CombinedOutput() 270 if err != nil { 271 return errors.Wrapf(err, "failed to check image %s: %s", ogImage, output) 272 } 273 if !strings.Contains(string(output), ogImageName) { 274 // nolint:gosec 275 output, err = exec.Command("docker", "pull", ogImage).CombinedOutput() 276 if err != nil { 277 return errors.Wrapf(err, "failed to pull %s: %s", ogImage, output) 278 } 279 } 280 281 // copy embedded templates/{meta.Lang} to sdkDir 282 if meta.Template == "" { 283 langDir := path.Join(sdkDir, "templates", meta.Lang) 284 if err = os.MkdirAll(langDir, 0750); err != nil { 285 return err 286 } 287 langTemplateDir := path.Join("openapi-generator", "templates", meta.Lang) 288 langTemplateFiles, err := Templates.ReadDir(langTemplateDir) 289 if err != nil { 290 return err 291 } 292 for _, langTemplateFile := range langTemplateFiles { 293 src, err := Templates.Open(path.Join(langTemplateDir, langTemplateFile.Name())) 294 if err != nil { 295 return err 296 } 297 // nolint:gosec 298 dst, err := os.Create(path.Join(langDir, langTemplateFile.Name())) 299 if err != nil { 300 return err 301 } 302 _, err = io.Copy(dst, src) 303 _ = dst.Close() 304 _ = src.Close() 305 if err != nil { 306 return err 307 } 308 } 309 meta.templatePath = langDir 310 } else { 311 meta.templatePath, err = filepath.Abs(meta.Template) 312 if err != nil { 313 return errors.Wrap(err, "failed to get absolute path of template") 314 } 315 } 316 return nil 317 } 318 319 // Run will generally do two thing: 320 // 1. Generate OpenAPI schema from cue files 321 // 2. Generate code from OpenAPI schema 322 func (meta *GenMeta) Run() error { 323 g := NewModifiableGenerator(meta) 324 if len(meta.cuePaths) == 0 { 325 return nil 326 } 327 APIGenerated := false 328 for _, cuePath := range meta.cuePaths { 329 klog.Infof("Generating API for %s", cuePath) 330 // nolint:gosec 331 cueBytes, err := os.ReadFile(cuePath) 332 if err != nil { 333 return errors.Wrapf(err, "failed to read %s", cuePath) 334 } 335 template, defName, defKind, err := g.GetDefinitionValue(cueBytes) 336 if err != nil { 337 return err 338 } 339 g.meta.SetDefinition(defName, defKind) 340 341 err = g.GenOpenAPISchema(template) 342 if err != nil { 343 if strings.Contains(err.Error(), "unsupported node string (*ast.Ident)") { 344 // https://github.com/cue-lang/cue/issues/2259 345 klog.Warningf("Skip generating OpenAPI schema for %s, known issue: %s", cuePath, err.Error()) 346 continue 347 } 348 return errors.Wrapf(err, "generate OpenAPI schema") 349 } 350 351 err = g.GenerateCode() 352 if err != nil { 353 return err 354 } 355 APIGenerated = true 356 } 357 if !APIGenerated { 358 return nil 359 } 360 for _, m := range g.moduleModifiers { 361 err := m.Modify() 362 if err != nil { 363 return err 364 } 365 } 366 367 return nil 368 } 369 370 // SetDefinition sets definition name and kind 371 func (meta *GenMeta) SetDefinition(defName, defKind string) { 372 meta.name = defName 373 meta.kind = defKind 374 } 375 376 // GetDefinitionValue returns a value.Value definition name, definition kind from cue bytes 377 func (g *Generator) GetDefinitionValue(cueBytes []byte) (*value.Value, string, string, error) { 378 g.def = definition.Definition{Unstructured: unstructured.Unstructured{}} 379 if err := g.def.FromCUEString(string(cueBytes), g.meta.config); err != nil { 380 return nil, "", "", errors.Wrapf(err, "failed to parse CUE") 381 } 382 383 templateString, _, err := unstructured.NestedString(g.def.Object, definition.DefinitionTemplateKeys...) 384 if err != nil { 385 return nil, "", "", err 386 } 387 if templateString == "" { 388 return nil, "", "", errors.New("definition doesn't include cue schematic") 389 } 390 template, err := value.NewValue(templateString+velacue.BaseTemplate, nil, "") 391 if err != nil { 392 return nil, "", "", err 393 } 394 return template, g.def.GetName(), g.def.GetKind(), nil 395 } 396 397 // GenOpenAPISchema generates OpenAPI json schema from cue.Instance 398 func (g *Generator) GenOpenAPISchema(val *value.Value) error { 399 var err error 400 defer func() { 401 if r := recover(); r != nil { 402 err = fmt.Errorf("invalid cue definition to generate open api: %v", r) 403 debug.PrintStack() 404 return 405 } 406 }() 407 if val.CueValue().Err() != nil { 408 return val.CueValue().Err() 409 } 410 paramOnlyVal, err := common.RefineParameterValue(val) 411 if err != nil { 412 return err 413 } 414 defaultConfig := &openapi.Config{ExpandReferences: false, NameFunc: func(val cue.Value, path cue.Path) string { 415 sels := path.Selectors() 416 lastLabel := sels[len(sels)-1].String() 417 return strings.TrimPrefix(lastLabel, "#") 418 }, DescriptionFunc: func(v cue.Value) string { 419 for _, d := range v.Doc() { 420 if strings.HasPrefix(d.Text(), "+usage=") { 421 return strings.TrimPrefix(d.Text(), "+usage=") 422 } 423 } 424 return "" 425 }} 426 b, err := openapi.Gen(paramOnlyVal, defaultConfig) 427 if err != nil { 428 return err 429 } 430 doc, err := openapi3.NewLoader().LoadFromData(b) 431 if err != nil { 432 return err 433 } 434 435 g.completeOpenAPISchema(doc) 436 openapiSchema, err := doc.MarshalJSON() 437 g.openapiSchema = openapiSchema 438 if g.meta.Verbose { 439 klog.Info("OpenAPI schema:") 440 klog.Info(string(g.openapiSchema)) 441 } 442 return err 443 } 444 445 func (g *Generator) completeOpenAPISchema(doc *openapi3.T) { 446 for key, schema := range doc.Components.Schemas { 447 switch key { 448 case "parameter": 449 spec := g.meta.name + "-spec" 450 schema.Value.Title = spec 451 completeFreeFormSchema(schema) 452 completeSchema(key, schema) 453 doc.Components.Schemas[spec] = schema 454 delete(doc.Components.Schemas, key) 455 case g.meta.name + "-spec": 456 continue 457 default: 458 completeSchema(key, schema) 459 } 460 } 461 } 462 463 // GenerateCode will call openapi-generator to generate code and modify it 464 func (g *Generator) GenerateCode() (err error) { 465 tmpFile, err := os.CreateTemp("", g.meta.name+"-*.json") 466 if err != nil { 467 return err 468 } 469 _, err = tmpFile.Write(g.openapiSchema) 470 if err != nil { 471 return errors.Wrap(err, "write openapi schema to temporary file") 472 } 473 defer func() { 474 _ = tmpFile.Close() 475 if err == nil { 476 _ = os.Remove(tmpFile.Name()) 477 } 478 }() 479 apiDir, err := filepath.Abs(path.Join(g.meta.Output, g.meta.APIDirectory)) 480 if err != nil { 481 return errors.Wrapf(err, "get absolute path of %s", apiDir) 482 } 483 err = os.MkdirAll(path.Join(apiDir, definition.DefinitionKindToType[g.meta.kind]), 0750) 484 if err != nil { 485 return errors.Wrapf(err, "create directory %s", apiDir) 486 } 487 488 // nolint:gosec 489 cmd := exec.Command("docker", "run", 490 "-v", fmt.Sprintf("%s:/local/output", apiDir), 491 "-v", fmt.Sprintf("%s:/local/input", filepath.Dir(tmpFile.Name())), 492 "-v", fmt.Sprintf("%s:/local/template", g.meta.templatePath), 493 "-u", fmt.Sprintf("%d:%d", os.Getuid(), os.Getgid()), 494 "--rm", 495 "openapitools/openapi-generator-cli:v6.3.0", 496 "generate", 497 "-i", "/local/input/"+filepath.Base(tmpFile.Name()), 498 "-g", g.meta.Lang, 499 "-o", fmt.Sprintf("/local/output/%s/%s", definition.DefinitionKindToType[g.meta.kind], g.meta.name), 500 "-t", "/local/template", 501 "--skip-validate-spec", 502 "--enable-post-process-file", 503 "--generate-alias-as-model", 504 "--inline-schema-name-defaults", "arrayItemSuffix=,mapItemSuffix=", 505 "--additional-properties", fmt.Sprintf("packageName=%s", strings.ReplaceAll(g.meta.name, "-", "_")), 506 "--global-property", "modelDocs=false,models,supportingFiles=utils.go", 507 ) 508 if g.meta.Verbose { 509 klog.Info(cmd.String()) 510 } 511 output, err := cmd.CombinedOutput() 512 if err != nil { 513 return errors.Wrap(err, string(output)) 514 } 515 if g.meta.Verbose { 516 klog.Info(string(output)) 517 } 518 519 // Adjust the generated files and code 520 for _, m := range g.defModifiers { 521 err := m.Modify() 522 if err != nil { 523 return errors.Wrapf(err, "modify fail by %s", m.Name()) 524 } 525 } 526 return nil 527 } 528 529 // completeFreeFormSchema can complete the schema of free form parameter, such as `parameter: {...}` 530 // This is a workaround for openapi-generator, which won't generate the correct code for free form parameter. 531 func completeFreeFormSchema(schema *openapi3.SchemaRef) { 532 v := schema.Value 533 if v.OneOf == nil && v.AnyOf == nil && v.AllOf == nil && v.Properties == nil { 534 if v.Type == openapi3.TypeObject { 535 schema.Value.AdditionalProperties = openapi3.AdditionalProperties{Schema: &openapi3.SchemaRef{ 536 Value: &openapi3.Schema{ 537 Type: openapi3.TypeObject, 538 Nullable: true, 539 }, 540 }} 541 } else if v.Type == "string" { 542 schema.Value.AdditionalProperties = openapi3.AdditionalProperties{Schema: &openapi3.SchemaRef{ 543 Value: &openapi3.Schema{ 544 Type: "string", 545 }, 546 }} 547 } 548 } 549 } 550 551 // fixSchemaWithOneOf do some fix for schema with OneOf. 552 // 1. move properties in the root schema to sub schema in OneOf. See https://github.com/OpenAPITools/openapi-generator/issues/14250 553 // 2. move default value to sub schema in OneOf. 554 // 3. remove duplicated type in OneOf. 555 func fixSchemaWithOneOf(schema *openapi3.SchemaRef) { 556 var schemaNeedFix []*openapi3.Schema 557 558 oneOf := schema.Value.OneOf 559 typeSet := make(map[string]struct{}) 560 duplicateIndex := make([]int, 0) 561 // If the schema have default value, it should be moved to sub-schema with right type. 562 defaultValue := schema.Value.Default 563 schema.Value.Default = nil 564 for _, s := range oneOf { 565 completeSchemas(s.Value.Properties) 566 if defaultValueMatchOneOfItem(s.Value, defaultValue) { 567 s.Value.Default = defaultValue 568 } 569 570 // If the schema is without type or ref. It may need to be fixed. 571 // Cases can be: 572 // 1. A non-ref sub-schema maybe have no properties and the needed properties is in the root schema. 573 // 2. A sub-schema maybe have no type and the needed type is in the root schema. 574 // 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. 575 if s.Value.Properties == nil || s.Value.Type == "" { 576 schemaNeedFix = append(schemaNeedFix, s.Value) 577 } 578 } 579 580 if schemaNeedFix == nil { 581 return // no non-ref schema found 582 } 583 for _, s := range schemaNeedFix { 584 if s.Properties == nil { 585 s.Properties = schema.Value.Properties 586 } 587 if s.Type == "" { 588 s.Type = schema.Value.Type 589 } 590 } 591 schema.Value.Properties = nil 592 593 // remove duplicated type 594 for i, s := range oneOf { 595 if s.Value.Type == "" { 596 continue 597 } 598 if _, ok := typeSet[s.Value.Type]; ok && s.Value.Type != openapi3.TypeObject { 599 duplicateIndex = append(duplicateIndex, i) 600 } else { 601 typeSet[s.Value.Type] = struct{}{} 602 } 603 } 604 if len(duplicateIndex) > 0 { 605 newRefs := make(openapi3.SchemaRefs, 0, len(oneOf)-len(duplicateIndex)) 606 for i, s := range oneOf { 607 if !slices.Contains(duplicateIndex, i) { 608 newRefs = append(newRefs, s) 609 } 610 } 611 schema.Value.OneOf = newRefs 612 } 613 614 } 615 616 func completeSchema(key string, schema *openapi3.SchemaRef) { 617 schema.Value.Title = key 618 if schema.Value.OneOf != nil { 619 fixSchemaWithOneOf(schema) 620 return 621 } 622 623 // allow all the fields to be empty to avoid this case: 624 // A field is initialized with empty value and marshalled to JSON with empty value (e.g. empty string) 625 // However, the empty value is not allowed on the server side when it is conflict with the default value in CUE. 626 // schema.Value.Required = []string{} 627 628 switch schema.Value.Type { 629 case openapi3.TypeObject: 630 completeSchemas(schema.Value.Properties) 631 case openapi3.TypeArray: 632 completeSchema(key, schema.Value.Items) 633 } 634 635 } 636 637 func completeSchemas(schemas openapi3.Schemas) { 638 for k, schema := range schemas { 639 completeSchema(k, schema) 640 } 641 } 642 643 // NewModifiableGenerator returns a new Generator with modifiers 644 func NewModifiableGenerator(meta *GenMeta) *Generator { 645 g := &Generator{ 646 meta: meta, 647 defModifiers: []Modifier{}, 648 moduleModifiers: []Modifier{}, 649 } 650 appendModifiersByLanguage(g, meta) 651 return g 652 } 653 654 func appendModifiersByLanguage(g *Generator, meta *GenMeta) { 655 switch meta.Lang { 656 case "go": 657 g.defModifiers = append(g.defModifiers, &GoDefModifier{GenMeta: meta}) 658 g.moduleModifiers = append(g.moduleModifiers, &GoModuleModifier{GenMeta: meta}) 659 default: 660 panic(fmt.Sprintf("unsupported language: %s", meta.Lang)) 661 } 662 } 663 664 // getValueType returns the cue type of the value 665 func getValueType(i interface{}) CUEType { 666 if i == nil { 667 return "" 668 } 669 switch i.(type) { 670 case string: 671 return "string" 672 case int: 673 return "integer" 674 case float64, float32: 675 return "number" 676 case bool: 677 return "boolean" 678 case map[string]interface{}: 679 return "object" 680 case []interface{}: 681 return "array" 682 default: 683 return "" 684 } 685 } 686 687 // CUEType is the possible types in CUE 688 type CUEType string 689 690 func (t CUEType) fit(schema *openapi3.Schema) bool { 691 openapiType := schema.Type 692 switch t { 693 case "string": 694 return openapiType == "string" 695 case "integer": 696 return openapiType == "integer" || openapiType == "number" 697 case "number": 698 return openapiType == "number" 699 case "boolean": 700 return openapiType == "boolean" 701 case "array": 702 return openapiType == "array" 703 default: 704 return false 705 } 706 } 707 708 // defaultValueMatchOneOfItem checks if the default value matches one of the items in the oneOf schema. 709 func defaultValueMatchOneOfItem(item *openapi3.Schema, defaultValue interface{}) bool { 710 if item.Default != nil { 711 return false 712 } 713 defaultValueType := getValueType(defaultValue) 714 // let's skip the case that default value is object because it's hard to match now. 715 if defaultValueType == "" || defaultValueType == openapi3.TypeObject { 716 return false 717 } 718 if defaultValueType != "" && defaultValueType.fit(item) && item.Default == nil { 719 return true 720 } 721 return false 722 } 723 724 func fnName(fn interface{}) string { 725 return runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name() 726 }