github.com/aacfactory/fns@v1.2.86-0.20240310083819-80d667fc0a17/services/commons/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 commons
    19  
    20  import (
    21  	"fmt"
    22  	"github.com/aacfactory/errors"
    23  	"github.com/aacfactory/fns/context"
    24  	"github.com/aacfactory/fns/logs"
    25  	"github.com/aacfactory/fns/runtime"
    26  	"github.com/aacfactory/fns/services"
    27  	"github.com/aacfactory/fns/services/authorizations"
    28  	"github.com/aacfactory/fns/services/caches"
    29  	"github.com/aacfactory/fns/services/metrics"
    30  	"github.com/aacfactory/fns/services/permissions"
    31  	"github.com/aacfactory/fns/services/validators"
    32  	"github.com/aacfactory/fns/transports/middlewares/cachecontrol"
    33  	"reflect"
    34  	"strconv"
    35  	"time"
    36  )
    37  
    38  var (
    39  	emptyType = reflect.TypeOf(new(services.Empty))
    40  )
    41  
    42  type FnHandler[P any, R any] func(ctx context.Context, param P) (v R, err error)
    43  
    44  type FnOptions struct {
    45  	readonly        bool
    46  	internal        bool
    47  	deprecated      bool
    48  	validation      bool
    49  	validationTitle string
    50  	authorization   bool
    51  	permission      bool
    52  	cacheCommand    string
    53  	cacheTTL        time.Duration
    54  	cacheControl    []cachecontrol.MakeOption
    55  	metric          bool
    56  	barrier         bool
    57  }
    58  
    59  type FnOption func(opt *FnOptions) (err error)
    60  
    61  func Readonly() FnOption {
    62  	return func(opt *FnOptions) (err error) {
    63  		opt.readonly = true
    64  		return
    65  	}
    66  }
    67  
    68  func Internal() FnOption {
    69  	return func(opt *FnOptions) (err error) {
    70  		opt.internal = true
    71  		return
    72  	}
    73  }
    74  
    75  func Deprecated() FnOption {
    76  	return func(opt *FnOptions) (err error) {
    77  		opt.deprecated = true
    78  		return
    79  	}
    80  }
    81  
    82  func Validation(title string) FnOption {
    83  	return func(opt *FnOptions) (err error) {
    84  		opt.validation = true
    85  		if title == "" {
    86  			title = "invalid"
    87  		}
    88  		opt.validationTitle = title
    89  		return
    90  	}
    91  }
    92  
    93  func Authorization() FnOption {
    94  	return func(opt *FnOptions) (err error) {
    95  		opt.authorization = true
    96  		return
    97  	}
    98  }
    99  
   100  func Permission() FnOption {
   101  	return func(opt *FnOptions) (err error) {
   102  		opt.permission = true
   103  		return
   104  	}
   105  }
   106  
   107  func Metric() FnOption {
   108  	return func(opt *FnOptions) (err error) {
   109  		opt.metric = true
   110  		return
   111  	}
   112  }
   113  
   114  func Barrier() FnOption {
   115  	return func(opt *FnOptions) (err error) {
   116  		opt.barrier = true
   117  		return
   118  	}
   119  }
   120  
   121  const (
   122  	GetCacheMod    = "get"
   123  	GetSetCacheMod = "get-set"
   124  	SetCacheMod    = "set"
   125  	RemoveCacheMod = "remove"
   126  )
   127  
   128  func Cache(mod string, param string) FnOption {
   129  	return func(opt *FnOptions) (err error) {
   130  		switch mod {
   131  		case GetCacheMod:
   132  			opt.cacheCommand = GetCacheMod
   133  			break
   134  		case GetSetCacheMod:
   135  			if param == "" {
   136  				param = "60"
   137  			}
   138  			sec, secErr := strconv.ParseInt(param, 10, 64)
   139  			if secErr != nil {
   140  				err = errors.Warning("invalid cache param")
   141  				break
   142  			}
   143  			if sec < 1 {
   144  				sec = 60
   145  			}
   146  			opt.cacheCommand = GetSetCacheMod
   147  			opt.cacheTTL = time.Duration(sec) * time.Second
   148  			break
   149  		case SetCacheMod:
   150  			if param == "" {
   151  				param = "60"
   152  			}
   153  			sec, secErr := strconv.ParseInt(param, 10, 64)
   154  			if secErr != nil {
   155  				err = errors.Warning("invalid cache param")
   156  				break
   157  			}
   158  			if sec < 1 {
   159  				sec = 60
   160  			}
   161  			opt.cacheCommand = SetCacheMod
   162  			opt.cacheTTL = time.Duration(sec) * time.Second
   163  			break
   164  		case RemoveCacheMod:
   165  			opt.cacheCommand = RemoveCacheMod
   166  			break
   167  		default:
   168  			err = errors.Warning("invalid cache mod")
   169  			break
   170  		}
   171  		return
   172  	}
   173  }
   174  
   175  func CacheControl(maxAge int, public bool, mustRevalidate bool, proxyRevalidate bool) FnOption {
   176  	return func(opt *FnOptions) (err error) {
   177  		if maxAge > 0 {
   178  			opt.cacheControl = append(opt.cacheControl, cachecontrol.MaxAge(maxAge))
   179  			if public {
   180  				opt.cacheControl = append(opt.cacheControl, cachecontrol.Public())
   181  			}
   182  			if mustRevalidate {
   183  				opt.cacheControl = append(opt.cacheControl, cachecontrol.MustRevalidate())
   184  			}
   185  			if proxyRevalidate {
   186  				opt.cacheControl = append(opt.cacheControl, cachecontrol.ProxyRevalidate())
   187  			}
   188  		}
   189  		return
   190  	}
   191  }
   192  
   193  func NewFn[P any, R any](name string, handler FnHandler[P, R], options ...FnOption) services.Fn {
   194  	opt := FnOptions{}
   195  	for _, option := range options {
   196  		if optErr := option(&opt); optErr != nil {
   197  			panic(fmt.Sprintf("%+v", errors.Warning("new fn failed").WithMeta("fn", name).WithCause(optErr)))
   198  			return nil
   199  		}
   200  	}
   201  	return &Fn[P, R]{
   202  		name:                    name,
   203  		internal:                opt.internal,
   204  		readonly:                opt.readonly,
   205  		deprecated:              opt.deprecated,
   206  		validation:              opt.validation,
   207  		validationTitle:         opt.validationTitle,
   208  		authorization:           opt.authorization,
   209  		permission:              opt.permission,
   210  		metric:                  opt.metric,
   211  		barrier:                 opt.barrier,
   212  		cacheCommand:            opt.cacheCommand,
   213  		cacheTTL:                opt.cacheTTL,
   214  		cacheControl:            len(opt.cacheControl) > 0,
   215  		cacheControlMakeOptions: opt.cacheControl,
   216  		handler:                 handler,
   217  		hasParam:                reflect.TypeOf(new(P)) != emptyType,
   218  		hasResult:               reflect.TypeOf(new(R)) != emptyType,
   219  	}
   220  }
   221  
   222  // Fn
   223  // builtin fn handler wrapper
   224  // supported annotations
   225  // @fn {name}
   226  // @readonly
   227  // @authorization
   228  // @permission
   229  // @validation
   230  // @cache {get} {set} {get-set} {remove} {ttl}
   231  // @cache-control {max-age=sec} {public=true} {must-revalidate} {proxy-revalidate}
   232  // @barrier
   233  // @metric
   234  // @title {title}
   235  // @description >>>
   236  // {description}
   237  // <<<
   238  // @errors >>>
   239  // {error_name}
   240  // zh: {zh_message}
   241  // en: {en_message}
   242  // <<<
   243  type Fn[P any, R any] struct {
   244  	name                    string
   245  	internal                bool
   246  	readonly                bool
   247  	deprecated              bool
   248  	authorization           bool
   249  	permission              bool
   250  	validation              bool
   251  	validationTitle         string
   252  	metric                  bool
   253  	barrier                 bool
   254  	cacheCommand            string
   255  	cacheTTL                time.Duration
   256  	cacheControl            bool
   257  	cacheControlMakeOptions []cachecontrol.MakeOption
   258  	handler                 FnHandler[P, R]
   259  	hasParam                bool
   260  	hasResult               bool
   261  }
   262  
   263  func (fn *Fn[P, R]) Name() string {
   264  	return fn.name
   265  }
   266  
   267  func (fn *Fn[P, R]) Internal() bool {
   268  	return fn.internal
   269  }
   270  
   271  func (fn *Fn[P, R]) Readonly() bool {
   272  	return fn.readonly
   273  }
   274  
   275  func (fn *Fn[P, R]) Handle(r services.Request) (v interface{}, err error) {
   276  	if fn.internal && !r.Header().Internal() {
   277  		err = errors.NotAcceptable("fns: fn cannot be accessed externally")
   278  		return
   279  	}
   280  	if fn.metric {
   281  		metrics.Begin(r)
   282  	}
   283  	if fn.barrier {
   284  		var key []byte
   285  		if fn.authorization {
   286  			key, err = services.HashRequest(r, services.HashRequestWithToken())
   287  		} else {
   288  			key, err = services.HashRequest(r)
   289  		}
   290  		if err != nil {
   291  			return
   292  		}
   293  		resp, doErr := runtime.Barrier(r, key, func() (result interface{}, err error) {
   294  			result, err = fn.handle(r)
   295  			return
   296  		})
   297  		if doErr == nil && fn.hasResult {
   298  			v, err = services.ValueOfResponse[R](resp)
   299  		} else {
   300  			err = doErr
   301  		}
   302  	} else {
   303  		v, err = fn.handle(r)
   304  	}
   305  	if fn.metric {
   306  		if err != nil {
   307  			metrics.EndWithCause(r, err)
   308  		} else {
   309  			metrics.End(r)
   310  		}
   311  	}
   312  	if !r.Header().Internal() {
   313  		// cache control
   314  		if fn.cacheControl {
   315  			cachecontrol.Make(r, fn.cacheControlMakeOptions...)
   316  		}
   317  		// deprecated
   318  		if fn.deprecated {
   319  			services.MarkDeprecated(r)
   320  		}
   321  	}
   322  	return
   323  }
   324  
   325  func (fn *Fn[P, R]) handle(r services.Request) (v R, err error) {
   326  	log := logs.Load(r)
   327  	var param P
   328  	paramScanned := false
   329  	// validation
   330  	if fn.hasParam {
   331  		if param, err = fn.param(r); err != nil {
   332  			return
   333  		}
   334  		paramScanned = true
   335  		if fn.validation {
   336  			if err = validators.ValidateWithErrorTitle(param, fn.validationTitle); err != nil {
   337  				return
   338  			}
   339  			if log.DebugEnabled() {
   340  				log.Debug().With("validation", true).Message("fns: fn param is valid")
   341  			}
   342  		}
   343  	}
   344  	// authorization
   345  	if fn.authorization {
   346  		err = authorizations.ValidateContext(r)
   347  		if err != nil {
   348  			return
   349  		}
   350  		if log.DebugEnabled() {
   351  			log.Debug().With("authorization", true).Message("fns: fn authorization is valid")
   352  		}
   353  	}
   354  	// permission
   355  	if fn.permission {
   356  		if err = permissions.EnforceContext(r); err != nil {
   357  			return
   358  		}
   359  		if log.DebugEnabled() {
   360  			log.Debug().With("permission", true).Message("fns: fn permission is valid")
   361  		}
   362  	}
   363  
   364  	// cache get or get-set
   365  	if fn.hasParam && (fn.cacheCommand == GetCacheMod || fn.cacheCommand == GetSetCacheMod) {
   366  		if !paramScanned {
   367  			if param, err = fn.param(r); err != nil {
   368  				return
   369  			}
   370  		}
   371  		result, cached, cacheErr := caches.Load[R](r, param)
   372  		if cacheErr != nil {
   373  			if log.WarnEnabled() {
   374  				log.Warn().Cause(cacheErr).With("fns", "caches").Message("fns: get cache failed")
   375  			}
   376  		}
   377  		if log.DebugEnabled() {
   378  			log.Debug().With("cache-hit", cached).Message("fns: get fn result from cache")
   379  		}
   380  		if cached {
   381  			v = result
   382  			return
   383  		}
   384  	}
   385  	// handle
   386  	v, err = fn.handler(r, param)
   387  	// cache set or remove
   388  	if fn.hasParam && fn.cacheCommand != "" {
   389  		switch fn.cacheCommand {
   390  		case SetCacheMod, GetSetCacheMod:
   391  			if fn.hasResult {
   392  				if cacheErr := caches.Set(r, param, v, fn.cacheTTL); cacheErr != nil {
   393  					if log.WarnEnabled() {
   394  						log.Warn().Cause(cacheErr).With("fns", "caches").Message("fns: set cache failed")
   395  					}
   396  				} else {
   397  					if log.DebugEnabled() {
   398  						log.Debug().With("cache-set", true).Message("fns: set fn result into cache succeed")
   399  					}
   400  				}
   401  			}
   402  			break
   403  		case RemoveCacheMod:
   404  			if cacheErr := caches.Remove(r, param); cacheErr != nil {
   405  				if log.WarnEnabled() {
   406  					log.Warn().Cause(cacheErr).With("fns", "caches").Message("fns: set cache failed")
   407  				} else {
   408  					if log.DebugEnabled() {
   409  						log.Debug().With("cache-remove", true).Message("fns: remove fn result from cache succeed")
   410  					}
   411  				}
   412  			}
   413  			break
   414  		default:
   415  			break
   416  		}
   417  	}
   418  	return
   419  }
   420  
   421  func (fn *Fn[P, R]) param(r services.Request) (param P, err error) {
   422  	param, err = services.ValueOfParam[P](r.Param())
   423  	if err != nil {
   424  		err = errors.BadRequest("scan params failed").WithCause(err)
   425  		return
   426  	}
   427  	return
   428  }