apis 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .yardoc
6
+ doc
7
+ .DS_Store
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ -m markdown
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in apis.gemspec
4
+ gemspec
5
+
6
+ gem 'rack-test', :require => 'rack/test'
7
+ gem 'yajl-ruby', :require => 'yajl'
data/README.md ADDED
@@ -0,0 +1,43 @@
1
+ ## Usage
2
+
3
+ connection = Apis::Connecton.new(:url => 'http://api.example.com') do
4
+ request do
5
+ use Apis::Request::JSON # |
6
+ use Apis::Request::OAuth2 # |
7
+ use Apis::Request::Logger # \/
8
+ end
9
+ adapter :net_http # -->
10
+ response do
11
+ use Apis::Response::JSON # |
12
+ use Apis::Response::Logger # \/
13
+ end
14
+ end
15
+
16
+ connection.get('/hello')
17
+
18
+ connection.request.replace Apis::Request::OAuth2, Apis::Request::OAuth
19
+
20
+ connection.post('/post_with_oauth_10') do |request|
21
+ request.params = {:q => 'm'}
22
+ end
23
+
24
+ ## Request Middleware
25
+
26
+ ### Example
27
+
28
+ class Request::Middleware
29
+ def call(env)
30
+ env[:params][:token] = 'abcdef'
31
+ end
32
+ end
33
+
34
+ ### Description
35
+
36
+ Basically, request middleware is an object that responds to `#call`.
37
+ It will be initialized with args passed if any.
38
+
39
+ ### Environment
40
+
41
+ * `:method` - HTTP method name
42
+ * `:body` – body of request sent to server
43
+ * `:headers` – headers hash
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'bundler'
2
+ require 'rspec/core/rake_task'
3
+ Bundler::GemHelper.install_tasks
4
+ RSpec::Core::RakeTask.new
data/apis.gemspec ADDED
@@ -0,0 +1,32 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "apis/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "apis"
7
+ s.version = Apis::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Marjan Krekoten' (Мар'ян Крекотень)"]
10
+ s.email = ["m@hmarynka.com"]
11
+ s.homepage = "http://hmarynka.com/labs/apis"
12
+ s.summary = %q{Working bee of API wrapper}
13
+ s.description = %q{Rack-like HTTP client library inspired by Faraday done my way}
14
+
15
+ s.rubyforge_project = "apis"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_development_dependency 'rspec', '>= 2.5'
23
+ s.add_development_dependency 'sinatra'
24
+ s.add_development_dependency 'rack-test'
25
+ s.add_development_dependency 'unicorn'
26
+
27
+ # Middlewares
28
+ s.add_development_dependency 'multi_json'
29
+ s.add_development_dependency 'yajl-ruby'
30
+
31
+ s.add_dependency 'addressable'
32
+ end
data/lib/apis.rb ADDED
@@ -0,0 +1,55 @@
1
+ $: << lib_dir unless $:.include?(lib_dir = File.expand_path('..', __FILE__))
2
+
3
+ module Apis
4
+ class DuplicateMiddleware < StandardError; end
5
+ autoload :Connection, 'apis/connection'
6
+ autoload :ConnectionScope, 'apis/connection_scope'
7
+ autoload :Builder, 'apis/builder'
8
+ autoload :Response, 'apis/response'
9
+
10
+ module Registerable
11
+ def register(symbol, klass)
12
+ @lookup_table ||= {}
13
+ @lookup_table[symbol] = klass
14
+ end
15
+
16
+ def lookup(symbol)
17
+ @lookup_table ||= {}
18
+ self.const_get(@lookup_table[symbol])
19
+ end
20
+ end
21
+
22
+ module Adapter
23
+ autoload :Abstract, 'apis/adapter/abstract'
24
+ autoload :NetHTTP, 'apis/adapter/net_http'
25
+ autoload :RackTest, 'apis/adapter/rack_test'
26
+
27
+ extend Registerable
28
+
29
+ class << self
30
+ # Default connection adapter
31
+ # You can change it by assignin new value
32
+ def default
33
+ @default ||= :net_http
34
+ end
35
+ attr_writer :default
36
+ end
37
+
38
+ register :net_http, :NetHTTP
39
+ register :rack_test, :RackTest
40
+ end
41
+
42
+ module Middleware
43
+ module Request
44
+ extend Registerable
45
+ end
46
+
47
+ module Response
48
+ autoload :Json, 'apis/middleware/response/json'
49
+
50
+ extend Registerable
51
+
52
+ register :json, :Json
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,27 @@
1
+ module Apis
2
+ module Adapter
3
+ class Abstract
4
+ class NotImplemented < StandardError; end
5
+
6
+ attr_accessor :uri
7
+
8
+ def initialize(options = {})
9
+ options.each do |key, value|
10
+ send("#{key}=", value) if respond_to?("#{key}=")
11
+ end
12
+ end
13
+
14
+ # Performs request to resource
15
+ #
16
+ # @param [Symbol, String] method HTTP method to perform
17
+ # @param [String] path Relative path to resource host
18
+ # @param [Hash] params Params to be sent
19
+ # @param [Hash] headers Headers to be sent
20
+ #
21
+ # @return [Array] headers, body
22
+ def run(method, path, params = {}, headers = {})
23
+ raise Apis::Adapter::Abstract::NotImplemented
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,21 @@
1
+ require 'net/http'
2
+
3
+ module Apis
4
+ module Adapter
5
+ class NetHTTP < Abstract
6
+ def connection
7
+ Net::HTTP.start(uri.host, uri.port)
8
+ end
9
+
10
+ def run(method, path, params = {}, headers = {})
11
+ _module = Net::HTTP.const_get(method.to_s.capitalize)
12
+ request = _module.new(path)
13
+ response = connection.request(
14
+ request,
15
+ params.empty? ? nil : Addressable::URI.new.tap { |uri| uri.query_values = params }.query
16
+ )
17
+ [response.code.to_i] + response
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ require 'rack/test'
2
+
3
+ module Apis
4
+ module Adapter
5
+ class RackTest < Abstract
6
+ include Rack::Test::Methods
7
+
8
+ attr_accessor :app
9
+
10
+ attr_reader :last_path, :last_params, :last_headers
11
+
12
+ def run(method, path, params = {}, headers = {})
13
+ @last_path, @last_params, @last_headers = path, params, headers
14
+ send(method, path, params, headers)
15
+ [last_response.status, last_response.headers, last_response.body]
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,76 @@
1
+ module Apis
2
+ class Builder
3
+ attr_accessor :lookup_context
4
+ def initialize(options = {}, &block)
5
+ options.each do |key, value|
6
+ send("#{key}=", value) if respond_to?("#{key}=")
7
+ end
8
+ @stack = []
9
+ @mapping = {}
10
+ block_eval(&block) if block
11
+ end
12
+
13
+ def use(middleware)
14
+ insert(middleware)
15
+ end
16
+
17
+ def replace(old, middleware)
18
+ if index = index(old)
19
+ insert(middleware, index)
20
+ remove(old)
21
+ end
22
+ end
23
+
24
+ def insert(middleware, index = nil)
25
+ middleware = lookup_middleware(middleware)
26
+ raise Apis::DuplicateMiddleware, "#{middleware} already in stack" if include?(middleware)
27
+ index ||= @stack.length
28
+ @stack[index] = lambda do |parent|
29
+ middleware.new(parent)
30
+ end
31
+ @mapping[middleware] = index
32
+ end
33
+
34
+ def remove(middleware)
35
+ middleware = lookup_middleware(middleware)
36
+ @stack.delete_at(@mapping.delete(middleware))
37
+ end
38
+
39
+ def index(middleware)
40
+ middleware = lookup_middleware(middleware)
41
+ @mapping[middleware]
42
+ end
43
+
44
+ def length
45
+ @stack.length
46
+ end
47
+
48
+ def include?(middleware)
49
+ !!index(middleware)
50
+ end
51
+
52
+ def to_a
53
+ @mapping.to_a.sort { |a, b| a.last <=> b.last}.map { |e| e.first }
54
+ end
55
+ alias to_ary to_s
56
+
57
+ def to_app
58
+ unless @stack.empty?
59
+ inner_app = @stack.last.call(nil)
60
+ @stack.reverse[1..-1].inject(inner_app) { |parent, lazy| lazy.call(parent) }
61
+ else
62
+ []
63
+ end
64
+ end
65
+
66
+ def block_eval(&block)
67
+ instance_eval(&block)
68
+ end
69
+
70
+ def lookup_middleware(middleware)
71
+ @lookup_context && !(Class === middleware) ?
72
+ @lookup_context.lookup(middleware) :
73
+ middleware
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,96 @@
1
+ require 'addressable/uri'
2
+
3
+ module Apis
4
+ class Connection
5
+ def initialize(options = {})
6
+ @scope = Apis::ConnectionScope.new
7
+ @scope.headers, @scope.params = {}, {}
8
+
9
+ if String === options
10
+ self.uri = options
11
+ else
12
+ options.each do |key, value|
13
+ send("#{key}=", value) if respond_to?("#{key}=")
14
+ end
15
+ end
16
+
17
+ if block_given?
18
+ block = Proc.new
19
+ instance_eval(&block)
20
+ end
21
+ adapter(Apis::Adapter.default) unless @adapter
22
+ end
23
+
24
+ def uri
25
+ @scope.uri
26
+ end
27
+
28
+ def uri=(value)
29
+ @scope.uri = Addressable::URI.parse(value)
30
+ end
31
+
32
+ def headers
33
+ @scope.headers
34
+ end
35
+
36
+ def headers=(value)
37
+ @scope.headers.merge!(value)
38
+ end
39
+
40
+ def params
41
+ @scope.params
42
+ end
43
+
44
+ def params=(value)
45
+ @scope.params.merge!(value)
46
+ end
47
+
48
+ def request
49
+ block = block_given? ? Proc.new : nil
50
+ @request ||= Apis::Builder.new(:lookup_context => Apis::Middleware::Request, &block)
51
+ @request
52
+ end
53
+
54
+ def response
55
+ block = block_given? ? Proc.new : nil
56
+ @response ||= Apis::Builder.new(:lookup_context => Apis::Middleware::Response, &block)
57
+ @response
58
+ end
59
+
60
+ def adapter(value = nil)
61
+ if Symbol === value
62
+ value = Apis::Adapter.lookup(value)
63
+ end
64
+
65
+ @adapter = value.new(:uri => uri) if value
66
+ @adapter
67
+ end
68
+
69
+ [:get, :head, :post, :put, :delete].each do |method|
70
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
71
+ def #{method}(path = nil, params = {}, headers = {})
72
+ run_request(#{method.inspect}, path, params, headers, &(block_given? ? Proc.new : nil))
73
+ end
74
+ RUBY
75
+ end
76
+
77
+ def run_request(method, path = nil, params = {}, headers = {}, &block)
78
+ #block = block_given? ? Proc.new : nil
79
+ # TODO: refactor this, it's ugly
80
+ @scope.scoped do
81
+ self.params = params if params
82
+ self.headers = headers if headers
83
+ block.call(self) if block
84
+ path ||= uri.path.empty? ? '/' : uri.path
85
+ self.request.to_app.call(
86
+ :method => method,
87
+ :params => self.params,
88
+ :headers => self.headers
89
+ ) unless self.request.to_a.empty?
90
+ res = Apis::Response.new(*adapter.run(method, path, self.params, self.headers))
91
+ self.response.to_app.call(res) unless self.response.to_a.empty?
92
+ res
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,27 @@
1
+ module Apis
2
+ class ConnectionScope
3
+ def initialize
4
+ @data = {}
5
+ end
6
+
7
+ def method_missing(method, value = nil)
8
+ if method =~ /\=$/ && value
9
+ @data[method.to_s.gsub(/\=/, '').to_sym] = value
10
+ else
11
+ @data[method]
12
+ end
13
+ end
14
+
15
+ def scoped
16
+ backup = {}
17
+ @data.each do |key, value|
18
+ backup[key] = value.dup
19
+ end
20
+ yield
21
+ ensure
22
+ backup.each do |key, value|
23
+ @data[key] = value
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,28 @@
1
+ require 'multi_json'
2
+
3
+ module Apis
4
+ module Middleware
5
+ module Response
6
+ class Json
7
+ attr_accessor :app
8
+ def initialize(app = nil)
9
+ @app = app
10
+ end
11
+
12
+ def call(env)
13
+ env.body = case env.body
14
+ when ''
15
+ nil
16
+ when 'true'
17
+ true
18
+ when 'false'
19
+ false
20
+ else
21
+ ::MultiJson.decode(env.body)
22
+ end
23
+ @app.call(env) if @app
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,25 @@
1
+ module Apis
2
+ class Response
3
+ attr_accessor :status, :headers, :body
4
+ def initialize(status, headers, body)
5
+ @status, @headers, @body = status, headers, body
6
+ end
7
+
8
+ def to_env
9
+ {:status => status, :headers => headers, :body => body}
10
+ end
11
+
12
+ def to_a
13
+ [status, headers, body]
14
+ end
15
+ alias to_ary to_a
16
+
17
+ def [](key)
18
+ respond_to?(key) ? send(key) : nil
19
+ end
20
+
21
+ def []=(key, value)
22
+ send("#{key}=", value) if respond_to?("#{key}=")
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ module Apis
2
+ VERSION = "0.4.1"
3
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+ require 'addressable/uri'
3
+
4
+ describe Apis::Adapter::NetHTTP do
5
+ before(:all) do
6
+ start_server
7
+ end
8
+ after(:all) do
9
+ stop_server
10
+ end
11
+
12
+ [:get, :post, :put, :delete, :head].each do |method|
13
+ context method.to_s.upcase do
14
+ it "returns body" do
15
+ adapter = Apis::Adapter::NetHTTP.new(:uri => Addressable::URI.parse(server_host))
16
+ status, headers, body = adapter.run(method, '/')
17
+ body.should == method.to_s.upcase
18
+ end unless method == :head
19
+
20
+ it "returns headers" do
21
+ adapter = Apis::Adapter::NetHTTP.new(:uri => Addressable::URI.parse(server_host))
22
+ status, headers, body = adapter.run(method, '/')
23
+ headers['X-Requested-With-Method'].should == method.to_s.upcase
24
+ end
25
+
26
+ it "returns status" do
27
+ adapter = Apis::Adapter::NetHTTP.new(:uri => Addressable::URI.parse(server_host))
28
+ status, headers, body = adapter.run(method, '/')
29
+ status.should == 200
30
+ end
31
+
32
+ it "sends params" do
33
+ adapter = Apis::Adapter::NetHTTP.new(:uri => Addressable::URI.parse(server_host))
34
+ status, headers, body = adapter.run(method, "/#{method}", {:param => 'value'})
35
+ headers['X-Sent-Params'].should == '{"param"=>"value"}'
36
+ end
37
+ end
38
+ end
39
+
40
+ it 'registered under :net_http' do
41
+ Apis::Connection.new do
42
+ adapter :net_http
43
+ end.adapter.should be_instance_of(Apis::Adapter::NetHTTP)
44
+ end
45
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ class RackApp
4
+ def call(env)
5
+ [
6
+ 200,
7
+ {
8
+ 'X-Request-Method' => env['REQUEST_METHOD'],
9
+ 'X-Request-At' => env['PATH_INFO']
10
+ },
11
+ env['REQUEST_METHOD'] == 'HEAD' ? [] : [env['REQUEST_METHOD']]
12
+ ]
13
+ end
14
+ end
15
+
16
+ describe Apis::Adapter::RackTest do
17
+ [:get, :post, :put, :delete, :head].each do |method|
18
+ context method.to_s.upcase do
19
+ let(:adapter) { Apis::Adapter::RackTest.new(:app => RackApp.new) }
20
+ it "returns body" do
21
+ status, headers, body = adapter.run(method, '/')
22
+ body.should == method.to_s.upcase
23
+ end unless method == :head
24
+
25
+ it "returns headers" do
26
+ status, headers, body = adapter.run(method, '/')
27
+ headers['X-Request-Method'].should == method.to_s.upcase
28
+ end
29
+
30
+ it "returns status" do
31
+ status, headers, body = adapter.run(method, '/')
32
+ status.should == 200
33
+ end
34
+
35
+ it 'saves passed path' do
36
+ adapter.run(method, "/#{method}")
37
+ adapter.last_path.should == "/#{method}"
38
+ end
39
+
40
+ it 'saves passed params' do
41
+ adapter.run(method, '/', {:test => 'param'})
42
+ adapter.last_params.should == {:test => 'param'}
43
+ end
44
+
45
+ it 'saves passed headers' do
46
+ adapter.run(method, '/', {}, {'Content-Type' => 'text'})
47
+ adapter.last_headers.should == {'Content-Type' => 'text'}
48
+ end
49
+ end
50
+ end
51
+
52
+ it 'registered under :rack_test' do
53
+ Apis::Connection.new do
54
+ adapter :rack_test
55
+ end.adapter.should be_instance_of(Apis::Adapter::RackTest)
56
+ end
57
+ end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ describe Apis::Adapter do
4
+ it 'registers adapters shortnames' do
5
+ Apis::Adapter.register(:fake, :FakeAdapter)
6
+ Apis::Adapter.lookup(:fake).should == FakeAdapter
7
+ end
8
+
9
+ specify ':net_http is set as default adapter' do
10
+ Apis::Adapter.default.should == :net_http
11
+ end
12
+ end
@@ -0,0 +1,100 @@
1
+ require 'spec_helper'
2
+
3
+ describe Apis::Builder do
4
+ it 'adds middleware to stack' do
5
+ builder = Apis::Builder.new
6
+ builder.use Middleware
7
+ builder.length.should == 1
8
+ end
9
+
10
+ it 'raises error when duplicate middleware added' do
11
+ builder = Apis::Builder.new
12
+ builder.use Middleware
13
+ expect { builder.use Middleware }.to raise_error(Apis::DuplicateMiddleware, 'Middleware already in stack')
14
+ end
15
+
16
+ it 'shows if middleware in stack' do
17
+ builder = Apis::Builder.new
18
+ builder.use Middleware
19
+ builder.include?(Middleware).should == true
20
+ end
21
+
22
+ it 'replaces middleware with another' do
23
+ builder = Apis::Builder.new
24
+ builder.use Middleware
25
+ builder.use RESTMiddleware
26
+ builder.replace Middleware, NewMiddleware
27
+ builder.include?(Middleware).should == false
28
+ builder.include?(NewMiddleware).should == true
29
+ builder.to_a.should == [NewMiddleware, RESTMiddleware]
30
+ end
31
+
32
+ it 'removes middleware from stack' do
33
+ builder = Apis::Builder.new
34
+ builder.use Middleware
35
+ builder.include?(Middleware).should == true
36
+ builder.remove(Middleware)
37
+ builder.include?(Middleware).should == false
38
+ end
39
+
40
+ it 'returns stacked middlewares in order' do
41
+ builder = Apis::Builder.new
42
+ builder.use Middleware
43
+ builder.use NewMiddleware
44
+ builder.use RESTMiddleware
45
+ app = builder.to_app
46
+ app.should be_instance_of(Middleware)
47
+ app.app.should be_instance_of(NewMiddleware)
48
+ app.app.app.should be_instance_of(RESTMiddleware)
49
+ end
50
+
51
+ it 'evals block' do
52
+ builder = Apis::Builder.new
53
+ builder.block_eval do |builder|
54
+ use Middleware
55
+ use NewMiddleware
56
+ use RESTMiddleware
57
+ end
58
+
59
+ app = builder.to_app
60
+ app.should be_instance_of(Middleware)
61
+ app.app.should be_instance_of(NewMiddleware)
62
+ app.app.app.should be_instance_of(RESTMiddleware)
63
+ end
64
+
65
+ it 'evals block passed to constructor' do
66
+ builder = Apis::Builder.new do
67
+ use Middleware
68
+ use NewMiddleware
69
+ use RESTMiddleware
70
+ end
71
+
72
+ app = builder.to_app
73
+ app.should be_instance_of(Middleware)
74
+ app.app.should be_instance_of(NewMiddleware)
75
+ app.app.app.should be_instance_of(RESTMiddleware)
76
+ end
77
+
78
+ it 'inserts middleware only once' do
79
+ builder = Apis::Builder.new do
80
+ use Middleware
81
+ end
82
+
83
+ app = builder.to_app
84
+ app.should be_instance_of(Middleware)
85
+ app.app.should be_nil
86
+ end
87
+
88
+ it 'lookups middleware by shortcut if lookup object givven' do
89
+ Lookup = Module.new do
90
+ extend Apis::Registerable
91
+ end
92
+ Lookup.register(:middleware, :Middleware)
93
+ builder = Apis::Builder.new(:lookup_context => Lookup) do
94
+ use :middleware
95
+ end
96
+
97
+ app = builder.to_app
98
+ app.should be_instance_of(Middleware)
99
+ end
100
+ end
@@ -0,0 +1,230 @@
1
+ require 'spec_helper'
2
+
3
+ describe Apis::Connection do
4
+ context 'configuration' do
5
+ context 'options' do
6
+ it 'sets url' do
7
+ Apis::Connection.new(:uri => 'http://api.example.org').uri.should == Addressable::URI.parse('http://api.example.org')
8
+ end
9
+
10
+ it 'sets url if it is only parameter' do
11
+ Apis::Connection.new('http://api.example.org').uri.should == Addressable::URI.parse('http://api.example.org')
12
+ end
13
+
14
+ it 'sets headers' do
15
+ Apis::Connection.new(
16
+ :headers => {
17
+ 'Content-Type' => 'text',
18
+ 'User-Agent' => 'apis'
19
+ }
20
+ ).headers.should == {
21
+ 'Content-Type' => 'text',
22
+ 'User-Agent' => 'apis'
23
+ }
24
+ end
25
+
26
+ it 'sets params' do
27
+ Apis::Connection.new(
28
+ :params => {:q => 'apis', :hl => 'uk'}
29
+ ).params.should == {:q => 'apis', :hl => 'uk'}
30
+ end
31
+ end
32
+
33
+ context 'methods' do
34
+ let(:connection) { Apis::Connection.new }
35
+ it 'sets url' do
36
+ connection.uri = 'http://api.example.org'
37
+ connection.uri.should == Addressable::URI.parse('http://api.example.org')
38
+ end
39
+
40
+ it 'sets headers' do
41
+ connection.headers = {
42
+ 'Content-Type' => 'text',
43
+ 'User-Agent' => 'apis'
44
+ }
45
+ connection.headers.should == {
46
+ 'Content-Type' => 'text',
47
+ 'User-Agent' => 'apis'
48
+ }
49
+ end
50
+
51
+ it 'sets params' do
52
+ connection.params = {:q => 'apis', :hl => 'uk'}
53
+ connection.params.should == {:q => 'apis', :hl => 'uk'}
54
+ end
55
+
56
+ it 'updates headers' do
57
+ connection.headers = {'Content-Type' => 'text'}
58
+ connection.headers = {'User-Agent' => 'apis'}
59
+ connection.headers.should == {
60
+ 'Content-Type' => 'text',
61
+ 'User-Agent' => 'apis'
62
+ }
63
+ end
64
+
65
+ it 'updates params' do
66
+ connection.params = {:q => 'apis'}
67
+ connection.params = {:hl => 'uk'}
68
+ connection.params.should == {:q => 'apis', :hl => 'uk'}
69
+ end
70
+ end
71
+
72
+ context 'stack building' do
73
+ it 'constructs request stack' do
74
+ connection = Apis::Connection.new do
75
+ request do
76
+ use Middleware
77
+ use NewMiddleware
78
+ use RESTMiddleware
79
+ end
80
+ end
81
+
82
+ connection.request.to_a.should == [Middleware, NewMiddleware, RESTMiddleware]
83
+ end
84
+
85
+ it 'constructs response stack' do
86
+ connection = Apis::Connection.new do
87
+ response do
88
+ use Middleware
89
+ use NewMiddleware
90
+ use RESTMiddleware
91
+ end
92
+ end
93
+
94
+ connection.response.to_a.should == [Middleware, NewMiddleware, RESTMiddleware]
95
+ end
96
+
97
+ it 'sets adapter' do
98
+ connection = Apis::Connection.new do
99
+ adapter FakeAdapter
100
+ end
101
+ connection.adapter.should be_instance_of(FakeAdapter)
102
+ end
103
+
104
+ it 'finds adapter using symbol shortcut' do
105
+ Apis::Adapter.register(:fake, :FakeAdapter)
106
+ connection = Apis::Connection.new do
107
+ adapter :fake
108
+ end
109
+ connection.adapter.should be_instance_of(FakeAdapter)
110
+ end
111
+
112
+ it 'uses default adapter if none specified' do
113
+ connection = Apis::Connection.new
114
+ connection.adapter nil
115
+ connection.adapter.should be_instance_of(Apis::Adapter::NetHTTP)
116
+ end
117
+ end
118
+ end
119
+
120
+ context 'performing request' do
121
+ before do
122
+ @connection = Apis::Connection.new(:uri => server_host) do
123
+ adapter FakeAdapter
124
+ end
125
+ end
126
+
127
+ [:get, :head, :post, :put, :delete].each do |method|
128
+ context method.to_s.upcase do
129
+ it 'passes method to adapter' do
130
+ @connection.send(method)
131
+ @connection.adapter.last_method.should == method
132
+ end
133
+
134
+ it 'passes path to adapter' do
135
+ @connection.send(method, "/#{method}")
136
+ @connection.adapter.last_path.should == "/#{method}"
137
+ end
138
+
139
+ it 'passes query params to adapter' do
140
+ @connection.send(method, "/#{method}", {:q => 'text'})
141
+ @connection.adapter.last_params.should == {:q => 'text'}
142
+ end
143
+
144
+ it 'passes params specified in block' do
145
+ @connection.send(method, "/#{method}") do |request|
146
+ request.params = {:test => 'params'}
147
+ end
148
+ @connection.adapter.last_params.should == {:test => 'params'}
149
+ end
150
+
151
+ it 'doesn\'t not overwrite params of connection' do
152
+ @connection.params = {:q => 'test'}
153
+ @connection.send(method, "/#{method}") do |request|
154
+ request.params = {:test => 'params'}
155
+ end
156
+ @connection.adapter.last_params.should == {:q => 'test', :test => 'params'}
157
+ @connection.params.should == {:q => 'test'}
158
+ end
159
+
160
+ it 'merges connection params with method params' do
161
+ @connection.params = {:test => 'param'}
162
+ @connection.send(method, "/#{method}", {:q => 'text'})
163
+ @connection.adapter.last_params.should == {:q => 'text', :test => 'param'}
164
+ end
165
+
166
+ it 'passes headers to adapter' do
167
+ @connection.send(method, "/#{method}", {}, {'Content-Type' => 'text'})
168
+ @connection.adapter.last_headers.should == {'Content-Type' => 'text'}
169
+ end
170
+
171
+ it 'merges connection headers with method headers' do
172
+ @connection.headers = {'User-Agent' => 'apis'}
173
+ @connection.send(method, "/#{method}", {}, {'Content-Type' => 'text'})
174
+ @connection.adapter.last_headers.should == {'Content-Type' => 'text', 'User-Agent' => 'apis'}
175
+ end
176
+
177
+ it 'doesn\'t overwrite headers of connection' do
178
+ @connection.headers = {:q => 'test'}
179
+ @connection.send(method, "/#{method}") do |request|
180
+ request.headers = {:test => 'params'}
181
+ end
182
+ @connection.adapter.last_headers.should == {:q => 'test', :test => 'params'}
183
+ @connection.headers.should == {:q => 'test'}
184
+ end
185
+ end
186
+ end
187
+ end
188
+
189
+ context 'request with middleware' do
190
+ context 'request middleware' do
191
+ before do
192
+ Apis::Middleware::Request.register(:middleware, :Middleware)
193
+ @connection = Apis::Connection.new(:uri => server_host) do
194
+ request do
195
+ use :middleware
196
+ end
197
+ adapter FakeAdapter
198
+ end
199
+ end
200
+
201
+ it 'calls midleware' do
202
+ @connection.get
203
+ @connection.adapter.last_headers.should == {'Middleware' => 'true'}
204
+ end
205
+
206
+ it "doesn't overwrite headers of connection" do
207
+ @connection.get
208
+ @connection.adapter.last_headers.should == {'Middleware' => 'true'}
209
+ @connection.headers.should == {}
210
+ end
211
+ end
212
+
213
+ context 'response midleware' do
214
+ before do
215
+ Apis::Middleware::Response.register(:res, :Response)
216
+ @connection = Apis::Connection.new(:uri => server_host) do
217
+ adapter FakeAdapter
218
+ response do
219
+ use :res
220
+ end
221
+ end
222
+ end
223
+
224
+ it 'calls midleware' do
225
+ response = @connection.get
226
+ response.body.should == 'altered'
227
+ end
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe Apis::Middleware::Response::Json do
4
+ it 'replaces body with parsed data' do
5
+ MultiJson.engine = :yajl
6
+ json = Apis::Middleware::Response::Json.new
7
+ request = Apis::Response.new(200, {}, %|{"name":"Marjan Krekoten'","age": 23}|)
8
+ json.call(request)
9
+ request.body.should == {"name" => "Marjan Krekoten'", "age" => 23}
10
+ end
11
+
12
+ it 'registered under :json' do
13
+ Apis::Connection.new do
14
+ response do
15
+ use :json
16
+ end
17
+ end.response.to_a.should include(Apis::Middleware::Response::Json)
18
+ end
19
+ end
data/spec/apis_spec.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'spec_helper'
2
+
3
+ describe Apis do
4
+ end
@@ -0,0 +1,56 @@
1
+ require "spec_helper"
2
+
3
+ describe "The library itself" do
4
+ def check_for_tab_characters(filename)
5
+ failing_lines = []
6
+ File.readlines(filename).each_with_index do |line,number|
7
+ failing_lines << number + 1 if line =~ /\t/
8
+ end
9
+
10
+ unless failing_lines.empty?
11
+ "#{filename} has tab characters on lines #{failing_lines.join(', ')}"
12
+ end
13
+ end
14
+
15
+ def check_for_extra_spaces(filename)
16
+ failing_lines = []
17
+ File.readlines(filename).each_with_index do |line,number|
18
+ next if line =~ /^\s+#.*\s+\n$/
19
+ failing_lines << number + 1 if line =~ /\s+\n$/
20
+ end
21
+
22
+ unless failing_lines.empty?
23
+ "#{filename} has spaces on the EOL on lines #{failing_lines.join(', ')}"
24
+ end
25
+ end
26
+
27
+ RSpec::Matchers.define :be_well_formed do
28
+ failure_message_for_should do |actual|
29
+ actual.join("\n")
30
+ end
31
+
32
+ match do |actual|
33
+ actual.empty?
34
+ end
35
+ end
36
+
37
+ it "has no malformed whitespace" do
38
+ error_messages = []
39
+ Dir.chdir(File.expand_path("../..", __FILE__)) do
40
+ `git ls-files`.split("\n").each do |filename|
41
+ next if filename =~ /\.gitmodules|fixtures|\.md/
42
+ error_messages << check_for_tab_characters(filename)
43
+ error_messages << check_for_extra_spaces(filename)
44
+ end
45
+ end
46
+ error_messages.compact.should be_well_formed
47
+ end
48
+
49
+ it "can still be built" do
50
+ Dir.chdir(root) do
51
+ `gem build apis.gemspec`
52
+ $?.should == 0
53
+ `rm apis-*.gem`
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,60 @@
1
+ require File.expand_path('../../lib/apis', __FILE__)
2
+
3
+ class BaseMiddleware
4
+ attr_accessor :app
5
+ def initialize(app)
6
+ @app = app
7
+ end
8
+ end
9
+ class Middleware < BaseMiddleware
10
+ def call(env)
11
+ env[:params][:middleware] = 'true'
12
+ env[:headers]['Middleware'] = 'true'
13
+ @app.call(env) if @app
14
+ end
15
+ end
16
+ NewMiddleware = Class.new(BaseMiddleware)
17
+ RESTMiddleware = Class.new(BaseMiddleware)
18
+
19
+ class Response < BaseMiddleware
20
+ def call(env)
21
+ env[:body] = 'altered'
22
+ end
23
+ end
24
+
25
+ class FakeAdapter < Apis::Adapter::Abstract
26
+ attr_accessor :last_method, :last_path, :last_params, :last_headers
27
+ def run(method, path = nil, params = {}, headers = {})
28
+ @last_method, @last_path, @last_params, @last_headers = method, path, params, headers
29
+ [200, {}, 'body']
30
+ end
31
+ end
32
+
33
+ module DirHelper
34
+ def root
35
+ @root ||= File.expand_path('../..', __FILE__)
36
+ end
37
+ end
38
+
39
+ module SinatraHelper
40
+ def server_port
41
+ 1234
42
+ end
43
+
44
+ def server_host
45
+ "http://localhost:#{server_port}"
46
+ end
47
+
48
+ def start_server
49
+ %x{unicorn -p #{server_port} #{root}/spec/test_app.ru -D -P #{root}/spec/uni.pid} # 3&> /dev/null
50
+ end
51
+
52
+ def stop_server
53
+ %x{kill `cat #{root}/spec/uni.pid`}
54
+ end
55
+ end
56
+
57
+ RSpec.configure do |rspec|
58
+ rspec.include DirHelper
59
+ rspec.include SinatraHelper
60
+ end
data/spec/test_app.ru ADDED
@@ -0,0 +1,16 @@
1
+ require 'sinatra'
2
+
3
+ [:head, :get, :post, :put, :delete].each do |method|
4
+ send(method, '/') do
5
+ headers['X-Requested-With-Method'] = method.to_s.upcase
6
+ "#{method.to_s.upcase}" unless method == :head
7
+ end
8
+
9
+ send(method, "/#{method}") do
10
+ headers['X-Requested-With-Method'] = method.to_s.upcase
11
+ headers['X-Sent-Params'] = params.inspect
12
+ "#{method.to_s.upcase}" unless method == :head
13
+ end
14
+ end
15
+
16
+ run Sinatra::Application
metadata ADDED
@@ -0,0 +1,167 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: apis
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.4.1
6
+ platform: ruby
7
+ authors:
8
+ - "Marjan Krekoten' (\xD0\x9C\xD0\xB0\xD1\x80'\xD1\x8F\xD0\xBD \xD0\x9A\xD1\x80\xD0\xB5\xD0\xBA\xD0\xBE\xD1\x82\xD0\xB5\xD0\xBD\xD1\x8C)"
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-04-07 00:00:00 +03:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: rspec
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: "2.5"
25
+ type: :development
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: sinatra
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: "0"
36
+ type: :development
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: rack-test
40
+ prerelease: false
41
+ requirement: &id003 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: "0"
47
+ type: :development
48
+ version_requirements: *id003
49
+ - !ruby/object:Gem::Dependency
50
+ name: unicorn
51
+ prerelease: false
52
+ requirement: &id004 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ type: :development
59
+ version_requirements: *id004
60
+ - !ruby/object:Gem::Dependency
61
+ name: multi_json
62
+ prerelease: false
63
+ requirement: &id005 !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ type: :development
70
+ version_requirements: *id005
71
+ - !ruby/object:Gem::Dependency
72
+ name: yajl-ruby
73
+ prerelease: false
74
+ requirement: &id006 !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: "0"
80
+ type: :development
81
+ version_requirements: *id006
82
+ - !ruby/object:Gem::Dependency
83
+ name: addressable
84
+ prerelease: false
85
+ requirement: &id007 !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: "0"
91
+ type: :runtime
92
+ version_requirements: *id007
93
+ description: Rack-like HTTP client library inspired by Faraday done my way
94
+ email:
95
+ - m@hmarynka.com
96
+ executables: []
97
+
98
+ extensions: []
99
+
100
+ extra_rdoc_files: []
101
+
102
+ files:
103
+ - .gitignore
104
+ - .yardopts
105
+ - Gemfile
106
+ - README.md
107
+ - Rakefile
108
+ - apis.gemspec
109
+ - lib/apis.rb
110
+ - lib/apis/adapter/abstract.rb
111
+ - lib/apis/adapter/net_http.rb
112
+ - lib/apis/adapter/rack_test.rb
113
+ - lib/apis/builder.rb
114
+ - lib/apis/connection.rb
115
+ - lib/apis/connection_scope.rb
116
+ - lib/apis/middleware/response/json.rb
117
+ - lib/apis/response.rb
118
+ - lib/apis/version.rb
119
+ - spec/apis/adapter/net_http_spec.rb
120
+ - spec/apis/adapter/rack_test_spec.rb
121
+ - spec/apis/adapter_spec.rb
122
+ - spec/apis/builder_spec.rb
123
+ - spec/apis/connection_spec.rb
124
+ - spec/apis/middleware/response/json_spec.rb
125
+ - spec/apis_spec.rb
126
+ - spec/quality_spec.rb
127
+ - spec/spec_helper.rb
128
+ - spec/test_app.ru
129
+ has_rdoc: true
130
+ homepage: http://hmarynka.com/labs/apis
131
+ licenses: []
132
+
133
+ post_install_message:
134
+ rdoc_options: []
135
+
136
+ require_paths:
137
+ - lib
138
+ required_ruby_version: !ruby/object:Gem::Requirement
139
+ none: false
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: "0"
144
+ required_rubygems_version: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: "0"
150
+ requirements: []
151
+
152
+ rubyforge_project: apis
153
+ rubygems_version: 1.6.2
154
+ signing_key:
155
+ specification_version: 3
156
+ summary: Working bee of API wrapper
157
+ test_files:
158
+ - spec/apis/adapter/net_http_spec.rb
159
+ - spec/apis/adapter/rack_test_spec.rb
160
+ - spec/apis/adapter_spec.rb
161
+ - spec/apis/builder_spec.rb
162
+ - spec/apis/connection_spec.rb
163
+ - spec/apis/middleware/response/json_spec.rb
164
+ - spec/apis_spec.rb
165
+ - spec/quality_spec.rb
166
+ - spec/spec_helper.rb
167
+ - spec/test_app.ru