Handling routes from an API in a type-safe way with Swift

I’m working on an application that has to handle some kind of routing directions coming from an API in the following form:

{
    "title": "Tap to go to your profile!",
    "destination": "profile"
}

These items would show in a form of timeline of events, that when tapped, would take you to specific parts of the application.

I was thinking about how I could leverage Swift’s type-safety to make sure the application validates the destinations for each of these items and make a more robust solution for this.

Immediately, enums came to mind. I can easily define an enumeration with raw values:

enum Destination: String {
    case profile
    case info
    case feed
}

But then, I looked closely at the API’s documentation, and noticed that some destinations could come with attached metadata:

{
    "title": "Tap to go to Oscar's profile!",
    "destination": "profile:859928508374784884"
}

As you can probably guess, tapping on the item above should take you to the profile screen for the user with id 859928508374784884. I thought of enums with associated values, but that wouldn’t cut it. Associated values in enumerations prevent me from inflating instances from raw values.

I decided to come up with my alternative for enumerations to handle this specific case so I could build destination instances in a type-safe manner directly from the JSON objects coming from the API.


First, I started with a structure. Since I’m interested in being able to convert a raw string to an instance of my type and viceversa, RawRepresentable is the protocol to adopt.

struct Destination: RawRepresentable {
    private let _identifier: String
    
    var rawValue: String {
        return _identifier
    }
    
    init?(rawValue: String) {
        _identifier = rawValue
    }
}

Now, I need to check whether the raw value passed to the initializer is a valid one. For that, I defined a string set that contained all the possible identifiers for a Destination, and check for that on the failable initializer from RawRepresentable.

struct Destination: RawRepresentable {
    // ...
    
    private static let options: Set<String> = [
        "info",
        "profile",
        "feed",
        "event"
    ]
    
    init?(rawValue: String) {
        guard Destination.options.contains(rawValue) else {
            return nil
        }
        
        _identifier = rawValue
    }
}

The only thing missing right now would be handling the attached metadata for the available destinations.

For that, I added a value property that would hold the destination’s metadata (if any), and a couple more checks to the the initializer:

struct Destination: RawRepresentable {
    // ...
    var value: String?
    
    public var rawValue: String {
        return _identifier + (value != nil ? ":(value!)" : "")
    }
    
    init?(rawValue: String) {
        // Split the raw value
        let ids = rawValue.split(separator: ":")
            
        // Check that the identifier exists
        guard let identifier = ids.first else {
            return nil
        }
            
        // Convert from String.SubSequence
        let id = String(identifier)
        
        // Make sure this identifier is valid
        guard Destination.options.contains(id) else {
            return nil
        }
            
        _identifier = id
        
        // If metadata exists, add that metadata to value
        if ids.count == 2 {
            value = String(ids[1])
        }
    }
}

Now, conform with Codable so that Destination can be parsed from and to raw data:

struct Destination: RawRepresentable {
    // ...
    enum DestinationError: Error {
        case unsupportedDestination
        case wrongFormat
    }
    
    enum CodingKeys: String, CodingKey {
        case _identifier = "destination"
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        
        try container.encode(_identifier, forKey: ._identifier)
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let rawValue = try container.decode(String.self, forKey: ._identifier)
        let ids = rawValue.split(separator: ":")
        
        guard let identifier = ids.first else {
            throw DestinationError.wrongFormat
        }
        
        let id = String(identifier)
        
        guard Destination.options.contains(id) else {
            throw DestinationError.unsupportedDestination
        }
        
        _identifier = id
        
        if ids.count == 2 {
            value = String(ids[1])
        }
    }
}

Unfortunately, I coulnd’t find an elegant way to extract the initialization checks to another method (because scope initialization, etc), so the initializer from RawRepresentable has to be basically duplicated on the one from Decodable.

One last step would be making Destination conform with Hashable:

struct Destination: RawRepresentable, Codable, Hashable {
    // ...
    var hashValue: Int {
        return _identifier.hashValue
    }
}

func ==(lhs: Destination, rhs: Destination) -> Bool {
    return lhs.hashValue == rhs.hashValue
}

