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 }