package request import ( "bytes" "context" "encoding/json" "encoding/xml" "fmt" "io" "net/http" "net/url" "os" "path" "reflect" "regexp" "strings" ) const ( JSON = "application/json" XML = "application/xml" plainTextType = "text/plain; charset=utf-8" jsonContentType = "application/json" formContentType = "application/x-www-form-urlencoded" ) var ( jsonCheck = regexp.MustCompile(`(?i:(application|text)/(json|.*\+json|json\-.*)(;|$))`) xmlCheck = regexp.MustCompile(`(?i:(application|text)/(xml|.*\+xml)(;|$))`) ) type Request struct { context context.Context method string uri string url *url.URL body any query url.Values formData url.Values header http.Header contentType string authorization Authorization client *Client rawRequest *http.Request rawResponse *http.Response } func (r *Request) detectContentType(body interface{}) string { contentType := plainTextType kind := reflect.Indirect(reflect.ValueOf(body)).Type().Kind() switch kind { case reflect.Struct, reflect.Map: contentType = jsonContentType case reflect.String: contentType = plainTextType default: if b, ok := body.([]byte); ok { contentType = http.DetectContentType(b) } else if kind == reflect.Slice { contentType = jsonContentType } } return contentType } func (r *Request) readRequestBody(contentType string, body any) (reader io.Reader, err error) { var ( ok bool s string buf []byte ) kind := reflect.Indirect(reflect.ValueOf(body)).Type().Kind() if reader, ok = r.body.(io.Reader); ok { return reader, nil } if buf, ok = r.body.([]byte); ok { goto __end } if s, ok = r.body.(string); ok { buf = []byte(s) goto __end } if jsonCheck.MatchString(contentType) && (kind == reflect.Struct || kind == reflect.Map || kind == reflect.Slice) { buf, err = json.Marshal(r.body) goto __end } if xmlCheck.MatchString(contentType) && (kind == reflect.Struct) { buf, err = xml.Marshal(r.body) goto __end } err = fmt.Errorf("unmarshal content type %s", contentType) __end: if err == nil { if len(buf) > 0 { return bytes.NewReader(buf), nil } } return } func (r *Request) SetContext(ctx context.Context) *Request { r.context = ctx return r } func (r *Request) AddQuery(k, v string) *Request { r.query.Add(k, v) return r } func (r *Request) SetQuery(vs map[string]string) *Request { for k, v := range vs { r.query.Set(k, v) } return r } func (r *Request) AddFormData(k, v string) *Request { r.contentType = formContentType r.formData.Add(k, v) return r } func (r *Request) SetFormData(vs map[string]string) *Request { r.contentType = formContentType for k, v := range vs { r.formData.Set(k, v) } return r } func (r *Request) SetBody(v any) *Request { r.body = v return r } func (r *Request) SetContentType(v string) *Request { r.contentType = v return r } func (r *Request) AddHeader(k, v string) *Request { r.header.Add(k, v) return r } func (r *Request) SetHeader(h http.Header) *Request { r.header = h return r } func (r *Request) Do() (res *http.Response, err error) { var s string s = r.formData.Encode() if len(s) > 0 { r.body = s } r.url.RawQuery = r.query.Encode() r.uri = r.url.String() return r.client.execute(r) } func (r *Request) Response(v any) (err error) { var ( res *http.Response buf []byte contentType string ) if res, err = r.Do(); err != nil { return } defer func() { _ = res.Body.Close() }() if res.StatusCode/100 != 2 { if buf, err = io.ReadAll(res.Body); err == nil && len(buf) > 0 { err = fmt.Errorf("http response %s(%d): %s", res.Status, res.StatusCode, string(buf)) } else { err = fmt.Errorf("http response %d: %s", res.StatusCode, res.Status) } return } contentType = strings.ToLower(res.Header.Get("Content-Type")) extName := path.Ext(r.rawRequest.URL.String()) if strings.Contains(contentType, JSON) || extName == ".json" { err = json.NewDecoder(res.Body).Decode(v) } else if strings.Contains(contentType, XML) || extName == ".xml" { err = xml.NewDecoder(res.Body).Decode(v) } else { err = fmt.Errorf("unsupported content type: %s", contentType) } return } func (r *Request) Download(s string) (err error) { var ( fp *os.File res *http.Response ) if res, err = r.Do(); err != nil { return } defer func() { _ = res.Body.Close() }() if fp, err = os.OpenFile(s, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644); err != nil { return } defer func() { _ = fp.Close() }() _, err = io.Copy(fp, res.Body) return } func newRequest(method string, uri string, client *Client) *Request { var ( err error ) r := &Request{ context: context.Background(), method: method, uri: uri, header: make(http.Header), formData: make(url.Values), client: client, } if r.url, err = url.Parse(uri); err == nil { r.query = r.url.Query() } else { r.query = make(url.Values) } return r }