github.com/gramework/gramework@v1.8.1-0.20231027140105-82555c9057f5/context.go (about)

     1  // Copyright 2017-present Kirill Danshin and Gramework contributors
     2  // Copyright 2019-present Highload LTD (UK CN: 11893420)
     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  
    11  package gramework
    12  
    13  import (
    14  	"bytes"
    15  	"context"
    16  	"encoding/xml"
    17  	"fmt"
    18  
    19  	"github.com/microcosm-cc/bluemonday"
    20  	"github.com/pquerna/ffjson/ffjson"
    21  
    22  	"github.com/gocarina/gocsv"
    23  )
    24  
    25  // @TODO: add more
    26  var ctypes = []string{
    27  	jsonCT,
    28  	xmlCT,
    29  	csvCT,
    30  }
    31  
    32  // ContextFromValue returns gramework.Context from context.Context value from gramework.ContextKey
    33  // in a more effective way, than standard eface.(*SomeType).
    34  // WARNING: this function may return nil, if ctx has no gramework.Context stored or ctx is nil.
    35  // This function will give a warning if you call it with nil context.Context.
    36  func ContextFromValue(ctx context.Context) *Context {
    37  	if ctx == nil {
    38  		internalLog.Warn("ContextFromValue was called with nil context.Context, returning nil")
    39  		return nil
    40  	}
    41  
    42  	ctxIface := ctx.Value(ContextKey)
    43  	gctx, ok := ctxIface.(*Context)
    44  	if !ok {
    45  		return nil
    46  	}
    47  
    48  	return gctx
    49  }
    50  
    51  // MWKill kills current context and stop any user-defined processing.
    52  // This function intented for use in middlewares.
    53  func (ctx *Context) MWKill() {
    54  	ctx.middlewareKilledReq = true
    55  }
    56  
    57  // Sanitize returns a sanitized `s`.
    58  // It use bluemonday to sanitize given parameter.
    59  //
    60  // To change sanitizer policy, see (*App).SetSanitizerPolicy.
    61  func (ctx *Context) Sanitize(s string) string {
    62  	return ctx.App.sanitizerPolicy.Sanitize(s)
    63  }
    64  
    65  // Sanitizer returns current bluemonday policy.
    66  //
    67  // To change sanitizer policy, see (*App).SetSanitizerPolicy.
    68  //
    69  // Context must not update the policy at runtime. Instead, please
    70  // use a new policy.
    71  func (ctx *Context) Sanitizer() *bluemonday.Policy {
    72  	return ctx.App.sanitizerPolicy
    73  }
    74  
    75  // SubPrefixes returns list of router's prefixes that was created using .Sub() feature
    76  func (ctx *Context) SubPrefixes() []string {
    77  	return ctx.subPrefixes
    78  }
    79  
    80  // ContentType returns Content-Type header for current request
    81  func (ctx *Context) ContentType() string {
    82  	return string(ctx.Request.Header.Peek(contentType))
    83  }
    84  
    85  // ToContext returns context.Context with gramework.Context stored
    86  // in context values as a pointer (see gramework.ContextKey to receive and use this value).
    87  //
    88  // By default this func will extend context.Background(), if parentCtx is not provided.
    89  func (ctx *Context) ToContext(parentCtx ...context.Context) context.Context {
    90  	if len(parentCtx) > 0 {
    91  		return context.WithValue(parentCtx[0], ContextKey, ctx)
    92  	}
    93  
    94  	return context.WithValue(context.Background(), ContextKey, ctx)
    95  }
    96  
    97  // RouteArg returns an argument value as a string or empty string
    98  func (ctx *Context) RouteArg(argName string) string {
    99  	v, err := ctx.RouteArgErr(argName)
   100  	if err != nil {
   101  		return emptyString
   102  	}
   103  	return v
   104  }
   105  
   106  // ToCSV encodes csv-encoded value to client
   107  func (ctx *Context) ToCSV(v interface{}) ([]byte, error) {
   108  	return gocsv.MarshalBytes(v)
   109  }
   110  
   111  // ToXML encodes xml-encoded value to client
   112  func (ctx *Context) ToXML(v interface{}) ([]byte, error) {
   113  	b := bytes.NewBuffer(nil)
   114  	err := xml.NewEncoder(b).Encode(v)
   115  	return b.Bytes(), err
   116  }
   117  
   118  // GETKeys returns GET parameters keys (query args)
   119  func (ctx *Context) GETKeys() []string {
   120  	var res []string
   121  	ctx.Request.URI().QueryArgs().VisitAll(func(key, value []byte) {
   122  		res = append(res, string(key))
   123  	})
   124  	return res
   125  }
   126  
   127  // GETKeysBytes returns GET parameters keys (query args) as []byte
   128  func (ctx *Context) GETKeysBytes() [][]byte {
   129  	var res [][]byte
   130  	ctx.Request.URI().QueryArgs().VisitAll(func(key, value []byte) {
   131  		res = append(res, key)
   132  	})
   133  	return res
   134  }
   135  
   136  // GETParams returns GET parameters (query args)
   137  func (ctx *Context) GETParams() map[string][]string {
   138  	res := make(map[string][]string)
   139  	ctx.Request.URI().QueryArgs().VisitAll(func(key, value []byte) {
   140  		res[string(key)] = append(res[string(key)], string(value))
   141  	})
   142  	return res
   143  }
   144  
   145  // GETParam returns GET parameter (query arg) by name
   146  func (ctx *Context) GETParam(argName string) []string {
   147  	res := ctx.GETParams()
   148  	if param, ok := res[argName]; ok {
   149  		return param
   150  	}
   151  	return nil
   152  }
   153  
   154  // RouteArgErr returns an argument value as a string or empty string
   155  // and ErrArgNotFound if argument was not found
   156  func (ctx *Context) RouteArgErr(argName string) (string, error) {
   157  	i := ctx.UserValue(argName)
   158  	if i == nil {
   159  		return emptyString, ErrArgNotFound
   160  	}
   161  	switch value := i.(type) {
   162  	case string:
   163  		return value, nil
   164  	default:
   165  		return fmt.Sprintf(fmtV, i), nil
   166  	}
   167  }
   168  
   169  // ToTLS redirects user to HTTPS scheme
   170  func (ctx *Context) ToTLS() {
   171  	u := ctx.URI()
   172  	u.SetScheme(https)
   173  	ctx.Redirect(u.String(), redirectCode)
   174  }
   175  
   176  // Forbidden send 403 Forbidden error
   177  func (ctx *Context) Forbidden() {
   178  	ctx.Error(forbidden, forbiddenCode)
   179  }
   180  
   181  // ToJSON serializes v and returns the result
   182  func (ctx *Context) ToJSON(v interface{}) ([]byte, error) {
   183  	b := bytes.NewBuffer(nil)
   184  	enc := ffjson.NewEncoder(b)
   185  	err := enc.Encode(v)
   186  	return b.Bytes(), err
   187  }
   188  
   189  // UnJSONBytes deserializes JSON request body to given variable pointer or allocates a new one.
   190  // Returns resulting data and error. One of them may be nil.
   191  func (ctx *Context) UnJSONBytes(b []byte, v ...interface{}) (interface{}, error) {
   192  	return UnJSONBytes(b, v...)
   193  }
   194  
   195  // UnJSON deserializes JSON request body to given variable pointer
   196  func (ctx *Context) UnJSON(v interface{}) error {
   197  	return ffjson.NewDecoder().Decode(ctx.Request.Body(), &v)
   198  }
   199  
   200  // UnJSONBytes deserializes JSON request body to given variable pointer or allocates a new one.
   201  // Returns resulting data and error. One of them may be nil.
   202  func UnJSONBytes(b []byte, v ...interface{}) (interface{}, error) {
   203  	if len(v) == 0 {
   204  		var res interface{}
   205  		err := ffjson.NewDecoder().Decode(b, &res)
   206  		return res, err
   207  	}
   208  	err := ffjson.NewDecoder().Decode(b, &v[0])
   209  	return v[0], err
   210  }
   211  
   212  func (ctx *Context) jsonErrorLog(v interface{}) {
   213  	ctx.Err500()
   214  	if err := ctx.JSON(v); err != nil {
   215  		ctx.Logger.WithError(err).Error("JSONError err")
   216  	}
   217  }
   218  
   219  // RequestID return request ID for current context's request
   220  func (ctx *Context) RequestID() string {
   221  	return ctx.requestID
   222  }