Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/hayesgm/hyperbuffs
Seamlessly connect RESTful Phoenix apps with Protobufs
https://github.com/hayesgm/hyperbuffs
Last synced: 9 days ago
JSON representation
Seamlessly connect RESTful Phoenix apps with Protobufs
- Host: GitHub
- URL: https://github.com/hayesgm/hyperbuffs
- Owner: hayesgm
- Created: 2017-01-22T04:21:02.000Z (almost 8 years ago)
- Default Branch: master
- Last Pushed: 2019-04-10T23:46:04.000Z (over 5 years ago)
- Last Synced: 2024-04-15T14:01:50.365Z (7 months ago)
- Language: Elixir
- Homepage:
- Size: 40 KB
- Stars: 13
- Watchers: 3
- Forks: 3
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
Awesome Lists containing this project
README
# Hyperbuffs
Hyperbuffs is an Elixir library which strongly connects Phoenix to Protobuf definitions. Based on content negotiation from incoming requests, your controllers will seamlessly accept and respond in either JSON or Protobuf (you can even accept one and return another). The goal is that your controller definitions are strongly typed and you give clients the option of how the data is encoded.
To use Hyperbuffs, you will define your services with a desired RPC schema and connect those to your routes.
```protobuf
service ExampleService {
rpc ping (Ping) returns (Pong) {
option (google.api.http) = { post: "/ping" };
}rpc status (StatusRequest) returns (StatusResponse) {
option (google.api.http) = { get: "/status" };
}
}
``````elixir
service ExampleService, ExampleController
```and your controllers will speak Protobuf:
```elixir
defmodule ExampleController do# Our actions now take a protobuf and return a protobuf
@spec create(%Plug.Conn{}, %Defs.Ping{}) :: %Defs.Pong
def create(_conn, %Defs.Ping{payload: payload}) do
Defs.Pong.new(payload: payload)
end
end
```## Installation
If [available in Hex](https://hex.pm/docs/publish), the package can be installed as:
1. Add `hyperbuffs` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[{:hyperbuffs, "~> 0.2.3"}]
end
```2. Install `protoc-gen-elixir` from `protobuf-ex`:
```bash
cd ~
git clone https://github.com/hayesgm/protobuf-ex.git
cd protobuf-ex
mix escript.install
```3. Add the following to your controllers, views and router:
`lib/my_app.ex`
```elixir
defmodule MyApp do
# ...
def controller do
quote do
use Phoenix.Controller, namespace: MyApp
use Hyperbuffs.Controller # <- add this
# ...
end
enddef view do
quote do
use Phoenix.View, root: "lib/my_app/templates",
namespace: MyApp
use Hyperbuffs.View # <- add this
# ...
end
enddef router do
quote do
use Phoenix.Router
use Hyperbuffs.Router # <- add this
# ...
end
end
end
```*or*, add Hyperbuffs to each controller, view and router:
`page_controller.ex`
```elixir
def MyApp.PageController do
use MyApp, :controller
use Hyperbuffs.Controllerend
````page_view.ex`
```elixir
def MyApp.PageView do
use MyApp, :view
use Hyperbuffs.Viewend
````router.ex`
```elixir
defmodule API.Router do
use API, :router
use Hyperbuffs.Routerend
```4. Add `protobufs` mime type to your config:
`mix.exs`
defp deps do
# ...
{:mime, "~> 1.1"}
end`config.exs`
```elixir
config :mime, :types, %{
"application/x-protobuf" => ["proto"]
}
```5. After adding that, you'll need to recompile `mime`:
```bash
mix deps.clean mime --build
mix deps.get
```## Getting Started
To use Hyperbuffs, you'll need to define some protobufs, add the service definitions to your routes, and then build your controller actions to take and return protobufs. The following walks through an example of this.
1. Add your protobuf definitions, e.g.:
`priv/proto/services.proto`
```elixir
syntax = "proto3";import "annotations.proto";
package MyApp;
service PingService {
rpc ping (Ping) returns (Pong) {
option (google.api.http) = { get: "/ping" };
}
}message Ping {}
message Pong {
uint32 timestamp = 1;
}
```2. Generate your protobuf definitions (replace `my_app` with your app or package name)
```bash
mkdir ./lib/my_app/proto
``````bash
protoc --proto_path="./priv/proto" --proto_path="./deps/hyperbuffs/priv/proto" --elixir_out="./lib/my_app/proto" ./priv/proto/**
```Note: for an umbrella app, this would be:
```bash
protoc --proto_path="./priv/proto" --proto_path="../../deps/hyperbuffs/priv/proto" --elixir_out="./lib/my_app/proto" ./priv/proto/**
```2. Add proto config to your desired routes:
```elixir
defmodule MyApp.Router do
use MyApp, :router# Add this section if you want to allow protobuf inputs and responses
pipeline :api do
plug Plug.Parsers, parsers: [Plug.Parsers.Protobuf] # allows Protobuf input
plug :accepts, ["json", "proto"] # allows for Protobuf response
endscope "/" do
pipe_through :apiservice MyApp.PingService, StatusController
end
end
```3. Build your actions in your controller:
```elixir
defmodule MyApp.StatusController do
use MyApp, :controller@doc """
Responds Pong to Ping.## Examples
iex> MyApp.StatusController.ping(%Plug.Conn{}, %MyApp.Ping{})
%MyApp.Pong{timestamp: 1508114537}
"""
@spec ping(Plug.Conn.t, %MyApp.Ping{}) :: %MyApp.Pong{}
def ping(_conn, _ping) do
MyApp.Pong.new(timestamp: :os.system_time(:seconds))
end
end
```4. Make sure you have a view for your controller:
```elixir
defmodule MyApp.StatusView do
use MyApp, :viewend
```5. That's all, run your app and view the endpoint.
```bash
$ mix phx.server
$ curl localhost:4000/ping
{"timestamp":1509119708}
```### Actions
Actions in Hyperbuffs try to follow an RPC model where you have a declared input and you return a declared output. Hyperbuffs will ensure that the input and output can be either JSON or Protobufs based on the `Content-Type` and `Accept` headers respectively.
That said, you still have access to `conn` and can render traditionally as well. Here's a few examples:
```elixir
@spec my_action(Plug.Conn.t, %{}) :: Plug.Conn.t | %{} | {Plug.Conn.t, %{}}
def my_action(conn, req=%Defs.SomeReq{}) do
# Return just a protobuf
Defs.SomeResp.new(msg: "Hi #{req.name}")
enddef my_action(conn, req=%Defs.SomeReq{}) do
# Return a conn and a protobuf to be rendered
{
conn |> put_resp_header("X-Req-Id", 5)
Defs.SomeResp.new(msg: "Hi #{req.name}")
}
enddef my_action(conn, req=%Defs.SomeReq{}) do
# Return just a conn
conn
|> Hyperbuffs.View.render_proto Defs.SomeResp.new(msg: "Hi #{req.name}")
end
```## Contributing
* For bugs, please open an issue with steps to reproduce.
* For smaller feature requests, please either create an issue, or fork and create a PR.
* For larger feature requests, please create an issue before starting work so we can discuss the design decisions.