master
go 91 lines 2 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package web
4
5 import (
6 "encoding/json"
7 "encoding/xml"
8 "fmt"
9 "io"
10 "net/http"
11 "strings"
12 )
13
14 type Client struct {
15 httpClient *http.Client
16 onNokCode func(resp *http.Response) (bool, error)
17 }
18
19 func DoHTTP(cl *http.Client) *Client {
20 return &Client{
21 httpClient: cl,
22 }
23 }
24
25 func (c *Client) OnNokCode(fn func(resp *http.Response) (bool, error)) *Client {
26 c.onNokCode = fn
27 return c
28 }
29
30 func (c *Client) RequestJSON(req *http.Request, in any) error {
31 return c.Request(req, func(body io.Reader) error {
32 return json.NewDecoder(body).Decode(in)
33 })
34 }
35
36 func (c *Client) RequestXML(req *http.Request, in any, opts ...func(dec *xml.Decoder)) error {
37 return c.Request(req, func(body io.Reader) error {
38 dec := xml.NewDecoder(body)
39 for _, opt := range opts {
40 opt(dec)
41 }
42 return dec.Decode(in)
43 })
44 }
45
46 func (c *Client) Request(req *http.Request, parse func(body io.Reader) error) error {
47 resp, err := c.httpClient.Do(req)
48 if err != nil {
49 return fmt.Errorf("error on HTTP request to '%s': %w", req.URL, err)
50 }
51
52 defer CloseBody(resp)
53
54 if resp.StatusCode != http.StatusOK {
55 if err := c.handleNokCode(req, resp); err != nil {
56 return err
57 }
58 }
59
60 if parse != nil {
61 if err := parse(resp.Body); err != nil {
62 return fmt.Errorf("error on parsing response from '%s': %w", req.URL, err)
63 }
64 }
65
66 return nil
67 }
68
69 func (c *Client) handleNokCode(req *http.Request, resp *http.Response) error {
70 if c.onNokCode != nil {
71 handled, err := c.onNokCode(resp)
72 if err != nil {
73 return fmt.Errorf("%s '%s' returned HTTP status code: %d (%w)", req.Method, req.URL, resp.StatusCode, err)
74 }
75 if handled {
76 return nil
77 }
78 }
79 return fmt.Errorf("%s '%s' returned HTTP status code: %d", req.Method, req.URL, resp.StatusCode)
80 }
81
82 func CloseBody(resp *http.Response) {
83 if resp != nil && resp.Body != nil {
84 _, _ = io.Copy(io.Discard, resp.Body)
85 _ = resp.Body.Close()
86 }
87 }
88
89 func IsStatusCode(err error, code int) bool {
90 return err != nil && strings.Contains(err.Error(), fmt.Sprintf("code: %d", code))
91 }