Note that hashValue takes only into account the identifier for the destination, not it’s attached value. That way, profile:859928508374784884 and a profile are considered equal, even though the first one has attached metadata.

This is super specific in this case, because I only care about the route and the metadata is treated as optional. However, in your own implementation, taking value into consideration for Hashable might make sense.

And that’s it. A quick hack would be to add static properties to Destination with prebuilt values so that they can be treated as enumeration cases:

struct Destination: RawRepresentable {
    static let info = Destination(rawValue: "info")!
    static let profile = Destination(rawValue: "profile")!
    static let feed = Destination(rawValue: "feed")!
    static let event = Destination(rawValue: "event")!
    
    static func profile(value: String) -> Destination? {
        return Destination.rawValue("profile:(value)")
    }
}

Here’s how all that comes together:

let json = ["destination": "profile:859928508374784884"]
let jsonData = try! JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)

let decoder = JSONDecoder()
let parsedDestination = try? decoder.decode(Destination.self, from: jsonData)

let simpleDestination = Destination.profile
let profileDestination = Destination.profile(value: "859928508374784884")!

parsedDestination == profileDestination // #=> true

And of course, you can switch on Destination values as well:

switch parsedDestination! {
case .profile: 
    router.navigateToProfile(parsedDestination)
    
default:
    // Handle other cases.
}

I thought this could make for a nice example on how to leverage Swift’s flexibility to help when the default data structures are not compatible with our use cases.

If you have any comments or idea, or I missed something, feel free to ping me on Twitter @Swanros.

Thanks for reading!

action_fallback and contexts in Phoenix 1.3 made my controllers tiny!

Yesterday I posted about how Phoenix 1.3 was pure love for API development. In that post, I mentioned that one of my favorites features in this new release is the action_fallback plug.

Today, I want to talk a little bit more about a killer combo I’ve uncovered: action_fallback + contexts.

I’m still amazed at how Phoenix 1.3 enables my controllers to be so tiny.



# Background
In my application, I have a `User` module that’s managed through an `Accounts` context:

def update(conn, %{"id" => id, "user" => user_params}) do
  user = Accounts.get_user(id)

  with {:ok, %User{} = user} <- Accounts.update_user(user, user_params), 
  do: render(conn, "show.json", user: user)
end

The default MyApp.Web.FallbackController that’s generated when you first create your app includes, amongst others, a call/2 definition that accepts an %Ecto.Changeset{} instance:

defmodule MyApp.Web.FallbackController do
  use MyApp.Web, :controller

  def call(conn, {:error, %Ecto.Changeset{} = changeset}) do
    conn
    |> put_status(:unprocessable_entity)
    |> render(MyApp.Web.ChangesetView, "error.json", changeset: changeset)
  end

  # 
end

This function will be called if, for instance, the update/2 method on UserController failed to pattern match the with statement. Repo.update/1 returns {:error, changeset} in case it fails. Since I didn’t define an else path, {:error, changeset} would be then forwarded to MyApp.Web.FallbackController.



# Custom Errors
I like to define custom errors for the domains for my app. The default behavior on `MyApp.Web.ChangesetView` will output something like this:

{
  errors: [
    "username has already been taken"
  ]
}

I modified MyApp.Web.ChangesetView to expose the error the way I wanted:

defmodule MyApp.Web.ChangesetView do
  use MyApp.Web, :view
  alias MyApp.ResponseWrapper

  def translate_errors(changeset) do
    case hd(changeset.errors) do
      {label, {"has already been taken", _}} ->
        "#{label}_taken"
      {:password, {"should be at most %{count} character(s)", _}} ->
        "password_too_long"
      {:password, {"should be at least %{count} character(s)", _}} ->
        "password_too_short"
      _ ->
        :something_went_wrong
    end
  end

  def render("error.json", %{changeset: changeset}) do
    ResponseWrapper.error translate_errors(changeset)
  end
end

In the translate_errors/1 function, I take the first error off the changeset, and then pattern match its content. I can add more patterns to match later depending on my needs. Now, MyApp.Web.ChangesetView outputs errors like this:

{
  status: "error",
  error: "username_taken"
}

