diff --git a/CHANGELOG.md b/CHANGELOG.md index a3bda05..e906268 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # CHANGELOG +## v1.0.4 (2026-05-05) +- **基础设施对齐**: + - 更新 `go/file` 至 v1.0.5, `go/log` 至 v1.1.1, `go/config` 至 v1.0.5。 + - `PostMultipart` 现在通过 `go/file` API 透明支持物理文件与内存文件系统的读取。 +- **鲁棒性增强**: + - 优化 `doByRequest` 中对 `X-Forwarded-For` 的处理,增强 `RemoteAddr` 解析的健壮性。 +- **代码规范**: + - 统一变量命名规范,重命名冗余的单字母变量,提升代码可读性。 + ## v1.0.3 (2026-05-03) - **API 变更**: 将泛型解析函数 `Bind[T]` 重命名为 `To[T]`,以保持与全局 API 风格一致。 - **文档优化**: 移除冗余的 `AI.md`,更新 `README.md` 中的 API 示例。 diff --git a/TEST.md b/TEST.md index 4d42ad0..4f05d6d 100644 --- a/TEST.md +++ b/TEST.md @@ -14,5 +14,5 @@ goos: darwin goarch: amd64 pkg: apigo.cc/go/http cpu: Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz -BenchmarkGet-16 15244 79100 ns/op +BenchmarkGet-16 15944 73379 ns/op ``` diff --git a/client.go b/client.go index 32935cc..6bafdd2 100644 --- a/client.go +++ b/client.go @@ -19,7 +19,6 @@ import ( "time" "apigo.cc/go/cast" - "apigo.cc/go/convert" "apigo.cc/go/encoding" "apigo.cc/go/file" "apigo.cc/go/log" @@ -94,81 +93,82 @@ func NewClientH2C(timeout time.Duration) *Client { } } -func (c *Client) GetRawClient() *http.Client { - return c.pool +func (client *Client) GetRawClient() *http.Client { + return client.pool } -func (c *Client) EnableRedirect() { - c.pool.CheckRedirect = nil +func (client *Client) EnableRedirect() { + client.pool.CheckRedirect = nil } -func (c *Client) SetGlobalHeader(k, v string) { - c.headersMu.Lock() - defer c.headersMu.Unlock() - if v == "" { - delete(c.globalHeaders, k) +func (client *Client) SetGlobalHeader(key, value string) { + client.headersMu.Lock() + defer client.headersMu.Unlock() + if value == "" { + delete(client.globalHeaders, key) } else { - c.globalHeaders[k] = v + client.globalHeaders[key] = value } } -func (c *Client) GetGlobalHeader(k string) string { - c.headersMu.RLock() - defer c.headersMu.RUnlock() - return c.globalHeaders[k] +func (client *Client) GetGlobalHeader(key string) string { + client.headersMu.RLock() + defer client.headersMu.RUnlock() + return client.globalHeaders[key] } -func (c *Client) Destroy() { - if c.pool != nil { - c.pool.CloseIdleConnections() - c.pool = nil +func (client *Client) Destroy() { + if client.pool != nil { + client.pool.CloseIdleConnections() + client.pool = nil } } -func (c *Client) Get(url string, headers ...string) *Result { - return c.Do("GET", url, nil, headers...) +func (client *Client) Get(url string, headers ...string) *Result { + return client.Do("GET", url, nil, headers...) } -func (c *Client) Post(url string, data any, headers ...string) *Result { - return c.Do("POST", url, data, headers...) +func (client *Client) Post(url string, data any, headers ...string) *Result { + return client.Do("POST", url, data, headers...) } -func (c *Client) Put(url string, data any, headers ...string) *Result { - return c.Do("PUT", url, data, headers...) +func (client *Client) Put(url string, data any, headers ...string) *Result { + return client.Do("PUT", url, data, headers...) } -func (c *Client) Delete(url string, data any, headers ...string) *Result { - return c.Do("DELETE", url, data, headers...) +func (client *Client) Delete(url string, data any, headers ...string) *Result { + return client.Do("DELETE", url, data, headers...) } -func (c *Client) Head(url string, headers ...string) *Result { - return c.Do("HEAD", url, nil, headers...) +func (client *Client) Head(url string, headers ...string) *Result { + return client.Do("HEAD", url, nil, headers...) } -func (c *Client) DoByRequest(request *http.Request, method, url string, data any, settedHeaders ...string) *Result { - return c.doByRequest(false, request, method, url, data, settedHeaders...) +func (client *Client) DoByRequest(request *http.Request, method, url string, data any, settedHeaders ...string) *Result { + return client.doByRequest(false, request, method, url, data, settedHeaders...) } -func (c *Client) ManualDoByRequest(request *http.Request, method, url string, data any, settedHeaders ...string) *Result { - return c.doByRequest(true, request, method, url, data, settedHeaders...) +func (client *Client) ManualDoByRequest(request *http.Request, method, url string, data any, settedHeaders ...string) *Result { + return client.doByRequest(true, request, method, url, data, settedHeaders...) } -func (c *Client) doByRequest(manualDo bool, request *http.Request, method, url string, data any, settedHeaders ...string) *Result { +func (client *Client) doByRequest(manualDo bool, request *http.Request, method, url string, data any, settedHeaders ...string) *Result { headers := make([]string, 0, len(RelayHeaders)*2+len(settedHeaders)+4) // 续传指定的头 - for _, h := range RelayHeaders { - if v := request.Header.Get(h); v != "" { - headers = append(headers, h, v) + for _, headerName := range RelayHeaders { + if value := request.Header.Get(headerName); value != "" { + headers = append(headers, headerName, value) } } // 续传 X-Forwarded-For xForwardFor := request.Header.Get(HeaderForwardedFor) - remoteIP, _, _ := net.SplitHostPort(request.RemoteAddr) - if remoteIP == "" { + remoteIP, _, err := net.SplitHostPort(request.RemoteAddr) + if err != nil { remoteIP = request.RemoteAddr } + if xForwardFor != "" { xForwardFor = remoteIP + ", " + xForwardFor } else { @@ -191,17 +191,17 @@ func (c *Client) doByRequest(manualDo bool, request *http.Request, method, url s headers = append(headers, settedHeaders...) if manualDo { - return c.ManualDo(method, url, data, headers...) + return client.ManualDo(method, url, data, headers...) } - return c.Do(method, url, data, headers...) + return client.Do(method, url, data, headers...) } -func (c *Client) Do(method, url string, data any, headers ...string) *Result { - return c.do(true, method, url, data, headers...) +func (client *Client) Do(method, url string, data any, headers ...string) *Result { + return client.do(true, method, url, data, headers...) } -func (c *Client) ManualDo(method, url string, data any, headers ...string) *Result { - return c.do(false, method, url, data, headers...) +func (client *Client) ManualDo(method, url string, data any, headers ...string) *Result { + return client.do(false, method, url, data, headers...) } type downloadRange struct { @@ -220,29 +220,29 @@ func (w *offsetWriter) Write(p []byte) (n int, err error) { return } -func (c *Client) downloadPart(fp *os.File, task *downloadRange, url string, headers ...string) (int64, error) { +func (client *Client) downloadPart(fp *os.File, task *downloadRange, url string, headers ...string) (int64, error) { partHeaders := make([]string, len(headers)) copy(partHeaders, headers) partHeaders[len(partHeaders)-1] = fmt.Sprintf("bytes=%d-%d", task.Start, task.End) - r := c.ManualDo("GET", url, nil, partHeaders...) - if r.Error != nil { - return 0, r.Error + result := client.ManualDo("GET", url, nil, partHeaders...) + if result.Error != nil { + return 0, result.Error } - defer r.Response.Body.Close() - return io.Copy(&offsetWriter{fp: fp, offset: task.Start}, r.Response.Body) + defer result.Response.Body.Close() + return io.Copy(&offsetWriter{fp: fp, offset: task.Start}, result.Response.Body) } -func (c *Client) Download(filename, url string, callback func(start, end int64, ok bool, finished, total int64), headers ...string) (*Result, error) { - r1 := c.Head(url, headers...) - if r1.Error != nil { - return r1, r1.Error +func (client *Client) Download(filename, url string, callback func(start, end int64, ok bool, finished, total int64), headers ...string) (*Result, error) { + resultHead := client.Head(url, headers...) + if resultHead.Error != nil { + return resultHead, resultHead.Error } - total := r1.Response.ContentLength + total := resultHead.Response.ContentLength if total > 0 { tasks := make([]downloadRange, 0) - for i := int64(0); i < total; i += c.DownloadPartSize { - end := i + c.DownloadPartSize - 1 + for i := int64(0); i < total; i += client.DownloadPartSize { + end := i + client.DownloadPartSize - 1 if end >= total { end = total - 1 } @@ -264,8 +264,8 @@ func (c *Client) Download(filename, url string, callback func(start, end int64, // 限制并发度,默认为 4,可以通过 Client 设置 concurrency := 4 - if c.MaxConnsPerHost > 0 { - concurrency = c.MaxConnsPerHost + if client.MaxConnsPerHost > 0 { + concurrency = client.MaxConnsPerHost } sem := make(chan struct{}, concurrency) @@ -276,10 +276,10 @@ func (c *Client) Download(filename, url string, callback func(start, end int64, sem <- struct{}{} defer func() { <-sem }() - n, err := c.downloadPart(fp, &t, url, headers...) + n, err := client.downloadPart(fp, &t, url, headers...) if err != nil { // 重试一次 - n, err = c.downloadPart(fp, &t, url, headers...) + n, err = client.downloadPart(fp, &t, url, headers...) } mu.Lock() @@ -305,25 +305,25 @@ func (c *Client) Download(filename, url string, callback func(start, end int64, if finished < total { return nil, errors.New("download file failed: incomplete") } - return r1, nil + return resultHead, nil } - r := c.ManualDo("GET", url, nil, headers...) - if r.Error != nil { - return r, r.Error + result := client.ManualDo("GET", url, nil, headers...) + if result.Error != nil { + return result, result.Error } - defer r.Response.Body.Close() + defer result.Response.Body.Close() file.EnsureParentDir(filename) fp, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) if err != nil { - return r, err + return result, err } defer fp.Close() - _, err = io.Copy(fp, r.Response.Body) - return r, err + _, err = io.Copy(fp, result.Response.Body) + return result, err } -func (c *Client) PostMultipart(url string, formData map[string]string, files map[string]any, headers ...string) (*Result, []error) { +func (client *Client) PostMultipart(url string, formData map[string]string, files map[string]any, headers ...string) (*Result, []error) { errs := make([]error, 0) buf := bufferPool.Get().(*bytes.Buffer) buf.Reset() @@ -332,47 +332,60 @@ func (c *Client) PostMultipart(url string, formData map[string]string, files map writer := multipart.NewWriter(buf) if formData != nil { - for k, v := range formData { - if err := writer.WriteField(k, v); err != nil { + for key, value := range formData { + if err := writer.WriteField(key, value); err != nil { errs = append(errs, err) } } } if files != nil { - for k, v := range files { - if filename, ok := v.(string); ok && file.Exists(filename) { - if fp, err := os.Open(filename); err == nil { - if part, err := writer.CreateFormFile(k, filepath.Base(filename)); err == nil { - if _, err = io.Copy(part, fp); err != nil { - errs = append(errs, err) - } + for key, value := range files { + if filename, ok := value.(string); ok && file.Exists(filename) { + var reader io.Reader + var closer io.Closer + if mf := file.ReadFileFromMemory(filename); mf != nil { + reader = bytes.NewReader(mf.GetData()) + } else { + if fp, err := os.Open(filename); err == nil { + reader = fp + closer = fp } else { errs = append(errs, err) + continue + } + } + + if part, err := writer.CreateFormFile(key, filepath.Base(filename)); err == nil { + if _, err = io.Copy(part, reader); err != nil { + errs = append(errs, err) } - _ = fp.Close() } else { errs = append(errs, err) } + + if closer != nil { + _ = closer.Close() + } } else { h := make(textproto.MIMEHeader) var dataBytes []byte - switch t := v.(type) { + switch t := value.(type) { case io.Reader: dataBytes, _ = io.ReadAll(t) - h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, k, k)) + h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, key, key)) h.Set("Content-Type", "application/octet-stream") case []byte: dataBytes = t - h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, k, k)) + h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, key, key)) h.Set("Content-Type", "application/octet-stream") case string: dataBytes = []byte(t) - h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s.txt"`, k, k)) + h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s.txt"`, key, key)) h.Set("Content-Type", "text/plain") default: - dataBytes = cast.MustJSONBytes(v) - h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s.json"`, k, k)) + dataBytes = cast.As(cast.ToJSONBytes(value)) + h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s.json"`, key, key)) h.Set("Content-Type", "application/json") } @@ -396,14 +409,14 @@ func (c *Client) PostMultipart(url string, formData map[string]string, files map } headers = append(headers, "Content-Type", writer.FormDataContentType()) - r := c.Post(url, buf.Bytes(), headers...) - if r.Error != nil { - errs = append(errs, r.Error) + result := client.Post(url, buf.Bytes(), headers...) + if result.Error != nil { + errs = append(errs, result.Error) } - return r, errs + return result, errs } -func (c *Client) do(fetchBody bool, method, url string, data any, headers ...string) *Result { +func (client *Client) do(fetchBody bool, method, url string, data any, headers ...string) *Result { var req *http.Request var err error contentType := "" @@ -470,17 +483,17 @@ func (c *Client) do(fetchBody bool, method, url string, data any, headers ...str } } - c.headersMu.RLock() - for k, v := range c.globalHeaders { + client.headersMu.RLock() + for k, v := range client.globalHeaders { req.Header.Set(k, v) } - c.headersMu.RUnlock() + client.headersMu.RUnlock() - if c.Debug { + if client.Debug { log.DefaultLogger.Info("http request", "method", req.Method, "url", req.URL.String(), "headers", req.Header) } - res, err := c.pool.Do(req) + res, err := client.pool.Do(req) if err != nil { return &Result{Error: err} } @@ -489,7 +502,7 @@ func (c *Client) do(fetchBody bool, method, url string, data any, headers ...str res.ContentLength = cast.Int64(res.Header.Get("Content-Length")) } - if !fetchBody || c.NoBody { + if !fetchBody || client.NoBody { return &Result{Response: res} } @@ -499,61 +512,61 @@ func (c *Client) do(fetchBody bool, method, url string, data any, headers ...str return &Result{Error: err, Response: res} } - if c.Debug { + if client.Debug { log.DefaultLogger.Info("http response", "status", res.StatusCode, "len", len(bodyBytes)) } return &Result{data: bodyBytes, Response: res} } -func (rs *Result) Save(filename string) error { +func (result *Result) Save(filename string) error { file.EnsureParentDir(filename) - if rs.data != nil { - return file.WriteBytes(filename, rs.data) + if result.data != nil { + return file.WriteBytes(filename, result.data) } - if rs.Response != nil && rs.Response.Body != nil { - defer rs.Response.Body.Close() + if result.Response != nil && result.Response.Body != nil { + defer result.Response.Body.Close() fp, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) if err != nil { return err } defer fp.Close() - _, err = io.Copy(fp, rs.Response.Body) + _, err = io.Copy(fp, result.Response.Body) return err } return errors.New("no data to save") } -func (rs *Result) String() string { - return string(rs.data) +func (result *Result) String() string { + return string(result.data) } -func (rs *Result) Bytes() []byte { - return rs.data +func (result *Result) Bytes() []byte { + return result.data } -func (rs *Result) Map() map[string]any { +func (result *Result) Map() map[string]any { var m map[string]any - _ = rs.To(&m) + _ = result.To(&m) return m } -func (rs *Result) Slice() []any { +func (result *Result) Slice() []any { var a []any - _ = rs.To(&a) + _ = result.To(&a) return a } -func (rs *Result) To(v any) error { - if rs.data == nil { +func (result *Result) To(v any) error { + if result.data == nil { return errors.New("no data") } - _, err := cast.UnmarshalJSONBytes(rs.data, v) + err := cast.UnmarshalJSON(result.data, v) if err != nil { // 如果 cast 直接解不出来,尝试通过 convert 做深度映射(处理 struct 字段匹配等) var tmp any - if _, err2 := cast.UnmarshalJSONBytes(rs.data, &tmp); err2 == nil { - convert.To(tmp, v) + if err2 := cast.UnmarshalJSON(result.data, &tmp); err2 == nil { + cast.Convert(v, tmp) return nil } } @@ -561,8 +574,8 @@ func (rs *Result) To(v any) error { } // To 使用泛型获取结果 -func To[T any](rs *Result) (T, error) { +func To[T any](result *Result) (T, error) { var v T - err := rs.To(&v) + err := result.To(&v) return v, err } diff --git a/go.mod b/go.mod index 3fdb4fe..0f1614b 100644 --- a/go.mod +++ b/go.mod @@ -3,21 +3,25 @@ module apigo.cc/go/http go 1.25.0 require ( - apigo.cc/go/cast v1.1.1 - apigo.cc/go/convert v1.0.4 + apigo.cc/go/cast v1.2.6 apigo.cc/go/encoding v1.0.4 - apigo.cc/go/file v1.0.4 - apigo.cc/go/log v1.0.0 + apigo.cc/go/file v1.0.5 + apigo.cc/go/log v1.1.1 apigo.cc/go/rand v1.0.4 golang.org/x/net v0.53.0 ) +require apigo.cc/go/convert v1.0.4 // indirect + require ( - apigo.cc/go/config v1.0.4 // indirect + apigo.cc/go/config v1.0.5 // indirect apigo.cc/go/safe v1.0.4 // indirect apigo.cc/go/shell v1.0.4 // indirect + github.com/kr/pretty v0.3.0 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect golang.org/x/crypto v0.50.0 // indirect golang.org/x/sys v0.43.0 // indirect golang.org/x/text v0.36.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 9b2b46c..dc3396e 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -apigo.cc/go/cast v1.1.1 h1:+5pluN8g1RK2J4byr2xkfOmEdKSmy1PByOqDOHtt/Ns= -apigo.cc/go/cast v1.1.1/go.mod h1:vh9ZqISCmTUiyinkNMI/s4f045fRlDK3xC+nPWQYBzI= +apigo.cc/go/cast v1.2.6 h1:xnWiaQAGsRCrnu1p8fIFQfg5HFSc7CxR+3ItiDIDMaY= +apigo.cc/go/cast v1.2.6/go.mod h1:lGlwImiOvHxG7buyMWhFzcdvQzmSaoKbmr7bcDfUpHk= apigo.cc/go/config v1.0.4 h1:WG9zrQkqfFPkrKIL7RNvvAbbkuUBt1Av11ZP/aIfldM= apigo.cc/go/config v1.0.4/go.mod h1:obryzJiK6j7lQex/58d5eWYOGx5O5IABguqNWxyyXJo= apigo.cc/go/convert v1.0.4 h1:5+qPjC3dlPB59GnWZRlmthxcaXQtKvN+iOuiLdJ1GvQ= @@ -8,14 +8,26 @@ apigo.cc/go/encoding v1.0.4 h1:aezB0J/qFuHs6iXkbtuJP5JIHUtmjsr5SFb0NNvbObY= apigo.cc/go/encoding v1.0.4/go.mod h1:V5CgT7rBbCxy+uCU20q0ptcNNRSgMtpA8cNOs6r8IeI= apigo.cc/go/file v1.0.4 h1:qCKegV7OYh7r0qc3jZjGA/aKh0vIHgmr1OEbhfEmGX8= apigo.cc/go/file v1.0.4/go.mod h1:C9gNo7386iA21OiBmuWh6CznKWlVBDFkhE4f0H0Susg= -apigo.cc/go/log v1.0.0 h1:lI1NGTSS+Jm12G8BD7ZJO4/hrkfuLTu5O8z36GD8GpU= -apigo.cc/go/log v1.0.0/go.mod h1:tvPgFpebY9Wf/DlqMHZ0ZjxDp9AaQTywOQKvtBaNqNo= +apigo.cc/go/log v1.0.2 h1:OY6T3SC28blDNkMpdRvDK2N4sGdriAB9DBItGl/qOos= +apigo.cc/go/log v1.0.2/go.mod h1:tvPgFpebY9Wf/DlqMHZ0ZjxDp9AaQTywOQKvtBaNqNo= apigo.cc/go/rand v1.0.4 h1:we070eWSL0dB8NEMaWjXj43+EekXQTm/h0kKpZ/frqw= apigo.cc/go/rand v1.0.4/go.mod h1:mZ/4Soa3bk+XvDaqPWJuUe1bfEi4eThBj1XmEAuYxsk= apigo.cc/go/safe v1.0.4 h1:07pRSdEHprF/2v6SsqAjICYFoeLcqjjvHGEdh6Dzrzg= apigo.cc/go/safe v1.0.4/go.mod h1:o568sHS5rTRSVPmhxWod0tGdc+8l1KjidsNY1/OVZr0= apigo.cc/go/shell v1.0.4 h1:EL9zjI39YBe1h+kRYQeAi/8zVGHe5W198DYYN7cENiY= apigo.cc/go/shell v1.0.4/go.mod h1:N2gDkgK4tJ9TadD60/+gAGuWxyVAWHs5YPBmytw6ELA= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= @@ -24,7 +36,10 @@ golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=