Fetch performs an HTTP GET request to the configured endpoint. It returns the response body as an io.ReadCloser on success. The caller is responsible for closing the reader. It accepts a variable number of FetchOption functions to customize the request and response handling.
(ctx context.Context, opts ...FetchOption)
| 89 | // The caller is responsible for closing the reader. |
| 90 | // It accepts a variable number of FetchOption functions to customize the request and response handling. |
| 91 | func (f HTTPFetcher) Fetch(ctx context.Context, opts ...FetchOption) (io.ReadCloser, error) { |
| 92 | // Default options |
| 93 | options := &FetchOptions{ |
| 94 | Response: ResponseOptions{ |
| 95 | ExpectedStatusCodes: []int{http.StatusOK}, |
| 96 | }, |
| 97 | Request: RequestOptions{ |
| 98 | Headers: nil, |
| 99 | }, |
| 100 | } |
| 101 | |
| 102 | // Apply custom options |
| 103 | for _, opt := range opts { |
| 104 | opt(options) |
| 105 | } |
| 106 | |
| 107 | req, err := http.NewRequestWithContext(ctx, http.MethodGet, f.endpoint.String(), nil) |
| 108 | if err != nil { |
| 109 | slog.ErrorContext(ctx, "unable to create request", "error", err, "url", f.endpoint.String()) |
| 110 | |
| 111 | return nil, errors.Join(fetchtypes.ErrFailedToBuildRequest, err) |
| 112 | } |
| 113 | |
| 114 | // Apply request options |
| 115 | for k, v := range options.Request.Headers { |
| 116 | req.Header.Add(k, v) |
| 117 | } |
| 118 | |
| 119 | resp, err := f.httpClient.Do(req) |
| 120 | if err != nil { |
| 121 | slog.ErrorContext(ctx, "unable to fetch", "error", err, "url", f.endpoint.String()) |
| 122 | |
| 123 | return nil, errors.Join(fetchtypes.ErrFailedToFetch, err) |
| 124 | } |
| 125 | |
| 126 | // Apply response options |
| 127 | statusCodeMatch := slices.Contains(options.Response.ExpectedStatusCodes, resp.StatusCode) |
| 128 | |
| 129 | if !statusCodeMatch { |
| 130 | slog.ErrorContext(ctx, "bad status code while fetching", "status", resp.StatusCode, "url", f.endpoint.String()) |
| 131 | // Clean up by closing since we will not be returning the body |
| 132 | resp.Body.Close() |
| 133 | |
| 134 | return nil, errors.Join(fetchtypes.ErrUnexpectedResult, fmt.Errorf("bad status code:%d", resp.StatusCode)) |
| 135 | } |
| 136 | |
| 137 | if resp.Body == nil { |
| 138 | slog.ErrorContext(ctx, "missing body", "url", f.endpoint.String()) |
| 139 | |
| 140 | return nil, fetchtypes.ErrMissingBody |
| 141 | } |
| 142 | |
| 143 | return resp.Body, nil |
| 144 | } |