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 }