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 }