Permalink
Browse files

initial commit

  • Loading branch information...
0 parents commit 987ca2fedc6416c3f7c1614972b759d65a98ff1e Josh Hull committed Jan 16, 2011
Showing with 230 additions and 0 deletions.
  1. +1 −0 .gitignore
  2. +10 −0 Gemfile
  3. +45 −0 Gemfile.lock
  4. +34 −0 README.markdown
  5. +20 −0 Rakefile
  6. +67 −0 lib/apiary.rb
  7. +10 −0 test/fixtures/basic.rb
  8. +23 −0 test/helper.rb
  9. +20 −0 test/test_basic.rb
@@ -0,0 +1 @@
+.bundle
10 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'
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,10 @@
+class Basic
+ include Apiary
+
+ version '1.0'
+
+ get
+ def ping
+ 'ping'
+ end
+end
@@ -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
@@ -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.