Skip to content

Go cookbook

Web requests

Boilerplate and timesavers.

package main

import (
    "net/http"
    "time"
)

type Client struct {
    httpClient http.Client
}

// build a re-usable client with some config e.g. timeout
func NewClient() Client {
    return Client{
        httpClient: http.Client{
            Timeout: time.Minute,
        },
    }
}

Structs

Nested structs

Structs can be nested to represent more complex entities.

type car struct {
  Make string
  Model string
  Height int
  Width int
  FrontWheel Wheel
  BackWheel Wheel
}

type Wheel struct {
  Radius int
  Material string
}

The fields of a struct can be accessed using the dot . operator.

myCar := car{}
myCar.FrontWheel.Radius = 5

Embedded structs

type car struct {
  make string
  model string
}

type truck struct {
  // "car" is embedded, so the definition of a
  // "truck" now also additionally contains all
  // of the fields of the car struct
  car
  bedSize int
}

An embedded struct's fields are accessed at the top level, unlike nested structs. Promoted fields can be accessed like normal fields except that they can't be used in composite literals.

lanesTruck := truck{
  bedSize: 10,
  car: car{
    make: "toyota",
    model: "camry",
  },
}

fmt.Println(lanesTruck.bedSize)

// embedded fields promoted to the top-level
// instead of lanesTruck.car.make
fmt.Println(lanesTruck.make)
fmt.Println(lanesTruck.model)

Working with JSON

The WebhookPayload structure in the Go code represents the JSON payload that your webhook expects to receive. Let's break down the nested structure to understand how it correlates with your JSON format:

  1. Overall Structure: WebhookPayload is designed to match the top-level structure of your JSON payload. It contains two fields, Component and Parameters, each corresponding to a key in your JSON.

  2. The Component Field:

  3. This is a nested structure within WebhookPayload.

  4. It corresponds to the "component" key in your JSON.
  5. It is defined as a struct Component, which has fields ID, Name, and Repository.
  6. These fields are meant to directly map to the nested JSON object under the "component" key.

  7. The Parameters Field:

  8. This represents the "parameters" key in your JSON.

  9. It's defined as a map[string]string.
  10. This is a flexible structure where keys and values are both strings.
  11. It's used here because your parameters appear to be a collection of key-value pairs where both the keys and the values are strings.

  12. JSON Tags:

  13. Notice the json:"..." tags in the struct definitions. These tags tell the Go json package how to map the JSON keys to struct fields.
  14. For example, ID string json:"id" in the Component struct means that the ID field in Go is mapped to the "id" key in your JSON payload.

Here's a visual breakdown:

Your JSON Payload:

{
  "component": {
    "id": "...",
    "name": "...",
    "repository": "..."
  },
  "parameters": {
    "key1": "value1",
    "key2": "value2"
    // ... more key-value pairs ...
  }
}

Mapped to Go Structs:

type WebhookPayload struct {
    Component  Component  `json:"component"`  // Maps to "component" in JSON
    Parameters Parameters `json:"parameters"` // Maps to "parameters" in JSON
}

type Component struct {
    ID         string `json:"id"`          // Maps to "id" in component
    Name       string `json:"name"`        // Maps to "name" in component
    Repository string `json:"repository"`  // Maps to "repository" in component
}

type Parameters map[string]string // Maps to key-value pairs in "parameters"

When a JSON payload is received, Go's json.Unmarshal function uses these definitions to correctly parse the JSON into a WebhookPayload object, making the data structured and easy to work with in your code.

Example

Handling a webhook of a specific shape. e.g.

{
  "component": {
    "id": "...",
    "name": "...",
    "repository": "..."
  },
  "parameters": {
    "key1": "value1",
    "key2": "value2"
    // ... more key-value pairs ...
  }
}
package main

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
)

// Define structs to match the JSON structure
type Component struct {
    ID         string `json:"id"`
    Name       string `json:"name"`
    Repository string `json:"repository"`
}

type Parameters map[string]string

type WebhookPayload struct {
    Component  Component  `json:"component"`
    Parameters Parameters `json:"parameters"`
}

func webhookHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != "POST" {
        http.Error(w, "Only POST method is accepted", http.StatusMethodNotAllowed)
        return
    }

    body, err := ioutil.ReadAll(r.Body)
    if err != nil {
        http.Error(w, "Error reading request body", http.StatusInternalServerError)
        return
    }
    defer r.Body.Close()

    var payload WebhookPayload
    if err := json.Unmarshal(body, &payload); err != nil {
        http.Error(w, "Error decoding JSON", http.StatusBadRequest)
        return
    }

    // Process the payload here
    fmt.Printf("Received component: %+v\n", payload.Component)
    fmt.Printf("Received parameters: %+v\n", payload.Parameters)

    // Respond to the webhook sender
    fmt.Fprintf(w, "Webhook received and processed")
}

func main() {
    http.HandleFunc("/webhook", webhookHandler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}


Send tokens to a channel

Waiting for databases to come online for example.

func getDatabasesChannel(numDBs int) chan struct{} {
    ch := make(chan struct{})
    go func() {
        for i := 0; i < numDBs; i++ {
            ch <- struct{}{} // send an empty struct, used as a "token"
            fmt.Printf("Database %v is online\n", i+1)
        }
    }()
    return ch
}

Send an empty struct, struct{}{}, to a channel to be used as a "token".

Then block and wait for a specific number of tokens in another function.

    for i := 0; i < numDBs; i++ {
        <-dbChan // unary operator pops off empty struct token from queue
    }
}