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  }