lib/twirp.ex

defmodule Twirp do
  @moduledoc """
  Twirp provides an elixir implementation of the [twirp rpc framework](https://github.com/twitchtv/twirp)
  developed by Twitch. The protocol defines semantics for routing and
  serialization of RPCs based on protobufs.

  ## Example

  The canonical Twirp example is a Haberdasher service. Here's the protobuf
  description for the service.

  ```protobuf
  syntax = "proto3";

  package example;

  // Haberdasher service makes hats for clients.
  service Haberdasher {
    // MakeHat produces a hat of mysterious, randomly-selected color!
    rpc MakeHat(Size) returns (Hat);
  }

  // Size of a Hat, in inches.
  message Size {
    int32 inches = 1; // must be > 0
  }

  // A Hat is a piece of headwear made by a Haberdasher.
  message Hat {
    int32 inches = 1;
    string color = 2; // anything but "invisible"
    string name = 3; // i.e. "bowler"
  }
  ```

  We'll assume for now that this proto file lives in `priv/protos/service.proto`

  ### Code generation

  We can now use `protoc` to generate the files we need. You can run this command
  from the root directory of your project.

      $ protoc --proto_path=./priv/protos --elixir_out=./lib/example --twirp_elixir_out=./lib/example ./priv/protos/service.proto

  After running this command there should be 2 files located in `lib/example`.

  The message definitions:

  ```elixir
  defmodule Example.Size do
    @moduledoc false
    use Protobuf, syntax: :proto3

    @type t :: %__MODULE__{
            inches: integer
          }

    defstruct [:inches]

    field :inches, 1, type: :int32
  end

  defmodule Example.Hat do
    @moduledoc false
    use Protobuf, syntax: :proto3

    @type t :: %__MODULE__{
            inches: integer,
            color: String.t(),
            name: String.t()
          }
    defstruct [:inches, :color, :name]

    field :inches, 1, type: :int32
    field :color, 2, type: :string
    field :name, 3, type: :string
  end
  ```

  The service and client definition:

  ```elixir
  defmodule Example.HaberdasherService do
    @moduledoc false
    use Twirp.Service

    package "example"
    service "Haberdasher"

    rpc :MakeHat, Example.Size, Example.Hat, :make_hat
  end

  defmodule Example.HaberdasherClient do
    @moduledoc false
    use Twirp.Client, service: Example.HaberdasherService
  end
  ```

  ### Implementing the server

  Now that we've generated the service definition we can implement a "handler"
  module that will implement each "method".

  ```elixir
  defmodule Example.HaberdasherHandler do
    @colors ~w|white black brown red blue|
    @names ["bowler", "baseball cap", "top hat", "derby"]

    def make_hat(_ctx, size) do
      if size <= 0 do
        Twirp.Error.invalid_argument("I can't make a hat that small!")
      else
        %Haberdasher.Hat{
          inches: size.inches,
          color: Enum.random(@colors),
          name: Enum.random(@names)
        }
      end
    end
  end
  ```

  Separating the service and handler like this may seem a little odd but there are
  good reasons to do this. The most important is that it allows the service to be
  autogenerated again in the future. The second reason is that it allows us to
  easily mock service implementations for testing.

  ### Running the server

  To serve traffic Twirp provides a Plug. We use this plug to attach our service
  definition with our handler.

  ```elixir
  defmodule Example.Router do
    use Plug.Router

    plug Twirp.Plug,
      service: Haberdasher.HatMakerService,
      handler: Haberdasher.HatMakerHandler
  end
  ```

  ```elixir
  defmodule Example.Application do
    use Application

    def start(_type, _args) do
      children = [
        Plug.Cowboy.child_spec(scheme: :http, plug: Example.Router, options: [port: 4040]),
      ]

      opts = [strategy: :one_for_one, name: Example.Supervisor]
      Supervisor.start_link(children, opts)
    end
  end
  ```

  If you start your application your plug will now be available on port 4040.

  ### Using the client

  Client definitions are generated alongside the service definition. This allows
  you to generate clients for your services in other applications.  You can make
  RPC calls like so:

  ```elixir
  defmodule AnotherService.GetHats do
    alias Example.HaberdasherClient, as: Client
    alias Example.{Size, Hat}

    def make_a_hat(inches) do
      case Client.make_hat(Size.new(inches: inches)) do
        {:ok, %Hat{}=hat} ->
          hat

        {:error, %Twirp.Error{msg: msg}} ->
          Logger.error(msg)
      end
    end
  end
  ```
  """
end