github.com/oam-dev/kubevela@v1.9.11/references/cuegen/generators/provider/provider.go (about)

     1  /*
     2  Copyright 2023 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 provider
    18  
    19  import (
    20  	"fmt"
    21  	goast "go/ast"
    22  	"io"
    23  	"strings"
    24  
    25  	cueast "cuelang.org/go/cue/ast"
    26  	cuetoken "cuelang.org/go/cue/token"
    27  	"golang.org/x/tools/go/packages"
    28  
    29  	"github.com/oam-dev/kubevela/references/cuegen"
    30  )
    31  
    32  const (
    33  	typeProviderFnMap          = "map[string]github.com/kubevela/pkg/cue/cuex/runtime.ProviderFn"
    34  	typeProvidersParamsPrefix  = "github.com/kubevela/pkg/cue/cuex/providers.Params"
    35  	typeProvidersReturnsPrefix = "github.com/kubevela/pkg/cue/cuex/providers.Returns"
    36  )
    37  
    38  const (
    39  	doKey       = "do"
    40  	providerKey = "provider"
    41  )
    42  
    43  type provider struct {
    44  	name    string
    45  	params  string
    46  	returns string
    47  	do      string
    48  }
    49  
    50  // Options is options of generation
    51  type Options struct {
    52  	File     string                 // Go file path
    53  	Writer   io.Writer              // target writer
    54  	Types    map[string]cuegen.Type // option cuegen.WithTypes
    55  	Nullable bool                   // option cuegen.WithNullable
    56  }
    57  
    58  // Generate generates cue provider from Go struct
    59  func Generate(opts Options) (rerr error) {
    60  	g, err := cuegen.NewGenerator(opts.File)
    61  	if err != nil {
    62  		return err
    63  	}
    64  
    65  	// make options
    66  	genOpts := make([]cuegen.Option, 0)
    67  	// any types
    68  	genOpts = append(genOpts, cuegen.WithTypes(opts.Types))
    69  	// nullable
    70  	if opts.Nullable {
    71  		genOpts = append(genOpts, cuegen.WithNullable())
    72  	}
    73  	// type filter
    74  	genOpts = append(genOpts, cuegen.WithTypeFilter(func(spec *goast.TypeSpec) bool {
    75  		typ := g.Package().TypesInfo.TypeOf(spec.Type)
    76  		// only process provider params and returns.
    77  		if strings.HasPrefix(typ.String(), typeProvidersParamsPrefix) ||
    78  			strings.HasPrefix(typ.String(), typeProvidersReturnsPrefix) {
    79  			return true
    80  		}
    81  
    82  		return false
    83  	}))
    84  
    85  	decls, err := g.Generate(genOpts...)
    86  	if err != nil {
    87  		return err
    88  	}
    89  
    90  	providers, err := extractProviders(g.Package())
    91  	if err != nil {
    92  		return err
    93  	}
    94  	newDecls, err := modifyDecls(g.Package().Name, decls, providers)
    95  	if err != nil {
    96  		return err
    97  	}
    98  
    99  	return g.Format(opts.Writer, newDecls)
   100  }
   101  
   102  // extractProviders extracts the providers from map[string]cuexruntime.ProviderFn
   103  func extractProviders(pkg *packages.Package) (providers []provider, rerr error) {
   104  	var (
   105  		providersMap *goast.CompositeLit
   106  		ok           bool
   107  	)
   108  	// extract provider def map
   109  	for k, v := range pkg.TypesInfo.Types {
   110  		if v.Type.String() != typeProviderFnMap {
   111  			continue
   112  		}
   113  
   114  		if providersMap, ok = k.(*goast.CompositeLit); ok {
   115  			break
   116  		}
   117  	}
   118  
   119  	if providersMap == nil {
   120  		return nil, fmt.Errorf("no provider function map found like '%s'", typeProviderFnMap)
   121  	}
   122  
   123  	defer recoverAssert(&rerr, "extract providers")
   124  
   125  	for _, e := range providersMap.Elts {
   126  		pair := e.(*goast.KeyValueExpr)
   127  		doName := pair.Key.(*goast.BasicLit)
   128  		value := pair.Value.(*goast.CallExpr)
   129  
   130  		indices := value.Fun.(*goast.IndexListExpr)
   131  		params := indices.Indices[0].(*goast.Ident)  // params struct name
   132  		returns := indices.Indices[1].(*goast.Ident) // returns struct name
   133  
   134  		do := value.Args[0].(*goast.Ident)
   135  
   136  		providers = append(providers, provider{
   137  			name:    doName.Value,
   138  			params:  params.Name,
   139  			returns: returns.Name,
   140  			do:      do.Name,
   141  		})
   142  	}
   143  
   144  	return providers, nil
   145  }
   146  
   147  // modifyDecls re-generates cue ast decls of providers.
   148  func modifyDecls(provider string, old []cuegen.Decl, providers []provider) (decls []cuegen.Decl, rerr error) {
   149  	defer recoverAssert(&rerr, "modify decls failed")
   150  
   151  	// map[StructName]StructLit
   152  	mapping := make(map[string]cueast.Expr)
   153  	for _, decl := range old {
   154  		if t, ok := decl.(*cuegen.Struct); ok {
   155  			mapping[t.Name] = t.Expr
   156  		}
   157  	}
   158  
   159  	providerField := &cueast.Field{
   160  		Label: cuegen.Ident(providerKey, true),
   161  		Value: cueast.NewString(provider),
   162  	}
   163  
   164  	for _, p := range providers {
   165  		params := mapping[p.params].(*cueast.StructLit).Elts
   166  		returns := mapping[p.returns].(*cueast.StructLit).Elts
   167  
   168  		doField := &cueast.Field{
   169  			Label: cuegen.Ident(doKey, true),
   170  			Value: cueast.NewLit(cuetoken.STRING, p.name), // p.name has contained double quotes
   171  		}
   172  
   173  		pdecls := []cueast.Decl{doField, providerField}
   174  		pdecls = append(pdecls, params...)
   175  		pdecls = append(pdecls, returns...)
   176  
   177  		decls = append(decls, &cuegen.Struct{CommonFields: cuegen.CommonFields{
   178  			Expr: &cueast.StructLit{
   179  				Elts: pdecls,
   180  			},
   181  			Name: "#" + p.do,
   182  			Pos:  cuetoken.NewSection.Pos(),
   183  		}})
   184  	}
   185  
   186  	return decls, nil
   187  }
   188  
   189  // recoverAssert captures panic caused by invalid type assertion or out of range index,
   190  // so we don't need to check each type assertion and index
   191  func recoverAssert(err *error, msg string) {
   192  	if r := recover(); r != nil {
   193  		*err = fmt.Errorf("%s: panic: %v", r, msg)
   194  	}
   195  }