{
  status: "error",
  error: "password_too_short"
}



# Benefits
What’s great about that, is that now I can basically reuse that same code for any other module that needs to be inserted in my database.

This lets me write code like this:

defmodule MyApp.Web.DeviceController do
  use MyApp.Web, :controller

  alias MyApp.{Authentication, Notifications}

  plug :scrub_params, "device" when action in [:create]
  plug Authentication
  action_fallback MyApp.Web.FallbackController

  def create(conn, %{"device" => device_params}) do
    with {:ok, user} <- Authentication.get_current_user(conn),
         {:ok, _dev} <- Notifications.add_device(device_params, user),
         do: :success
  end
end

My create/2 function in DeviceController is just 3 lines long. In those 3 lines, I:

  1. Get the current user.
  2. Insert a new device (with changeset validations) to the database.
  3. Passes :success to my the action_fallback plug.

The “happy path” response from my fallback looks like this:

{
  status: "success",
  data: {
    status: "success"
  }
}

If any of the guards in the with statement fails, guess what: my fallback controller’s got my back 😬.

For instance, say the Notifications.add_device/2 function fails to insert the new device into the database because it didn’t meet one of the changeset validations, such as the unique_constraint for device_token.

Notifications.add_device would then return {:error, changeset}, which would be forwarded to my fallback controller, and the JSON error would look like this:

{
  status: "error",
  error: "device_token_taken"
}

And I got that for free. 👆

Yay contexts! Yay action_fallback! 🎉

Phoenix 1.3 is pure love for API development

Phoenix 1.3-rc.0 was just released, and while it’s still not a final release, it already feels solid as h*ck.

I’ve been doing a personal project on the side for a while now, and it has a web API component, which I wrote in Phoenix 1.2. That was my first time using Phoenix on a serious project that I intend to ship.

With the release of Phoenix 1.3, and after I watched Chris McCord’s talk about the changes it brings to the table, I decided to rewrite my API from scratch with this new version.

And oh boy, is it great.

Here are my first-impressions.

New Folder Structure

It has been said a lot of times that Phoenix is "Rails for Elixir." With 1.3, the core team sure wants that notion to be as dead as the web/ folder and the model concept.

I’m not going to explain everything that Chris talked about on his keynote at Lonestar Elixir (you should seriously go watch it), but I think it’s worth saying that the change in the folder structure of a Phoenix project has bigger implications that just files moving from one place to another.

Now, there are concrete boundaries between your actual Elixir application, and the interface that Phoenix provides for it to communicate with the web.

Responsibility separation by design. I love this.

Thinking Ahead

Long gone are the mix phoenix.* commands. Say hi to mix phx.*. Less things to type. Love it.

Also, the generators will make you think in advance of what you want to do before you actually do it.

For instance, in Phoenix 1.2 you could do something like this:

$ mix phoenix.gen.json User users email:string

which then would generate the right migrations, and the model:

defmodule MyApp.User do
    use MyApp.Web, :model

    schema "users" do
        field :email, :string
    end
end

along with a (web) controller that would handle the CRUD tasks, with the logic for validation within each action. Over time, using this structure, is easy to end up with controller actions over hundred lines long.

Now, with Phoenix 1.3, you need to specify a context for each resource you create (don’t call them "models" anymore 🙄):

$ mix phx.gen.json Accounts User users email:string

In the command above, Accounts is the context module that’s going to be also generated, and is through this context that we’ll interact with our User module:

Accounts.get_user(2) #=> %User{email: oscar@swanros.com}
Accounts.create_user(params) #=> {:ok, new_user}

This is powerful, because now you can define clear boundaries for your application domains.

Say you have the concept of "contacts" in your application. Each user (User) has contacts (also User instances). This can get messy (as I’ve written before). But using this new notion of "contexts", we can have a ContactRelationship context and handle everything in a clean way:

user
|> ContactRelationships.relationship_with(another_user)
|> ContactRelationships.accept

user
|> ContactRelationships.get_contacts

user
|> ContactRelationships.request_contact(another_user)

This scenario would make our folder structure look something like this:

|- lib/
|---- my_app/
|-------- accounts/
|-------- contact_relationships/
|-------- web/

