go 46 lines · 6 steps

Fetching a charge over HTTP in Go

A timeout-bounded API client method that builds a request, checks the response, and decodes JSON into a typed struct.

Explained by highlit
1package payments
2 
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "net/http"
8 "time"
9)
10 
11type Charge struct {
12 ID string `json:"id"`
13 Status string `json:"status"`
14 Amount int64 `json:"amount"`
15}
16 
17func (c *Client) FetchCharge(ctx context.Context, id string) (*Charge, error) {
18 ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
19 defer cancel()
20 
21 url := fmt.Sprintf("%s/v1/charges/%s", c.baseURL, id)
22 req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
23 if err != nil {
24 return nil, fmt.Errorf("build request: %w", err)
25 }
26 req.Header.Set("Authorization", "Bearer "+c.apiKey)
27 
28 resp, err := c.http.Do(req)
29 if err != nil {
30 if ctx.Err() == context.DeadlineExceeded {
31 return nil, fmt.Errorf("fetch charge %s: timed out", id)
32 }
33 return nil, fmt.Errorf("fetch charge %s: %w", id, err)
34 }
35 defer resp.Body.Close()
36 
37 if resp.StatusCode != http.StatusOK {
38 return nil, fmt.Errorf("fetch charge %s: unexpected status %d", id, resp.StatusCode)
39 }
40 
41 var charge Charge
42 if err := json.NewDecoder(resp.Body).Decode(&charge); err != nil {
43 return nil, fmt.Errorf("decode charge: %w", err)
44 }
45 return &charge, nil
46}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Deriving a timeout context and deferring cancel bounds the whole request lifecycle in one place.
  2. 2Wrapping errors with %w preserves the underlying cause while adding call-site context.
  3. 3Inspecting ctx.Err() lets you turn a generic transport failure into a clear timeout message.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Fetching a charge over HTTP in Go — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code