goyave.dev/goyave/v4@v4.4.11/request.go (about)

     1  package goyave
     2  
     3  import (
     4  	"fmt"
     5  	"net"
     6  	"net/http"
     7  	"net/url"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/imdario/mergo"
    12  	"goyave.dev/goyave/v4/cors"
    13  	"goyave.dev/goyave/v4/util/fsutil"
    14  
    15  	"github.com/google/uuid"
    16  	"goyave.dev/goyave/v4/validation"
    17  )
    18  
    19  // Request struct represents an http request.
    20  // Contains the validated body in the Data attribute if the route was defined with a request generator function
    21  type Request struct {
    22  	httpRequest *http.Request
    23  	corsOptions *cors.Options
    24  	route       *Route
    25  	Rules       *validation.Rules
    26  	Params      map[string]string
    27  	Data        map[string]interface{}
    28  	Extra       map[string]interface{}
    29  	User        interface{}
    30  	Lang        string
    31  	cookies     []*http.Cookie
    32  }
    33  
    34  // Request return the raw http request.
    35  // Prefer using the "goyave.Request" accessors.
    36  func (r *Request) Request() *http.Request {
    37  	return r.httpRequest
    38  }
    39  
    40  // Method specifies the HTTP method (GET, POST, PUT, etc.).
    41  func (r *Request) Method() string {
    42  	return r.httpRequest.Method
    43  }
    44  
    45  // Protocol the protocol used by this request, "HTTP/1.1" for example.
    46  func (r *Request) Protocol() string {
    47  	return r.httpRequest.Proto
    48  }
    49  
    50  // URI specifies the URI being requested.
    51  // Use this if you absolutely need the raw query params, url, etc.
    52  // Otherwise use the provided methods and fields of the "goyave.Request".
    53  func (r *Request) URI() *url.URL {
    54  	return r.httpRequest.URL
    55  }
    56  
    57  // Route returns the current route.
    58  func (r *Request) Route() *Route {
    59  	return r.route
    60  }
    61  
    62  // Header contains the request header fields either received
    63  // by the server or to be sent by the client.
    64  // Header names are case-insensitive.
    65  //
    66  // If the raw request has the following header lines,
    67  //
    68  //	Host: example.com
    69  //	accept-encoding: gzip, deflate
    70  //	Accept-Language: en-us
    71  //	fOO: Bar
    72  //	foo: two
    73  //
    74  // then the header map will look like this:
    75  //
    76  //	Header = map[string][]string{
    77  //		"Accept-Encoding": {"gzip, deflate"},
    78  //		"Accept-Language": {"en-us"},
    79  //		"Foo": {"Bar", "two"},
    80  //	}
    81  func (r *Request) Header() http.Header {
    82  	return r.httpRequest.Header
    83  }
    84  
    85  // ContentLength records the length of the associated content.
    86  // The value -1 indicates that the length is unknown.
    87  func (r *Request) ContentLength() int64 {
    88  	return r.httpRequest.ContentLength
    89  }
    90  
    91  // RemoteAddress allows to record the network address that
    92  // sent the request, usually for logging.
    93  func (r *Request) RemoteAddress() string {
    94  	return r.httpRequest.RemoteAddr
    95  }
    96  
    97  // Cookies returns the HTTP cookies sent with the request.
    98  func (r *Request) Cookies() []*http.Cookie {
    99  	if r.cookies == nil {
   100  		r.cookies = r.httpRequest.Cookies()
   101  	}
   102  	return r.cookies
   103  }
   104  
   105  // Referrer returns the referring URL, if sent in the request.
   106  func (r *Request) Referrer() string {
   107  	return r.httpRequest.Referer()
   108  }
   109  
   110  // UserAgent returns the client's User-Agent, if sent in the request.
   111  func (r *Request) UserAgent() string {
   112  	return r.httpRequest.UserAgent()
   113  }
   114  
   115  // BasicAuth returns the username and password provided in the request's
   116  // Authorization header, if the request uses HTTP Basic Authentication.
   117  func (r *Request) BasicAuth() (username, password string, ok bool) {
   118  	return r.httpRequest.BasicAuth()
   119  }
   120  
   121  // BearerToken extract the auth token from the "Authorization" header.
   122  // Only takes tokens of type "Bearer".
   123  // Returns empty string if no token found or the header is invalid.
   124  func (r *Request) BearerToken() (string, bool) {
   125  	const schema = "Bearer "
   126  	header := r.Header().Get("Authorization")
   127  	if !strings.HasPrefix(header, schema) {
   128  		return "", false
   129  	}
   130  	return strings.TrimSpace(header[len(schema):]), true
   131  }
   132  
   133  // CORSOptions returns the CORS options applied to this request, or nil.
   134  // The returned object is a copy of the options applied to the router.
   135  // Therefore, altering the returned object will not alter the router's options.
   136  func (r *Request) CORSOptions() *cors.Options {
   137  	if r.corsOptions == nil {
   138  		return nil
   139  	}
   140  
   141  	cpy := *r.corsOptions
   142  	return &cpy
   143  }
   144  
   145  // Has check if the given field exists in the request data.
   146  func (r *Request) Has(field string) bool {
   147  	_, exists := r.Data[field]
   148  	return exists
   149  }
   150  
   151  // String get a string field from the request data.
   152  // Panics if the field is not a string.
   153  func (r *Request) String(field string) string {
   154  	str, ok := r.Data[field].(string)
   155  	if !ok {
   156  		panic(fmt.Sprintf("Field \"%s\" is not a string", field))
   157  	}
   158  	return str
   159  }
   160  
   161  // Numeric get a numeric field from the request data.
   162  // Panics if the field is not numeric.
   163  func (r *Request) Numeric(field string) float64 {
   164  	str, ok := r.Data[field].(float64)
   165  	if !ok {
   166  		panic(fmt.Sprintf("Field \"%s\" is not numeric", field))
   167  	}
   168  	return str
   169  }
   170  
   171  // Integer get an integer field from the request data.
   172  // Panics if the field is not an integer.
   173  func (r *Request) Integer(field string) int {
   174  	str, ok := r.Data[field].(int)
   175  	if !ok {
   176  		panic(fmt.Sprintf("Field \"%s\" is not an integer", field))
   177  	}
   178  	return str
   179  }
   180  
   181  // Bool get a bool field from the request data.
   182  // Panics if the field is not a bool.
   183  func (r *Request) Bool(field string) bool {
   184  	str, ok := r.Data[field].(bool)
   185  	if !ok {
   186  		panic(fmt.Sprintf("Field \"%s\" is not a bool", field))
   187  	}
   188  	return str
   189  }
   190  
   191  // File get a file field from the request data.
   192  // Panics if the field is not numeric.
   193  func (r *Request) File(field string) []fsutil.File {
   194  	str, ok := r.Data[field].([]fsutil.File)
   195  	if !ok {
   196  		panic(fmt.Sprintf("Field \"%s\" is not a file", field))
   197  	}
   198  	return str
   199  }
   200  
   201  // Timezone get a timezone field from the request data.
   202  // Panics if the field is not a timezone.
   203  func (r *Request) Timezone(field string) *time.Location {
   204  	str, ok := r.Data[field].(*time.Location)
   205  	if !ok {
   206  		panic(fmt.Sprintf("Field \"%s\" is not a timezone", field))
   207  	}
   208  	return str
   209  }
   210  
   211  // IP get an IP field from the request data.
   212  // Panics if the field is not an IP.
   213  func (r *Request) IP(field string) net.IP {
   214  	str, ok := r.Data[field].(net.IP)
   215  	if !ok {
   216  		panic(fmt.Sprintf("Field \"%s\" is not an IP", field))
   217  	}
   218  	return str
   219  }
   220  
   221  // URL get a URL field from the request data.
   222  // Panics if the field is not a URL.
   223  func (r *Request) URL(field string) *url.URL {
   224  	str, ok := r.Data[field].(*url.URL)
   225  	if !ok {
   226  		panic(fmt.Sprintf("Field \"%s\" is not a URL", field))
   227  	}
   228  	return str
   229  }
   230  
   231  // UUID get a UUID field from the request data.
   232  // Panics if the field is not a UUID.
   233  func (r *Request) UUID(field string) uuid.UUID {
   234  	str, ok := r.Data[field].(uuid.UUID)
   235  	if !ok {
   236  		panic(fmt.Sprintf("Field \"%s\" is not an UUID", field))
   237  	}
   238  	return str
   239  }
   240  
   241  // Date get a date field from the request data.
   242  // Panics if the field is not a date.
   243  func (r *Request) Date(field string) time.Time {
   244  	str, ok := r.Data[field].(time.Time)
   245  	if !ok {
   246  		panic(fmt.Sprintf("Field \"%s\" is not a date", field))
   247  	}
   248  	return str
   249  }
   250  
   251  // Object get an object field from the request data.
   252  // Panics if the field is not an object.
   253  func (r *Request) Object(field string) map[string]interface{} {
   254  	str, ok := r.Data[field].(map[string]interface{})
   255  	if !ok {
   256  		panic(fmt.Sprintf("Field \"%s\" is not an object", field))
   257  	}
   258  	return str
   259  }
   260  
   261  // ToStruct map the request data to a struct.
   262  //
   263  //	 type UserInsertRequest struct {
   264  //		 Username string
   265  //		 Email string
   266  //	 }
   267  //	 //...
   268  //	 userInsertRequest := UserInsertRequest{}
   269  //	 if err := request.ToStruct(&userInsertRequest); err != nil {
   270  //	  panic(err)
   271  //	 }
   272  func (r *Request) ToStruct(dst interface{}) error {
   273  	return mergo.Map(dst, r.Data)
   274  }
   275  
   276  func (r *Request) validate() validation.Errors {
   277  	if r.Rules == nil {
   278  		return nil
   279  	}
   280  
   281  	extra := map[string]interface{}{
   282  		"request": r,
   283  	}
   284  	contentType := r.httpRequest.Header.Get("Content-Type")
   285  	return validation.ValidateWithExtra(r.Data, r.Rules, strings.HasPrefix(contentType, "application/json"), r.Lang, extra)
   286  }