github.com/aacfactory/fns@v1.2.86-0.20240310083819-80d667fc0a17/cmd/generates/modules/fn.go (about) 1 /* 2 * Copyright 2023 Wang Min Xiang 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 18 package modules 19 20 import ( 21 "context" 22 "fmt" 23 "github.com/aacfactory/errors" 24 "github.com/aacfactory/fns/cmd/generates/sources" 25 "go/ast" 26 "reflect" 27 "strconv" 28 "strings" 29 ) 30 31 type FunctionField struct { 32 mod *sources.Module 33 Name string 34 Type *sources.Type 35 } 36 37 func (sf *FunctionField) String() (v string) { 38 v = fmt.Sprintf("%s %s", sf.Name, sf.Type.String()) 39 return 40 } 41 42 func (sf *FunctionField) Paths() (paths []string) { 43 paths = sf.Type.GetTopPaths() 44 return 45 } 46 47 type FunctionError struct { 48 Name string 49 Descriptions map[string]string 50 } 51 52 type Function struct { 53 mod *sources.Module 54 hostServiceName string 55 path string 56 filename string 57 file *ast.File 58 imports sources.Imports 59 decl *ast.FuncDecl 60 Ident string 61 VarIdent string 62 ProxyIdent string 63 ProxyAsyncIdent string 64 HandlerIdent string 65 Annotations sources.Annotations 66 Param *FunctionField 67 Result *FunctionField 68 } 69 70 func (f *Function) HostServiceName() (name string) { 71 name = f.hostServiceName 72 return 73 } 74 75 func (f *Function) Name() (name string) { 76 name, _ = f.Annotations.Value("fn") 77 return 78 } 79 80 func (f *Function) Readonly() (ok bool) { 81 _, ok = f.Annotations.Get("readonly") 82 return 83 } 84 85 func (f *Function) Internal() (ok bool) { 86 _, ok = f.Annotations.Get("internal") 87 return 88 } 89 90 func (f *Function) Title() (title string) { 91 has := false 92 title, has = f.Annotations.Value("title") 93 if !has { 94 title = f.Name() 95 } 96 return 97 } 98 99 func (f *Function) Description() (description string) { 100 description, _ = f.Annotations.Value("description") 101 return 102 } 103 104 func (f *Function) Errors() (errs string) { 105 errs, _ = f.Annotations.Value("errors") 106 return 107 } 108 109 func (f *Function) Validation() (title string, ok bool) { 110 if f.Param == nil { 111 return 112 } 113 anno, has := f.Annotations.Get("validation") 114 if !has { 115 return 116 } 117 if len(anno.Params) == 0 { 118 title = "invalid" 119 ok = true 120 return 121 } 122 title = anno.Params[0] 123 if title == "" { 124 title = "invalid" 125 } 126 ok = true 127 return 128 } 129 130 func (f *Function) Authorization() (ok bool) { 131 _, ok = f.Annotations.Get("authorization") 132 return 133 } 134 135 func (f *Function) Permission() (ok bool) { 136 _, ok = f.Annotations.Get("permission") 137 return 138 } 139 140 func (f *Function) Deprecated() (ok bool) { 141 _, ok = f.Annotations.Get("deprecated") 142 return 143 } 144 145 func (f *Function) Metric() (ok bool) { 146 _, ok = f.Annotations.Get("metric") 147 return 148 } 149 150 func (f *Function) Barrier() (ok bool) { 151 _, ok = f.Annotations.Get("barrier") 152 return 153 } 154 155 func (f *Function) Cache() (cmd string, ttl string, has bool) { 156 anno, exist := f.Annotations.Get("cache") 157 if !exist { 158 return 159 } 160 if len(anno.Params) == 0 { 161 return 162 } 163 cmd = strings.TrimSpace(anno.Params[0]) 164 switch cmd { 165 case "get": 166 has = true 167 break 168 case "set": 169 if len(anno.Params) == 1 { 170 ttl = "10" 171 has = true 172 break 173 } 174 ttl = anno.Params[1] 175 sec, ttlErr := strconv.Atoi(ttl) 176 if ttlErr != nil { 177 ttl = "10" 178 has = true 179 break 180 } 181 if sec < 1 { 182 ttl = "10" 183 has = true 184 break 185 } 186 has = true 187 break 188 case "remove": 189 has = true 190 break 191 case "get-set": 192 if len(anno.Params) == 1 { 193 ttl = "10" 194 has = true 195 break 196 } 197 ttl = anno.Params[1] 198 sec, ttlErr := strconv.Atoi(ttl) 199 if ttlErr != nil { 200 ttl = "10" 201 has = true 202 break 203 } 204 if sec < 1 { 205 ttl = "10" 206 has = true 207 break 208 } 209 has = true 210 break 211 default: 212 break 213 } 214 return 215 } 216 217 func (f *Function) CacheControl() (maxAge int, public bool, mustRevalidate bool, proxyRevalidate bool, has bool, err error) { 218 anno, exist := f.Annotations.Get("cache-control") 219 if !exist { 220 return 221 } 222 has = true 223 if len(anno.Params) == 0 { 224 return 225 } 226 for _, param := range anno.Params { 227 maxAgeValue, hasMaxValue := strings.CutPrefix(param, "max-age=") 228 if hasMaxValue { 229 maxAge, err = strconv.Atoi(strings.TrimSpace(maxAgeValue)) 230 if err != nil { 231 err = errors.Warning("fns: parse @cache-control max-age failed").WithMeta("max-age", maxAgeValue) 232 return 233 } 234 } 235 publicValue, hasPublic := strings.CutPrefix(param, "public=") 236 if hasPublic { 237 public, err = strconv.ParseBool(strings.TrimSpace(publicValue)) 238 if err != nil { 239 err = errors.Warning("fns: parse @cache-control public failed").WithMeta("public", publicValue) 240 return 241 } 242 } 243 mustRevalidateValue, hasMustRevalidate := strings.CutPrefix(param, "must-revalidate=") 244 if hasMustRevalidate { 245 mustRevalidate, err = strconv.ParseBool(strings.TrimSpace(mustRevalidateValue)) 246 if err != nil { 247 err = errors.Warning("fns: parse @cache-control must-revalidate failed").WithMeta("must-revalidate", mustRevalidateValue) 248 return 249 } 250 } 251 proxyRevalidateValue, hasProxyRevalidate := strings.CutPrefix(param, "proxy-revalidate=") 252 if hasProxyRevalidate { 253 proxyRevalidate, err = strconv.ParseBool(strings.TrimSpace(proxyRevalidateValue)) 254 if err != nil { 255 err = errors.Warning("fns: parse @cache-control proxy-revalidate failed").WithMeta("proxy-revalidate", proxyRevalidateValue) 256 return 257 } 258 } 259 } 260 return 261 } 262 263 func (f *Function) Annotation(name string) (params []string, has bool) { 264 anno, exist := f.Annotations.Get(name) 265 if exist { 266 params = anno.Params 267 has = true 268 } 269 return 270 } 271 272 func (f *Function) ForeachAnnotations(fn func(name string, params []string)) { 273 for _, annotation := range f.Annotations { 274 fn(annotation.Name, annotation.Params) 275 } 276 } 277 278 func (f *Function) FieldImports() (v sources.Imports) { 279 v = sources.Imports{} 280 paths := make([]string, 0, 1) 281 if f.Param != nil { 282 paths = append(paths, f.Param.Paths()...) 283 } 284 if f.Result != nil { 285 paths = append(paths, f.Result.Paths()...) 286 } 287 for _, path := range paths { 288 v.Add(&sources.Import{ 289 Path: path, 290 Alias: "", 291 }) 292 } 293 return 294 } 295 296 func (f *Function) Parse(ctx context.Context) (err error) { 297 if ctx.Err() != nil { 298 err = errors.Warning("modules: parse function failed").WithCause(ctx.Err()). 299 WithMeta("service", f.hostServiceName).WithMeta("function", f.Ident).WithMeta("file", f.filename) 300 return 301 } 302 if f.decl.Type.TypeParams != nil && f.decl.Type.TypeParams.List != nil && len(f.decl.Type.TypeParams.List) > 0 { 303 err = errors.Warning("modules: parse function failed").WithCause(errors.Warning("function can not use paradigm")). 304 WithMeta("service", f.hostServiceName).WithMeta("function", f.Ident).WithMeta("file", f.filename) 305 return 306 } 307 308 // params 309 params := f.decl.Type.Params 310 if params == nil || params.List == nil || len(params.List) == 0 || len(params.List) > 2 { 311 err = errors.Warning("modules: parse function failed").WithCause(errors.Warning("params length must be one or two")). 312 WithMeta("service", f.hostServiceName).WithMeta("function", f.Ident).WithMeta("file", f.filename) 313 return 314 } 315 316 if !f.mod.Types().IsContextType(params.List[0].Type, f.imports) { 317 err = errors.Warning("modules: parse function failed").WithCause(errors.Warning("first param must be context.Context")). 318 WithMeta("service", f.hostServiceName).WithMeta("function", f.Ident).WithMeta("file", f.filename) 319 return 320 } 321 if len(params.List) == 2 { 322 param, parseParamErr := f.parseField(ctx, params.List[1]) 323 if parseParamErr != nil { 324 err = errors.Warning("modules: parse function failed").WithCause(parseParamErr). 325 WithMeta("service", f.hostServiceName).WithMeta("function", f.Ident).WithMeta("file", f.filename) 326 return 327 } 328 f.Param = param 329 } 330 // results 331 results := f.decl.Type.Results 332 if results == nil || results.List == nil || len(results.List) == 0 || len(results.List) > 2 { 333 err = errors.Warning("modules: parse function failed").WithCause(errors.Warning("results length must be one or two")). 334 WithMeta("service", f.hostServiceName).WithMeta("function", f.Ident).WithMeta("file", f.filename) 335 return 336 } 337 if len(results.List) == 1 { 338 if !f.mod.Types().IsCodeErrorType(results.List[0].Type, f.imports) { 339 err = errors.Warning("modules: parse function failed").WithCause(errors.Warning("the last results must be error or github.com/aacfactory/errors.CodeError")). 340 WithMeta("service", f.hostServiceName).WithMeta("function", f.Ident).WithMeta("file", f.filename) 341 return 342 } 343 } else { 344 if !f.mod.Types().IsCodeErrorType(results.List[1].Type, f.imports) { 345 err = errors.Warning("modules: parse function failed").WithCause(errors.Warning("the last results must be error or github.com/aacfactory/errors.CodeError")). 346 WithMeta("service", f.hostServiceName).WithMeta("function", f.Ident).WithMeta("file", f.filename) 347 return 348 } 349 result, parseResultErr := f.parseField(ctx, results.List[0]) 350 if parseResultErr != nil { 351 err = errors.Warning("modules: parse function failed").WithCause(parseResultErr). 352 WithMeta("service", f.hostServiceName).WithMeta("function", f.Ident).WithMeta("file", f.filename) 353 return 354 } 355 f.Result = result 356 } 357 return 358 } 359 360 func (f *Function) parseField(ctx context.Context, field *ast.Field) (v *FunctionField, err error) { 361 if len(field.Names) != 1 { 362 err = errors.Warning("modules: field must has only one name") 363 return 364 } 365 name := field.Names[0].Name 366 typ, parseTypeErr := f.parseFieldType(ctx, field.Type) 367 if parseTypeErr != nil { 368 err = errors.Warning("modules: parse field failed").WithMeta("field", name).WithCause(parseTypeErr) 369 return 370 } 371 v = &FunctionField{ 372 mod: f.mod, 373 Name: name, 374 Type: typ, 375 } 376 return 377 } 378 379 func (f *Function) parseFieldType(ctx context.Context, e ast.Expr) (typ *sources.Type, err error) { 380 switch e.(type) { 381 case *ast.Ident: 382 typ, err = f.mod.Types().ParseExpr(ctx, e, &sources.TypeScope{ 383 Path: f.path, 384 Mod: f.mod, 385 Imports: f.imports, 386 GenericDoc: "", 387 }) 388 if err != nil { 389 return 390 } 391 _, isBasic := typ.Basic() 392 if isBasic { 393 err = errors.Warning("modules: field type only support value object") 394 return 395 } 396 typ.Path = f.path 397 typ.Name = e.(*ast.Ident).Name 398 case *ast.SelectorExpr: 399 typ, err = f.mod.Types().ParseExpr(ctx, e, &sources.TypeScope{ 400 Path: f.path, 401 Mod: f.mod, 402 Imports: f.imports, 403 GenericDoc: "", 404 }) 405 if err != nil { 406 return 407 } 408 _, isBasic := typ.Basic() 409 if isBasic { 410 err = errors.Warning("modules: field type only support value object") 411 return 412 } 413 break 414 default: 415 err = errors.Warning("modules: field type only support no paradigms value object or typed slice").WithMeta("expr", reflect.TypeOf(e).String()) 416 return 417 } 418 return 419 } 420 421 func (f *Function) Handle(ctx context.Context) (result interface{}, err error) { 422 err = f.Parse(ctx) 423 if err != nil { 424 return 425 } 426 result = fmt.Sprintf("%s/%s: parse succeed", f.HostServiceName(), f.Name()) 427 return 428 } 429 430 type Functions []*Function 431 432 func (fns Functions) Len() int { 433 return len(fns) 434 } 435 436 func (fns Functions) Less(i, j int) bool { 437 return fns[i].Ident < fns[j].Ident 438 } 439 440 func (fns Functions) Swap(i, j int) { 441 fns[i], fns[j] = fns[j], fns[i] 442 return 443 }