In Bite #294 we learned about annotations in Sourcery. They're a great way to make extra metadata available on our types when accessing them in Sourcery's .stencil templates.

Today we'll take a look at one of the most powerful features of annotations in Sourcery, Key/Value Annotations.

We'll explore these by building a fictional API client powered by a simple Swift Enum. Let's begin!

First, we'll need to create our enum. Then, we'll define some cases to represent each HTTP endpoint we want to be able to call.

For us, these are a classic set of "get all", "get one", "create", "change", and "delete":

enum SpaceshipsAPI {
    case ships
    case ship(shipID: Int)
    case createShip(shipID: Int, ship: Ship)
    case updateShip(shipID: Int, ship: Ship)
    case deleteShip(shipID: Int)
}

Long-time readers may recognize this enum technique from Bites such as #93 or #150.

Here's where the magic begins though. 🎩

Sourcery Key/Value Annotations are similar to regular Annotations. Instead of a simple tag though, they allow us to annotate types, enums, enum cases, etc with a set of names and a values.

Key/Value Annotations work everywhere that regular Annotations do. We can annotate types, enums, enum cases, and more.

They look like this:

/// sourcery: key = value
/// sourcery: anotherKey = someOtherValue

They can also be listed in a single line:

/// sourcery: key = value, anotherKey = someOtherValue

Values can contain some basic types:

/// sourcery: maxSpeed = 1500, hasHyperdrive = true
/// sourcery: codename = "blacksaber"

Ok, back to our API client. Now that we know we can add simple metadata like this above things in our code, let's use this technique to describe each API endpoint.

enum SpaceshipsAPI {
    /// sourcery: method = "GET", path = "/spaceships"
    /// sourcery: timeout = 5
    case ships
    ...
}

Nice! Normally, we might put things like method and path into essentially a big switch statement switching on self, and returning the correct method or path.

With Sourcery Key/Value Annotations though, we can let Sourcery generate all of these statements for us.

Let's create a SpaceshipsAPI+Properties.stencil template that extends our enum, generating the computed properties:

extension SpaceshipsAPI {
  public var path: String? {
    switch self {
      {% for c in type.SpaceshipsAPI.cases %}
      {% if c.annotations.path %}
      case .{{ c.name }}: return "{{c.annotations.path}}"
      {% endif %}
    {% endfor %}
    default: return nil
    }
  }

  // ... etc
}

Success! Now we can work at a much higher level. We can update our annotations and never need to manually edit tedious switch statements.

We'll run sourcery, which creates/updates SpaceshipsAPI+Properties.generated.swift, filling it with all those beautiful switches.

Finally, we can use our new properties like this:

  print(SpaceshipsAPI.ships.path)

The use cases can begin to boggle the mind a bit here.

We can now add rich metadata to just about anything in our Swift code. Then use it to metaprogram new Swift code that we can use in our project.

Neat!