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  }