Defining concrete boundaries between responsibilities within your app makes you come up with better code over time.

action_fallback

So far, the additions to the framework are really nice and welcomed. However, I think my favorite one is the new action_fallback plug.

On Phoenix 1.2 and earlier, every controller needed to return a valid conn for every request. Otherwise, an exception would rise.

This is still true, but with the new action_fallback plug we can delegate that task to another controller whose sole purpose is to handle the cases when other controllers couldn’t provide a "successful" response to the request.

Let me explain… before, you wrote code similar to this in every controller action:

def create(conn, %{"user" => user_params}) do
  user = Repo.get_by(User, phone_number: user_params["phone_number"])

  cond do
    user && checkpw(user_params["password"], user.password_hash) ->
      case create_session(user) do
        {:ok, session} ->
          conn
            |> put_status(:created)
            |> render("show.json", %{session: session, user: user})

        _ ->
          conn
            |> put_status(:unauthorized)
            |> render("error.json")
      end

    true ->
      dummy_checkpw()
      conn
        |> put_status(:unauthorized)
        |> render("error.json")
  end
end

Is not that bad, truly. But this is still better with Phoenix 1.3:

action_fallback MyApp.Web.FallbackController

def create(conn, %{"user" => user_params}) do
  user = Accounts.get_user_by_phone(user_params["phone_number"])

  cond do
    user && checkpw(user_params["password"], user.hashed_password) ->
      with {:ok, %Session{} = session} <- Sessions.create_session(user) do
        conn
        |> put_status(:created)
        |> render("auth_success.json", user: user, session: session)
      end

    true ->
      {:error, :wrong_credentials}
  end
end

With Phoenix 1.3 our controller actions can be simplified to just care about the "happy path", and leave the rest to the fallback controller that we define using the action_fallback plug.

In this case, when the cond evaluates to true we return from the action {:error, :wrong_credentials}. Since this is not a valid connection, our fallback controller comes forward:

defmodule MyApp.Web.FallbackController do
  use MyApp.Web, :controller

  def call(conn, {:error, %Ecto.Changeset{} = changeset}) do
    conn
    |> put_status(:unprocessable_entity)
    |> render(MyApp.Web.ChangesetView, "error.json", changeset: changeset)
  end

  def call(conn, {:error, :unauthorized}) do
    conn
    |> put_status(:unauthorized)
    |> render(MyApp.Web.ErrorView, "auth_required.json")
  end

  def call(conn, {:error, :wrong_credentials}) do
    conn
    |> put_status(:unprocessable_entity)
    |> render(MyApp.Web.ErrorView, "wrong_credentials.json")
  end
end

By pattern matching, the fallback controller knows exactly what to send back as a response to the request.

This also means that we can have a centralized list of error codes that our API can expose to our clients, on our ErrorView module.

No more error definitions scattered around our codebase. ❤️

However, why stop with errors? If your API has "default" responses for actions, you can rely on the fallback controller too to display them:

def delete(conn, _params) do
  with {:ok, session} <- Authentication.get_session(conn),
       {:ok, deleted} <- Sessions.delete_session(session) do
    :success
  else _ ->
    {:error, :unauthorized}
  end
end

:success will be passed to the appropriate action on your fallback controller, and there goes your default response. 🎉

Final thoughts

Phoenix 1.3 is a great update.

Please keep in mind that the defaults 1.3 offers could be done with 1.2, too. But the fact that this is now the default, really speaks of how much attention to detail the Phoenix team is putting in.

They don’t want us to be using "The Phoenix Way™", but rather provide sensible defaults that enable us to create our own solutions in the best possible way.

Some points:

  • The new fallback_controller plug is awesome.
  • The fact that you need to think in terms of boundaries for your app, I think will eventually lead to better, more maintainable codebases.
  • Smarter code generation is 👌.
  • For APIs, the centralized list of available errors is really welcomed.

I love how productive I am with Phoenix when building APIs, and I’m really looking forward to how this already-great framework will evolve past 1.X.


edit: I also wrote about how action_fallback and contexts in Phoenix 1.3 made my controllers tiny.