Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Josh Hull committed Jan 16, 2011
0 parents commit 987ca2f
Show file tree
Hide file tree
Showing 9 changed files with 230 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -0,0 +1 @@
.bundle
10 changes: 10 additions & 0 deletions Gemfile
@@ -0,0 +1,10 @@
source :rubygems

gem 'callsite'
gem 'method-args'
gem 'thin'
gem 'http_router'
gem 'rack'
gem 'rake'
gem 'em-http-request'
gem 'minitest', '~> 2.0.0'
45 changes: 45 additions & 0 deletions Gemfile.lock
@@ -0,0 +1,45 @@
GEM
remote: http://rubygems.org/
specs:
addressable (2.2.2)
callsite (0.0.4)
daemons (1.1.0)
em-http-request (0.2.12)
addressable (>= 2.0.0)
eventmachine (>= 0.12.9)
eventmachine (0.12.10)
http_router (0.5.0)
rack (>= 1.0.0)
url_mount (~> 0.2.1)
method-args (0.1.0)
ruby2ruby (~> 1.2.4)
ruby_parser (~> 2.0)
sexp_processor (~> 3.0.4)
minitest (2.0.2)
rack (1.2.1)
rake (0.8.7)
ruby2ruby (1.2.5)
ruby_parser (~> 2.0)
sexp_processor (~> 3.0)
ruby_parser (2.0.5)
sexp_processor (~> 3.0)
sexp_processor (3.0.5)
thin (1.2.7)
daemons (>= 1.0.9)
eventmachine (>= 0.12.6)
rack (>= 1.0.0)
url_mount (0.2.1)
rack

PLATFORMS
ruby

DEPENDENCIES
callsite
em-http-request
http_router
method-args
minitest (~> 2.0.0)
rack
rake
thin
34 changes: 34 additions & 0 deletions README.markdown
@@ -0,0 +1,34 @@
# Apiary

Stand up simple APIs for consumption.

## Usage

Apiary let's you use any existing class and turn it into an API. For instance, say you have a class like this.

class Temperature
def c2f(val)
Float(val) * 9 / 5 + 32
end
end

You can convert this to an API by annotating this class with three lines.

class Temperature
include Apiary

version '1.0'

get
def c2f(val)
Float(val) * 9 / 5 + 32
end
end

Now, your API is complete! You can run this with `Temperature.run`. This will create a server on port 3000. You can hit it with

curl http://localhost:3000/1.0/c2f/23.45

And you'll get back

74.21
20 changes: 20 additions & 0 deletions Rakefile
@@ -0,0 +1,20 @@
require 'rubygems'
require 'bundler'

desc "Run tests"
task :test do
$: << 'lib'
require 'apiary'
require 'test/helper'
Dir['test/**/test_*.rb'].each { |test| require test }
end

require 'rake/rdoctask'
desc "Generate documentation"
Rake::RDocTask.new do |rd|
rd.main = "README.rdoc"
rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
rd.rdoc_dir = 'rdoc'
end

#Bundler::GemHelper.install_tasks
67 changes: 67 additions & 0 deletions lib/apiary.rb
@@ -0,0 +1,67 @@
require 'method_args'
require 'callsite'
require 'http_router'
require 'thin'
require 'rack'

module Apiary
ApiMethod = Struct.new(:method, :http_method, :path)

module ClassMethods

def get(path = nil)
__set_routing(:get, path)
end

def post(path = nil)
__set_routing(:get, path)
end

def version(number)
@version = number
end

def __set_routing(method, path)
@method, @path = method, path
end

def method_added(m)
MethodArgs.register(Callsite.parse(caller.first).filename)
@cmds ||= []
@cmds << ApiMethod.new(m, @http_method, @path)
end

def default_path(m)
instance_method(m).args.inject(m.to_s) { |path, arg|
path << case arg.type
when :required
"/:#{arg.name}"
when :optional
"/(:#{arg.name})"
when :splat
"/*#{arg.name}"
end
}
end

def run(port = 3000)
raise "No version specified" unless @version
router = HttpRouter.new
@cmds.each do |cmd|
path = "#{@version}/#{cmd.path || default_path(cmd.method)}".squeeze('/')
route = router.add(path)
route.send(cmd.http_method) if cmd.http_method
route.to { |env|
Rack::Response.new(new.send(cmd.method, *env['router.response'].param_values).to_s).finish
}
end
Thin::Server.new('0.0.0.0', port) {
run router
}.start
end
end

def self.included(cls)
cls.instance_eval "class << self; include ClassMethods; end"
end
end
10 changes: 10 additions & 0 deletions test/fixtures/basic.rb
@@ -0,0 +1,10 @@
class Basic
include Apiary

version '1.0'

get
def ping
'ping'
end
end
23 changes: 23 additions & 0 deletions test/helper.rb
@@ -0,0 +1,23 @@
require 'minitest/autorun'
require 'em-http'
require 'test/fixtures/basic'

class MiniTest::Unit::TestCase
def run_with(cls, &blk)
EM.run do
cls.run
EM.add_timer(0.5) { EM.stop }
EM.next_tick(&blk)
end
end

def request(path, method = :get, opts = {}, &blk)
raise "you need to be running inside a request" unless EM.reactor_running?
http = EventMachine::HttpRequest.new("http://127.0.0.1:3000#{path}").__send__(method, opts)
http.callback { EM.next_tick { blk.call(http) } }
end

def done
EM.stop
end
end
20 changes: 20 additions & 0 deletions test/test_basic.rb
@@ -0,0 +1,20 @@
class TestBasic < MiniTest::Unit::TestCase
def test_simple
run_with(Basic) do
request('/1.0/ping') do |http|
assert_equal 200, http.response_header.status
assert_equal 'ping', http.response
done
end
end
end

def test_not_found
run_with(Basic) do
request('/1.0/something_else') do |http|
assert_equal 404, http.response_header.status
done
end
end
end
end

0 comments on commit 987ca2f

Please sign in to comment.