github.com/wfusion/gofusion@v1.1.14/context/context.go (about)

     1  package context
     2  
     3  import (
     4  	"context"
     5  	"encoding/gob"
     6  	"time"
     7  
     8  	"github.com/gin-gonic/gin"
     9  	"github.com/wfusion/gofusion/common/infra/watermill"
    10  
    11  	"github.com/wfusion/gofusion/common/infra/watermill/message"
    12  	"github.com/wfusion/gofusion/common/utils"
    13  	"github.com/wfusion/gofusion/common/utils/serialize/json"
    14  )
    15  
    16  // _context is a gofusion union context struct
    17  //nolint: revive // struct tag too long issue
    18  type _context struct {
    19  	Langs            []string `json:"langs" yaml:"langs" toml:"langs" mapstructure:"langs"`
    20  	UserID           *string  `json:"user_id" yaml:"user_id" toml:"user_id" mapstructure:"user_id"`
    21  	TraceID          *string  `json:"trace_id" yaml:"trace_id" toml:"trace_id" mapstructure:"trace_id"`
    22  	CronTaskID       *string  `json:"cron_task_id" yaml:"cron_task_id" toml:"cron_task_id" mapstructure:"cron_task_id"`
    23  	CronTaskName     *string  `json:"cron_task_name" yaml:"cron_task_name" toml:"cron_task_name" mapstructure:"cron_task_name"`
    24  	Deadline         *string  `json:"deadline" yaml:"deadline" toml:"deadline" mapstructure:"deadline"`
    25  	DeadlineLocation *string  `json:"deadline_location" yaml:"deadline_location" toml:"deadline_location" mapstructure:"deadline_location"`
    26  }
    27  
    28  func (c *_context) unmarshal() (ctx context.Context) {
    29  	ctx = context.Background()
    30  	if c == nil {
    31  		return
    32  	}
    33  
    34  	if c.Langs != nil {
    35  		ctx = SetLangs(ctx, c.Langs)
    36  	}
    37  	if c.UserID != nil {
    38  		ctx = SetUserID(ctx, *c.UserID)
    39  	}
    40  	if c.TraceID != nil {
    41  		ctx = SetTraceID(ctx, *c.TraceID)
    42  	}
    43  	if c.CronTaskID != nil {
    44  		ctx = SetCronTaskID(ctx, *c.CronTaskID)
    45  	}
    46  	if c.CronTaskName != nil {
    47  		ctx = SetCronTaskName(ctx, *c.CronTaskName)
    48  	}
    49  	if c.Deadline != nil {
    50  		location := utils.Must(time.LoadLocation(*c.DeadlineLocation))
    51  		// FIXME: it may result context leak issue
    52  		ctx, _ = context.WithDeadline(ctx, utils.Must(time.ParseInLocation(time.RFC3339Nano, *c.Deadline, location)))
    53  	}
    54  
    55  	return
    56  }
    57  
    58  func (c *_context) Marshal() (b []byte) {
    59  	bs, cb := utils.BytesBufferPool.Get(nil)
    60  	defer cb()
    61  	utils.MustSuccess(gob.NewEncoder(bs).Encode(c))
    62  
    63  	b = make([]byte, bs.Len())
    64  	copy(b, bs.Bytes())
    65  	return
    66  }
    67  
    68  func Flatten(ctx context.Context) (c *_context) {
    69  	c = new(_context)
    70  	if langs := GetLangs(ctx); langs != nil {
    71  		c.Langs = langs
    72  	}
    73  	if userID := GetUserID(ctx); utils.IsStrNotBlank(userID) {
    74  		c.UserID = utils.AnyPtr(userID)
    75  	}
    76  	if traceID := GetTraceID(ctx); utils.IsStrNotBlank(traceID) {
    77  		c.TraceID = utils.AnyPtr(traceID)
    78  	}
    79  	if taskID := GetCronTaskID(ctx); utils.IsStrNotBlank(taskID) {
    80  		c.CronTaskID = utils.AnyPtr(taskID)
    81  	}
    82  	if taskName := GetCronTaskName(ctx); utils.IsStrNotBlank(taskName) {
    83  		c.CronTaskName = utils.AnyPtr(taskName)
    84  	}
    85  	if deadline, ok := ctx.Deadline(); ok {
    86  		c.Deadline = utils.AnyPtr(deadline.Format(time.RFC3339Nano))
    87  		c.DeadlineLocation = utils.AnyPtr(deadline.Location().String())
    88  	}
    89  	return
    90  }
    91  
    92  func WatermillMetadata(ctx context.Context) (metadata message.Metadata) {
    93  	metadata = make(message.Metadata)
    94  	if langs := GetLangs(ctx); langs != nil {
    95  		marshaled, _ := json.Marshal(langs)
    96  		metadata["langs"] = string(marshaled)
    97  	}
    98  	if userID := GetUserID(ctx); utils.IsStrNotBlank(userID) {
    99  		metadata["user_id"] = userID
   100  	}
   101  	if traceID := GetTraceID(ctx); utils.IsStrNotBlank(traceID) {
   102  		metadata["trace_id"] = traceID
   103  	}
   104  	if taskID := GetCronTaskID(ctx); utils.IsStrNotBlank(taskID) {
   105  		metadata["cron_task_id"] = taskID
   106  	}
   107  	if taskName := GetCronTaskName(ctx); utils.IsStrNotBlank(taskName) {
   108  		metadata["cron_task_name"] = taskName
   109  	}
   110  	if deadline, ok := ctx.Deadline(); ok {
   111  		metadata["deadline"] = deadline.Format(time.RFC3339Nano)
   112  		metadata["deadline_location"] = deadline.Location().String()
   113  	}
   114  	return
   115  }
   116  
   117  type newOption struct {
   118  	g *gin.Context
   119  	c *_context
   120  	m message.Metadata
   121  }
   122  
   123  func (o *newOption) ginUnmarshal() (ctx context.Context) {
   124  	ctx = context.Background()
   125  	if userID := o.g.GetString(KeyUserID); utils.IsStrNotBlank(userID) {
   126  		ctx = SetUserID(ctx, userID)
   127  	}
   128  	if traceID := o.g.GetString(KeyTraceID); utils.IsStrNotBlank(traceID) {
   129  		ctx = SetTraceID(ctx, traceID)
   130  	}
   131  	langs := o.g.Request.Header.Values("Accept-Language")
   132  	if lang := o.g.GetString("lang"); utils.IsStrNotBlank(lang) {
   133  		langs = append(langs, lang)
   134  	}
   135  	if lang := o.g.GetString(KeyLangs); utils.IsStrNotBlank(lang) {
   136  		langs = append(langs, lang)
   137  	}
   138  	if len(langs) > 0 {
   139  		ctx = SetLangs(ctx, langs)
   140  	}
   141  	return
   142  }
   143  
   144  func (o *newOption) messageUnmarshal() (ctx context.Context) {
   145  	ctx = context.Background()
   146  	mapGetFn := func(k string) string { return o.m[k] }
   147  	if userID := utils.LookupByFuzzyKeyword[string](mapGetFn, "user_id"); utils.IsStrNotBlank(userID) {
   148  		ctx = SetUserID(ctx, userID)
   149  	}
   150  	if traceID := utils.LookupByFuzzyKeyword[string](mapGetFn, "trace_id"); utils.IsStrNotBlank(traceID) {
   151  		ctx = SetTraceID(ctx, traceID)
   152  	}
   153  	if langstr := utils.LookupByFuzzyKeyword[string](mapGetFn, "langs"); utils.IsStrNotBlank(langstr) {
   154  		var langs []string
   155  		_ = json.Unmarshal([]byte(langstr), &langs)
   156  		ctx = SetLangs(ctx, langs)
   157  	}
   158  	if taskID := utils.LookupByFuzzyKeyword[string](mapGetFn, "cron_task_id"); utils.IsStrNotBlank(taskID) {
   159  		ctx = SetCronTaskID(ctx, taskID)
   160  	}
   161  	if name := utils.LookupByFuzzyKeyword[string](mapGetFn, "cron_task_name"); utils.IsStrNotBlank(name) {
   162  		ctx = SetCronTaskName(ctx, name)
   163  	}
   164  	if messageUUID := o.m[watermill.ContextKeyMessageUUID]; utils.IsStrNotBlank(messageUUID) {
   165  		ctx = utils.SetCtxAny(ctx, watermill.ContextKeyMessageUUID, messageUUID)
   166  	}
   167  	if messageRawID := o.m[watermill.ContextKeyRawMessageID]; utils.IsStrNotBlank(messageRawID) {
   168  		ctx = utils.SetCtxAny(ctx, watermill.ContextKeyRawMessageID, messageRawID)
   169  	}
   170  
   171  	deadline := utils.LookupByFuzzyKeyword[string](mapGetFn, "deadline")
   172  	deadlineLoc := utils.LookupByFuzzyKeyword[string](mapGetFn, "deadline_location")
   173  	if utils.IsStrNotBlank(deadline) {
   174  		location := utils.Must(time.LoadLocation(deadlineLoc))
   175  		// FIXME: it may result context leak issue
   176  		ctx, _ = context.WithDeadline(ctx, utils.Must(time.ParseInLocation(time.RFC3339Nano, deadline, location)))
   177  	}
   178  	return
   179  }
   180  
   181  func Context(c []byte) utils.OptionFunc[newOption] {
   182  	return func(o *newOption) {
   183  		o.c = new(_context)
   184  		utils.MustSuccess(utils.Unmarshal(c, o.c, ""))
   185  	}
   186  }
   187  
   188  func Gin(c *gin.Context) utils.OptionFunc[newOption] {
   189  	return func(o *newOption) {
   190  		o.g = c
   191  	}
   192  }
   193  
   194  func Watermill(m message.Metadata) utils.OptionFunc[newOption] {
   195  	return func(o *newOption) {
   196  		o.m = m
   197  	}
   198  }
   199  
   200  func New(opts ...utils.OptionExtender) (ctx context.Context) {
   201  	o := utils.ApplyOptions[newOption](opts...)
   202  
   203  	// alternative
   204  	switch {
   205  	case o.g != nil:
   206  		return o.ginUnmarshal()
   207  	case o.c != nil:
   208  		return o.c.unmarshal()
   209  	case o.m != nil:
   210  		return o.messageUnmarshal()
   211  	default:
   212  		panic(ErrUnknownInstantiationMethod)
   213  	}
   214  
   215  	return
   216  }