api-transformer
NOTE: this is currently a work in progress
A Ruby web server DSL for exposing one or more back-end services through a different API.
Installation
Add this line to your application's Gemfile:
gem "api-transformer"
And then execute:
$ bundle
Or install it yourself as:
$ gem install api-transformer
Usage
Quick Start
Create a file ip_server.rb:
require 'api_transformer'
class IpServer < ApiTransformer::Server
base_url "http://ip.jsontest.com/"
get "/ip" do
request :ip do
path "/"
method :get
end
response do |data|
success do
status 200
attribute :your_ip, data[:ip][:ip]
end
end
end
end
Run it:
$ ruby ip_server.rb -sv
Try it:
$ curl -i http://localhost:9000/ip
HTTP/1.1 200 OK
Server: Goliath
Date: Tue, 30 Sep 2014 23:19:34 GMT
{"your_ip":"74.125.228.96"}
Let's break this down:
base_url "http://ip.jsontest.com/"
Declare the base URL for back-end requests, which will each provide a path under this.
get "/ip" do |params|
This defines a single endpoint that responds to GET requests at the path '/ip'.
request :ip do
path "/"
method :get
end
It makes a single GET request to the root path of the base_url (http://ip.jsontest.com/).
response do |data|
success do
status 200
attribute :your_ip, data[:ip][:ip]
end
end
Upon completion of the request to ip.jsontest.com with a 2XX response, it responds with a status of 200 and a JSON attribute of your_ip.
Endpoints without any requests
Back-end requests are not required.
get "/ping" do
response do
success { attribute :result, "pong" }
end
end
Accepting parameters
Query params:
get "/some_params" do
request :greeting do
path "/greeting"
method :get
query_param :greeting, "hello"
query_param :name, "Bob"
end
response do
success { attribute :result, "hello Bob" }
end
end
Form params:
post "/some_params" do
request :greeting do
path "/greeting"
method :post
form_param :greeting, "hello"
form_param :name, "Bob"
end
response do
success { attribute :result, "hello Bob" }
end
end
JSON params:
post "/some_params" do
request :greeting do
path "/greeting"
method :post
json_param :greeting, "hello"
json_param :name, "Bob"
end
response do
success { attribute :result, "hello Bob" }
end
end
JSON params are accepted as a JSON-encoded request body. In this case what would get sent to the back-end endpoint is "greeting":"hello","name":"Bob".
Form and JSON params can't both be used on the same request.
HTTP verbs
GET, POST, PUT and DELETE are provided:
delete "/ping" do
response do
success { attribute :result, "I should probably delete something" }
end
end
Pass-through of headers
get "/user_agent" do |_, headers|
response do
success { attribute :user_agent, headers["User-Agent"] }
end
end
JSON objects and arrays
Classes can be used to parse data and return JSON objects:
class Bob
def initialize(last_name)
@last_name = last_name
end
def to_hash
{ first_name: "Bob", last_name: @last_name }
end
end
get "/object" do
response do
success { object :bob, Bob, "Saget" }
end
end
# => {"bob":{"first_name":"Bob","last_name":"Saget"}}
These can also be used to return arrays of data:
get "/array" do
response do
success { array :bobs, Bob, %w(Ross Marley) }
end
end
# => {"bobs":[{"first_name":"Bob","last_name":"Ross"},{"first_name":"Bob","last_name":"Marley"}]}
Use request data on subsequent requests
This is perhaps easiest to explain with an example:
get "/multi" do
request :one do
path "/one"
method :get
end
request :two do |data|
path "/two"
method :get
query_param :name, data[:one][:name]
end
response do |data|
success { attribute :name, data[:two][:result] }
end
end
get "/one" do
response do
success { attribute :name, "Bob" }
end
end
get "/two" do |params|
response do
success { attribute :result, params[:name] }
end
end
Notably, on this line:
request :two do |data|
data will contain the response data from the first request.
Conditional requests
It can be useful to only send back-end requests under certain conditions:
get "/conditional" do |params|
request :start_background_job, when: params[:make_it_so] do
path "/job"
method :post
end
response do
success { attribute :result, "OK" }
end
end
Streaming
It can be done without a back-end request:
get "/stream_no_backend" do
stream true
response do
success do
stream do
(1..9).each { |n| stream_write n }
end
end
end
end
Things to notice:
- Inside the endpoint block,
stream trueis needed - Inside the success block, there's a
streamblock - The
streamblock usesstream_writeto write the response
This gets more useful when proxying to a back-end request that is going to return a lot of data:
get "/stream_download" do
stream true
request :download do
path "/huge_download"
method :get
end
response do |data|
success do
stream do
data[:one].stream do |chunk|
stream_write chunk
end
end
end
end
end
The stream do |chunk| block will receive the data in small chunks as it comes off the socket, so that the entire response body doesn't go into memory.
Testing
$ rake
Contributing
- Fork it ( https://github.com/CXInc/api-transformer/fork )
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am "Add some feature") - Push to the branch (
git push origin my-new-feature) - Create a new Pull Request