callapi 0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 930672be83fc5e7372fac0d6564fe7572f5cad79
4
+ data.tar.gz: a1a0d70c85b40f9b520e58a01174d22e6ba544fe
5
+ SHA512:
6
+ metadata.gz: 1af1b261ec7d87ac3a2fd1fa56041dbf1867992e47bbbdf8a8c4a456b97c964f1ef9f0e3106ff5cce5e14ade0302978a881d2ebd02b836ec931df53f5e5c3ca8
7
+ data.tar.gz: 64dcba1a07560ade4d23178f1155a74719aefbce7e75f92b4eda928280773dbda0b1919f830679aae3854cbe1ccc1c6673905c027e5d6ff0e62a4a9217ac67e4
@@ -0,0 +1,7 @@
1
+ /Gemfile.lock
2
+ /tmp/
3
+ *.bundle
4
+ .idea
5
+ .rspec
6
+ .ruby-version
7
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'multi_json'
4
+ gem 'memoist'
5
+ gem 'activesupport'
6
+ gem 'addressable'
7
+ gem 'addressabler', '>= 0.1'
8
+ gem 'colorize'
9
+ gem 'chainy', '~> 0.0.5'
10
+
11
+ gemspec
12
+
13
+ group :test do
14
+ gem 'rspec', '~> 3.1'
15
+ gem 'webmock'
16
+ end
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Kacper Walanus
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,207 @@
1
+ # Callapi
2
+
3
+ Easy API calls
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'callapi'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install callapi
20
+
21
+ <br>
22
+ ## Usage
23
+
24
+ ### Basics
25
+
26
+ ##### 1 Configuration: Set your API host
27
+ ```ruby
28
+ require 'callapi'
29
+
30
+ Callapi::Config.configure do |config|
31
+ config.api_host = 'http://private-50e9-callapi.apiary-mock.com'
32
+ end
33
+ ```
34
+
35
+ <br>
36
+ ##### 2 Routes: set your calls
37
+ ```ruby
38
+ Callapi::Routes.draw do
39
+ get 'notes' # This creates #get_notes_call method
40
+ get 'notes/:id' # This creates #get_notes_by_id_call method
41
+ post 'notes' # This creates #post_notes_call method
42
+ end
43
+ ```
44
+
45
+ <br>
46
+ ##### 3 Use your calls: #data and #body methods
47
+ ```ruby
48
+ # Parsed response (default parser is Callapi::Call::Parser::Json, see Parsers section):
49
+ get_notes_call.response.data #=> [{"id"=>1, "title"=>"Jogging in park"}, {"id"=>2, "title"=>"Pick-up posters from post-office"}]
50
+
51
+ # Raw response:
52
+ get_notes_call.response.body #=> '[{"id":1,"title":"Jogging in park"},{"id":2,"title":"Pick-up posters from post-office"}]'
53
+
54
+ # Request with params:
55
+ post_notes_call({id: 1, title: "Swimming"})
56
+ ```
57
+
58
+ <br>
59
+ ### Routes
60
+
61
+ ##### HTTP methods
62
+ ```ruby
63
+ Callapi::Routes.draw do
64
+ get 'results' #=> #get_results_call
65
+ post 'results' #=> #post_results_call
66
+ put 'results' #=> #put_results_call
67
+ patch 'results' #=> #patch_results_call
68
+ delete 'results' #=> #delete_results_call
69
+ end
70
+ ```
71
+
72
+ <br>
73
+ ##### Params
74
+ ```ruby
75
+ Callapi::Routes.draw do
76
+ get 'users/:id' #=> #get_users_by_id_call(id: 109) | :id is required
77
+ put 'users/:id' #=> #put_users_by_id_call(id: 109, name: 'Kacper', age: 467)
78
+ put 'users/:id/posts/:post_id' #=> #put_users_by_id_posts_by_post_id_call(id: 109, post_id: 209)
79
+ end
80
+ ```
81
+
82
+ <br>
83
+ ##### Namespaces
84
+ ```ruby
85
+ Callapi::Routes.draw do
86
+ get 'users' #=> #get_users_call
87
+
88
+ namespace 'users' do
89
+ get ':id' #=> #get_users_by_id_call(id: 109)
90
+ delete ':id' #=> #delete_users_by_id_call(id: 109)
91
+ end
92
+
93
+ namespace 'users/:id/posts' do
94
+ get ':post_id' #=> #get_users_by_id_posts_by_post_id_call(id: 109, post_id: 209)
95
+ end
96
+ end
97
+ ```
98
+
99
+ <br>
100
+ ##### Options
101
+ ```ruby
102
+ Callapi::Routes.draw do
103
+ get 'users', parser: Callapi::Call::Parser::Json
104
+ end
105
+ ```
106
+
107
+ <br>
108
+ ## Parsers
109
+
110
+ ##### Available parsers
111
+ Callapi provides following parsers:
112
+ - `Callapi::Call::Parser::Json`
113
+ - `Callapi::Call::Parser::Json::AsObject`
114
+ - `Callapi::Call::Parser::Plain`
115
+
116
+ Default is `Callapi::Call::Parser::Json`.
117
+
118
+ <br>
119
+ __`Callapi::Call::Parser::Json`__ converts JSON to `Hash`. If the API response for `/notes/12` is
120
+
121
+ ```javascript
122
+ { "id": 2, "title": "Pick-up posters from post-office" }
123
+ ```
124
+
125
+ then with `#data` method we can convert this response to `Hash`:
126
+ ```ruby
127
+ get_notes_by_id_call(id: 12).response.data['id'] #=> 2
128
+ ```
129
+
130
+ <br>
131
+ __`Callapi::Call::Parser::Json::AsObject`__ converts JSON to an object called `DeepStruct` (very similar to [`OpenStruct`](http://www.ruby-doc.org/stdlib-2.0/libdoc/ostruct/rdoc/OpenStruct.html)):
132
+ ```ruby
133
+ get_notes_by_id_call(id: 12).response.data.title #=> "Pick-up posters from post-office"
134
+ ```
135
+
136
+ <br>
137
+ __`Callapi::Call::Parser::Plain`__ does not parse at all:
138
+ ```ruby
139
+ get_notes_by_id_call(id: 12).response.data #=> '{ "id": 2, "title": "Pick-up posters from post-office" }'
140
+ ```
141
+
142
+ <br>
143
+ ##### Setting parsers
144
+ Default parser for all calls can be set in configuration:
145
+ ```ruby
146
+ Callapi::Config.configure do |config|
147
+ config.default_response_parser = Callapi::Call::Parser::Json::AsObject
148
+ end
149
+ ```
150
+
151
+ Default parser for specific calls can be set in routes:
152
+ ```ruby
153
+ Callapi::Routes.draw do
154
+ get 'users', parser: Callapi::Call::Parser::Json::AsObject
155
+ get 'version', parser: Callapi::Call::Parser::Plain
156
+ end
157
+ ```
158
+
159
+ Parser can be also set for each call instance separately:
160
+ ```ruby
161
+ get_notes_call.with_response_parser(Callapi::Call::Parser::Json::AsObject).response.data #=> [#<DeepStruct id=1, title="Jogging in park">]
162
+ get_notes_call.with_response_parser(Callapi::Call::Parser::Plain).response.data #=> "[{\n \"id\": 1, \"title\": \"Jogging in park\"\n}]"
163
+
164
+ call = get_notes_call
165
+ call.with_response_parser(Callapi::Call::Parser::Json::AsObject).response.data #=> [#<DeepStruct id=1, title="Jogging in park">]
166
+ call.reload # DO NOT FORGET TO CLEAR CACHE!
167
+ call.with_response_parser(Callapi::Call::Parser::Plain).response.data #=> "[{\n \"id\": 1, \"title\": \"Jogging in park\"\n}]"
168
+ ```
169
+
170
+ <br>
171
+ ## Configuration
172
+
173
+ ##### Config options
174
+
175
+ - `api_host`
176
+ - `api_path_prefix`
177
+ - `default_response_parser`
178
+ - `log_level`
179
+
180
+ ```ruby
181
+ Callapi::Config.configure do |config|
182
+ config.api_host = 'http://your.api.org'
183
+
184
+ # request will be send to http://your.api.org/api/
185
+ config.api_path_prefix = 'api'
186
+
187
+ # see Parsers section
188
+ config.default_response_parser = Callapi::Call::Parser::Json::AsObject
189
+
190
+ # :none is only possible option. turns off logs.
191
+ config.log_level = :none
192
+ end
193
+ ```
194
+
195
+ <br>
196
+ ## TO DO
197
+
198
+ - Support HTTPS
199
+
200
+ <br>
201
+ ## Contributing
202
+
203
+ 1. Fork it ( https://github.com/[my-github-username]/callapi/fork )
204
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
205
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
206
+ 4. Push to the branch (`git push origin my-new-feature`)
207
+ 5. Create a new Pull Request
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'callapi/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "callapi"
8
+ spec.version = Callapi::VERSION
9
+ spec.authors = ["Kacper Walanus"]
10
+ spec.email = ["kacper.walanus@elpassion.pl"]
11
+ spec.summary = %q{Callapi}
12
+ spec.description = %q{Callapi}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.7'
22
+ spec.add_development_dependency 'rake', '~> 10.0'
23
+
24
+ spec.add_runtime_dependency 'addressable', '~> 2.3'
25
+ spec.add_runtime_dependency 'addressabler', '~> 0.1'
26
+ spec.add_runtime_dependency 'multi_json', '~> 1.10'
27
+ spec.add_runtime_dependency 'colorize', '~> 0.7'
28
+ spec.add_runtime_dependency 'activesupport', '~> 4.2'
29
+ spec.add_runtime_dependency 'chainy', '~> 0.0.5'
30
+ spec.add_runtime_dependency 'memoist', '~> 0.11'
31
+ end
@@ -0,0 +1,12 @@
1
+ require 'callapi/version'
2
+ require 'memoist'
3
+ require 'forwardable'
4
+ require 'chainy'
5
+ require 'ostruct'
6
+
7
+ module Callapi
8
+ require 'callapi/config'
9
+ require 'callapi/errors'
10
+ require 'callapi/call'
11
+ require 'callapi/routes'
12
+ end
@@ -0,0 +1,3 @@
1
+ module Callapi::Call
2
+ require_relative 'call/base'
3
+ end
@@ -0,0 +1,66 @@
1
+ require_relative 'request_metadata'
2
+
3
+ class Callapi::Call::Base
4
+ require_relative 'request'
5
+ require_relative 'response'
6
+
7
+ extend Forwardable
8
+ extend Memoist
9
+
10
+ def_delegators :request_metadata, :request_method, :request_path
11
+
12
+ chain_attr_accessor :params, :headers, hash: true, prefix: 'add'
13
+ chain_attr_accessor :response_parser, :strategy
14
+
15
+ def initialize(params = {})
16
+ add_params(params)
17
+ end
18
+
19
+ def response
20
+ @response ||= build_response
21
+ end
22
+
23
+ def strategy
24
+ @strategy ||= (self.class.strategy || Callapi::Config.default_request_strategy)
25
+ end
26
+
27
+ def request_metadata
28
+ Callapi::Call::RequestMetadata.new(self)
29
+ end
30
+ memoize :request_metadata
31
+
32
+ def response_parser
33
+ @response_parser ||= (self.class.response_parser || Callapi::Config.default_response_parser)
34
+ end
35
+
36
+ def self.strategy=(strategy)
37
+ @strategy = strategy
38
+ end
39
+
40
+ def self.strategy
41
+ @strategy
42
+ end
43
+
44
+ def self.response_parser=(response_parser)
45
+ @response_parser = response_parser
46
+ end
47
+
48
+ def self.response_parser
49
+ @response_parser
50
+ end
51
+
52
+ def reload
53
+ @response = nil
54
+ end
55
+
56
+ private
57
+
58
+ def build_response
59
+ response_parser.new(request_class.new(self).response)
60
+ end
61
+
62
+ #TODO: Should be replaced with #strategy. There is no need for additional class between Base and actual strategy, for now Callapi::Call::Request is such unnecessary class.
63
+ def request_class
64
+ Callapi::Call::Request
65
+ end
66
+ end
@@ -0,0 +1,14 @@
1
+ class Callapi::Call::Request
2
+ require_relative 'request/base'
3
+ require_relative 'request/http'
4
+ require_relative 'request/api'
5
+ require_relative 'request/mock'
6
+
7
+ def initialize(context)
8
+ @context = context
9
+ end
10
+
11
+ def response
12
+ @context.strategy.new(@context).response
13
+ end
14
+ end
@@ -0,0 +1,9 @@
1
+ class Callapi::Call::Request::Api < Callapi::Call::Request::Http
2
+ def host
3
+ Callapi::Config.api_host || raise(Callapi::ApiHostNotSetError)
4
+ end
5
+
6
+ def api_path_prefix
7
+ Callapi::Config.api_path_prefix
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ class Callapi::Call::Request::Base
2
+ extend Forwardable
3
+
4
+ def_delegators :@context, :request_method, :request_path, :params, :headers
5
+
6
+ def initialize(context)
7
+ @context = context
8
+ end
9
+
10
+ def response
11
+ raise NotImplementedError
12
+ end
13
+ end
@@ -0,0 +1,60 @@
1
+ require 'net/http'
2
+ require 'addressable/uri'
3
+ require 'addressabler'
4
+
5
+ class Callapi::Call::Request::Http < Callapi::Call::Request::Base
6
+ require_relative 'http/log_helper'
7
+
8
+ extend Memoist
9
+ include Callapi::Call::Request::Http::LogHelper
10
+
11
+ HTTP_METHOD_TO_REQUEST_CLASS = {
12
+ get: Net::HTTP::Get,
13
+ post: Net::HTTP::Post,
14
+ put: Net::HTTP::Put,
15
+ delete: Net::HTTP::Delete,
16
+ patch: Net::HTTP::Patch
17
+ }
18
+
19
+ def response
20
+ with_logging do
21
+ http.request(request)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def host
28
+ raise NotImplementedError
29
+ end
30
+
31
+ def http
32
+ Net::HTTP.new(uri.host, uri.port)
33
+ end
34
+
35
+ def request
36
+ request_class.new(uri.request_uri, headers).tap do |request|
37
+ request.set_form_data params if put_params_in_request_body?
38
+ end
39
+ end
40
+
41
+ def request_class
42
+ HTTP_METHOD_TO_REQUEST_CLASS[request_method] || raise(Callapi::UnknownHttpMethodError)
43
+ end
44
+
45
+ def uri
46
+ Addressable::URI.parse(host).tap do |uri|
47
+ uri.path = path_prefix + request_path
48
+ uri.query_hash = params unless put_params_in_request_body?
49
+ end
50
+ end
51
+ memoize :uri
52
+
53
+ def put_params_in_request_body?
54
+ [:post, :patch, :put].include? request_method
55
+ end
56
+
57
+ def path_prefix
58
+ ''
59
+ end
60
+ end