apitizer 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -1
  3. data/.travis.yml +6 -0
  4. data/.yardopts +6 -0
  5. data/CHANGELOG.md +10 -0
  6. data/Guardfile +11 -4
  7. data/README.md +74 -7
  8. data/apitizer.gemspec +1 -2
  9. data/lib/apitizer.rb +0 -1
  10. data/lib/apitizer/base.rb +16 -27
  11. data/lib/apitizer/connection.rb +3 -1
  12. data/lib/apitizer/connection/adaptor.rb +1 -1
  13. data/lib/apitizer/connection/adaptor/standard.rb +24 -7
  14. data/lib/apitizer/connection/dispatcher.rb +5 -12
  15. data/lib/apitizer/connection/format.rb +14 -0
  16. data/lib/apitizer/{processing/parser → connection/format}/json.rb +6 -2
  17. data/lib/apitizer/{processing/parser → connection/format}/yaml.rb +6 -2
  18. data/lib/apitizer/connection/request.rb +3 -3
  19. data/lib/apitizer/connection/response.rb +3 -3
  20. data/lib/apitizer/core.rb +4 -4
  21. data/lib/apitizer/helper.rb +38 -14
  22. data/lib/apitizer/result.rb +2 -2
  23. data/lib/apitizer/routing.rb +1 -1
  24. data/lib/apitizer/routing/{mapper.rb → map.rb} +3 -10
  25. data/lib/apitizer/routing/node.rb +0 -1
  26. data/lib/apitizer/routing/node/base.rb +15 -17
  27. data/lib/apitizer/routing/node/collection.rb +17 -16
  28. data/lib/apitizer/routing/node/operation.rb +14 -15
  29. data/lib/apitizer/routing/node/root.rb +8 -2
  30. data/lib/apitizer/routing/path.rb +16 -8
  31. data/lib/apitizer/version.rb +1 -1
  32. data/spec/apitizer/base_spec.rb +36 -28
  33. data/spec/apitizer/connection/adaptor_spec.rb +87 -11
  34. data/spec/apitizer/connection/dispatcher_spec.rb +21 -23
  35. data/spec/apitizer/connection/format_spec.rb +15 -0
  36. data/spec/apitizer/helper_spec.rb +53 -24
  37. data/spec/apitizer/result_spec.rb +5 -7
  38. data/spec/apitizer/routing/map_spec.rb +71 -0
  39. data/spec/apitizer/routing/node_spec.rb +108 -36
  40. data/spec/apitizer/routing/path_spec.rb +12 -92
  41. data/spec/spec_helper.rb +4 -6
  42. data/spec/support/factory_helper.rb +25 -5
  43. data/spec/support/resource_helper.rb +8 -0
  44. metadata +14 -15
  45. data/lib/apitizer/processing.rb +0 -8
  46. data/lib/apitizer/processing/parser.rb +0 -14
  47. data/lib/apitizer/processing/translator.rb +0 -13
  48. data/lib/apitizer/routing/node/scope.rb +0 -19
  49. data/spec/apitizer/processing/parser_spec.rb +0 -23
  50. data/spec/apitizer/routing/mapper_spec.rb +0 -80
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 77d5476a8e3c80f3034bd2f27144d7f2fd997095
4
- data.tar.gz: 483fda35af1d8363671a0aad567ce9a55334058a
3
+ metadata.gz: 40fc294404896c8bf5da7bcc98d9a1fb4888d559
4
+ data.tar.gz: 0f20b6555f17c71ed78843794b7b37c9b3e1abb8
5
5
  SHA512:
6
- metadata.gz: ecb1d3880c1c46112e6ad3233ad60a49b112cc1d4c978200a6a69aa1923b217a95b25630503802ea3ae586ee3da8b6529d2ef61a075540ab0fc2f89efb06b3e3
7
- data.tar.gz: 5503cfb3bbdb45bd32da4b028432a12f68d948da8caca486c559677fda7953631695867084963f2776ac9ba608da0bd8538b993eb4d540d56d3b5c93ff12ddfb
6
+ metadata.gz: 4a38a7ce97d22a84accc45630b5810a36875e300dcfb097c90ceb05ca809ce4b80122bdd54350d35972c2ec04789cd0f260c3355f003a598a7fc43bfea2000ec
7
+ data.tar.gz: 20a7a1afa6a79245ac342635422fcfae978f22f70b75651d9287a813b5bfb0f4057da601dea5d60b62f19c0253b9341de200b2f177088e77811931143f03e174
data/.gitignore CHANGED
@@ -1,5 +1,7 @@
1
1
  .DS_Store
2
- *.swo
3
2
  *.swp
3
+ *.swo
4
4
  *.gem
5
5
  Gemfile.lock
6
+ doc
7
+ .yardoc
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.0
4
+ branches:
5
+ only:
6
+ - master
@@ -0,0 +1,6 @@
1
+ --markup-provider=redcarpet
2
+ --markup=markdown
3
+ lib/**/*.rb
4
+ -
5
+ CHANGELOG.md
6
+ LICENSE.txt
@@ -1 +1,11 @@
1
+ ## Apitizer 0.0.2 (July 20, 2014)
2
+
3
+ * A new strategy for searching request endpoints.
4
+ * Support for `on: :collection` in addition to `on: :member`.
5
+ * Support for `except: [ ... ]` in addition to `only: [ ... ]`.
6
+ * Setting the Accept header according to the data format.
7
+ * Sending the parameter of a POST/PUT/PATCH/DELETE request in its body.
8
+ * Enforcing UTF-8 when sending HTTP requests.
9
+ * Improved parameter encoding.
10
+
1
11
  ## Apitizer 0.0.1 (June 1, 2014)
data/Guardfile CHANGED
@@ -1,7 +1,14 @@
1
1
  guard :rspec do
2
2
  watch(%r{^spec/.+_spec\.rb$})
3
- watch('spec/spec_helper.rb') { 'spec' }
4
- watch(%r{^spec/support/(.+)\.rb$}) { 'spec' }
5
- watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{ m[1] }_spec.rb" }
6
- watch(%r{^lib/(.*\.rb)$}) { |m| "spec/#{ File.dirname(m[1]) }" }
3
+ watch(%r{^spec/.+_helper\.rb$}) { 'spec' }
4
+ watch(%r{^lib/(.+)\.rb$}) do |match|
5
+ result = "spec/#{ match[1] }_spec.rb"
6
+ loop do
7
+ break if File.exist?(result)
8
+ result = File.dirname(result)
9
+ break if result.empty?
10
+ end
11
+ result
12
+ end
7
13
  end
14
+ # vim: ft=ruby
data/README.md CHANGED
@@ -1,28 +1,91 @@
1
- # Apitizer
1
+ # Apitizer [![Gem Version](https://badge.fury.io/rb/apitizer.svg)](http://badge.fury.io/rb/apitizer) [![Dependency Status](https://gemnasium.com/IvanUkhov/apitizer.svg)](https://gemnasium.com/IvanUkhov/apitizer) [![Build Status](https://travis-ci.org/IvanUkhov/apitizer.svg?branch=master)](https://travis-ci.org/IvanUkhov/apitizer)
2
+
2
3
  The main ingredient of a RESTful API client.
3
4
 
4
5
  ## Installation
6
+
5
7
  Add the following line to your `Gemfile`:
8
+
6
9
  ```ruby
7
10
  gem 'apitizer'
8
11
  ```
9
12
 
10
13
  Then execute:
14
+
11
15
  ```bash
12
16
  $ bundle
13
17
  ```
14
18
 
15
19
  Alternatively, you can install the gem manually:
20
+
16
21
  ```bash
17
22
  $ gem install apitizer
18
23
  ```
19
24
 
25
+ Note that the minimal supported version of Ruby is `2.1`.
26
+
20
27
  ## Usage
28
+
29
+ Create an apitizer describing the API of the Web service you would like
30
+ to interact with:
31
+
32
+ ```ruby
33
+ apitizer = Apitizer::Base.new do
34
+ address 'https://service.com/api'
35
+
36
+ resources :posts do
37
+ resources :comments
38
+ end
39
+ end
40
+ ```
41
+
42
+ The apitizer can now be used to manipulate the resources provided by the
43
+ Web service. To this end, there are five methods: `index`, `show`, `create`,
44
+ `update`, and `delete`, which can be used as shown below.
45
+
46
+ To list the members of a collection:
47
+
48
+ ```ruby
49
+ apitizer.index(:posts)
50
+ apitizer.index(:posts, post_id, :comments)
51
+ ```
52
+
53
+ To read a member of a collection:
54
+
55
+ ```ruby
56
+ apitizer.show(:posts, post_id)
57
+ apitizer.show(:posts, post_id, :comments, comment_id)
58
+ ```
59
+
60
+ To create a new member in a collection:
61
+
62
+ ```ruby
63
+ apitizer.create(:posts, title: 'To be or not to be')
64
+ apitizer.create(:posts, post_id, :comments, content: 'That is the question.')
65
+ ```
66
+
67
+ To update a member of a collection:
68
+
69
+ ```ruby
70
+ apitizer.update(:posts, post_id, title: 'What is the meaning of life?')
71
+ apitizer.update(:posts, post_id, :comments, comment_id, content: '42.')
72
+ ```
73
+
74
+ To delete a member of a collection:
75
+
76
+ ```ruby
77
+ apitizer.delete(:posts, post_id)
78
+ apitizer.delete(:posts, post_id, :comments, comment_id)
79
+ ```
80
+
81
+ ## Example
82
+
21
83
  Here is an example for the [Typekit API](https://typekit.com/docs/api).
22
84
  Check out [Typekit Client](https://github.com/IvanUkhov/typekit-client)
23
85
  as well.
24
86
 
25
87
  Code:
88
+
26
89
  ```ruby
27
90
  require 'apitizer'
28
91
 
@@ -39,7 +102,7 @@ apitizer = Apitizer::Base.new(options) do
39
102
  address 'https://typekit.com/api/v1/json'
40
103
 
41
104
  resources :families, only: :show do
42
- show ':variant', on: :member
105
+ show ':variation', on: :member
43
106
  end
44
107
 
45
108
  resources :kits do
@@ -55,6 +118,7 @@ puts JSON.pretty_generate(apitizer.index(:kits))
55
118
  ```
56
119
 
57
120
  Output:
121
+
58
122
  ```json
59
123
  {
60
124
  "kits": [
@@ -79,13 +143,16 @@ Output:
79
143
  ```
80
144
 
81
145
  ## History
146
+
82
147
  Apitizer was a part of
83
148
  [Typekit Client](https://github.com/IvanUkhov/typekit-client).
84
149
 
85
150
  ## Contributing
86
151
 
87
- 1. Fork it ( https://github.com/IvanUkhov/apitizer/fork )
88
- 2. Create your feature branch (`git checkout -b my-new-feature`)
89
- 3. Commit your changes (`git commit -am 'Add some feature'`)
90
- 4. Push to the branch (`git push origin my-new-feature`)
91
- 5. Create a new Pull Request
152
+ 1. [Fork](https://help.github.com/articles/fork-a-repo) the project.
153
+ 2. Create a branch for your feature (`git checkout -b awesome-feature`).
154
+ 3. Implement your feature (`vim`).
155
+ 4. Commit your changes (`git commit -am 'Implemented an awesome feature'`).
156
+ 5. Push to the branch (`git push origin awesome-feature`).
157
+ 6. [Create](https://help.github.com/articles/creating-a-pull-request)
158
+ a new Pull Request.
@@ -15,7 +15,6 @@ Gem::Specification.new do |spec|
15
15
  spec.license = 'MIT'
16
16
 
17
17
  spec.files = `git ls-files -z`.split("\x0")
18
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
18
  spec.test_files = spec.files.grep(%r{^spec/})
20
19
  spec.require_paths = [ 'lib' ]
21
20
 
@@ -26,7 +25,7 @@ Gem::Specification.new do |spec|
26
25
 
27
26
  spec.add_development_dependency 'bundler', '~> 1.6'
28
27
  spec.add_development_dependency 'rake'
29
- spec.add_development_dependency 'rspec', '~> 2.14'
28
+ spec.add_development_dependency 'rspec', '~> 3.0'
30
29
  spec.add_development_dependency 'guard-rspec', '~> 4.2'
31
30
  spec.add_development_dependency 'webmock', '~> 1.18'
32
31
  end
@@ -6,7 +6,6 @@ require_relative 'apitizer/helper'
6
6
 
7
7
  require_relative 'apitizer/routing'
8
8
  require_relative 'apitizer/connection'
9
- require_relative 'apitizer/processing'
10
9
 
11
10
  require_relative 'apitizer/result'
12
11
  require_relative 'apitizer/base'
@@ -1,5 +1,9 @@
1
1
  module Apitizer
2
2
  class Base
3
+ extend Forwardable
4
+
5
+ def_delegator :map, :define
6
+
3
7
  def initialize(**options, &block)
4
8
  @options = Helper.deep_merge(Apitizer.defaults, options)
5
9
  @block = block
@@ -8,8 +12,7 @@ module Apitizer
8
12
  def process(*arguments)
9
13
  request = build_request(*arguments)
10
14
  response = dispatcher.process(request)
11
- content = translator.process(response)
12
- Result.new(request: request, response: response, content: content)
15
+ Result.new(request: request, response: response)
13
16
  end
14
17
 
15
18
  Apitizer.actions.each do |action|
@@ -20,42 +23,28 @@ module Apitizer
20
23
 
21
24
  private
22
25
 
23
- [ :mapper, :dispatcher, :translator ].each do |component|
24
- class_eval <<-METHOD, __FILE__, __LINE__ + 1
25
- def #{ component }
26
- @#{ component } ||= build_#{ component }
27
- end
28
- METHOD
29
- end
30
-
31
- def build_mapper
32
- Routing::Mapper.new(&@block)
26
+ def map
27
+ @map ||= Routing::Map.new(&@block)
33
28
  end
34
29
 
35
- def build_dispatcher
36
- Connection::Dispatcher.new(adaptor: self.adaptor,
37
- dictionary: self.dictionary, headers: self.headers)
38
- end
39
-
40
- def build_translator
41
- Processing::Translator.new(format: self.format)
30
+ def dispatcher
31
+ @dispatcher ||= Connection::Dispatcher.new(format: @options[:format],
32
+ adaptor: @options[:adaptor], headers: @options[:headers])
42
33
  end
43
34
 
44
35
  def build_request(*arguments)
45
- action, steps, parameters = prepare(*arguments)
46
- path = mapper.trace(action, steps)
47
- Connection::Request.new(action: action, path: path,
36
+ action, method, steps, parameters = prepare(*arguments)
37
+ Connection::Request.new(method: method, path: map.trace(action, steps),
48
38
  parameters: parameters)
49
39
  end
50
40
 
51
41
  def prepare(action, *path)
42
+ action = action.to_sym
43
+ method = @options[:dictionary][action] or raise Error, 'Unknown action'
52
44
  parameters = path.last.is_a?(Hash) ? path.pop : {}
53
- [ action.to_sym, path.flatten.map(&:to_sym), parameters ]
54
- end
45
+ steps = path.flatten.map(&:to_sym)
55
46
 
56
- def method_missing(name, *arguments, &block)
57
- return @options[name] if @options.key?(name)
58
- super
47
+ [ action, method, steps, parameters ]
59
48
  end
60
49
  end
61
50
  end
@@ -1,6 +1,8 @@
1
- require_relative 'connection/request'
1
+ require_relative 'connection/format'
2
2
  require_relative 'connection/adaptor'
3
3
  require_relative 'connection/dispatcher'
4
+
5
+ require_relative 'connection/request'
4
6
  require_relative 'connection/response'
5
7
 
6
8
  module Apitizer
@@ -6,7 +6,7 @@ module Apitizer
6
6
  def self.build(name)
7
7
  self.const_get(name.to_s.capitalize).new
8
8
  rescue NameError
9
- raise Error, 'Unknown connection adaptor'
9
+ raise Error, 'Unknown adaptor'
10
10
  end
11
11
  end
12
12
  end
@@ -6,13 +6,14 @@ module Apitizer
6
6
  module Adaptor
7
7
  class Standard
8
8
  def process(method, address, parameters = {}, headers = {})
9
- klass = Net::HTTP.const_get(method.to_s.capitalize)
10
- request = klass.new(build_uri(address, parameters))
9
+ request = build_request(method, address, parameters)
11
10
  headers.each { |k, v| request[k] = v }
11
+
12
12
  http = Net::HTTP.new(request.uri.host, request.uri.port)
13
13
  http.use_ssl = true if address =~ /^https:/
14
+
14
15
  response = http.request(request)
15
- [ response.code, response.to_hash, response.body ]
16
+ [ response.code.to_i, response.to_hash, Array(response.body) ]
16
17
  rescue NoMethodError
17
18
  raise
18
19
  rescue NameError
@@ -23,10 +24,26 @@ module Apitizer
23
24
 
24
25
  private
25
26
 
26
- def build_uri(address, parameters)
27
- chunks = [ address ]
28
- chunks << Helper.build_query(parameters) unless parameters.empty?
29
- URI(chunks.join('?'))
27
+ def build_request(method, address, parameters)
28
+ klass = Net::HTTP.const_get(method.to_s.capitalize)
29
+
30
+ return klass.new(URI(address)) if parameters.empty?
31
+
32
+ parameters = Helper.build_query(parameters)
33
+
34
+ if klass == Net::HTTP::Get
35
+ address = [ address, parameters ].join('?')
36
+ request = klass.new(URI(address))
37
+ else
38
+ request = klass.new(URI(address))
39
+ request.body = parameters
40
+ request['Content-Type'] = [
41
+ 'application/x-www-form-urlencoded',
42
+ "charset=#{ parameters.encoding.to_s }"
43
+ ].join('; ')
44
+ end
45
+
46
+ request
30
47
  end
31
48
  end
32
49
  end
@@ -1,23 +1,16 @@
1
1
  module Apitizer
2
2
  module Connection
3
3
  class Dispatcher
4
- def initialize(adaptor: :standard, dictionary:, headers: {})
4
+ def initialize(format:, adaptor: :standard, headers: {})
5
+ @format = Format.build(format)
5
6
  @adaptor = Adaptor.build(adaptor)
6
- @dictionary = dictionary
7
- @headers = headers
7
+ @headers = headers.merge('Accept' => @format.mime_type)
8
8
  end
9
9
 
10
10
  def process(request)
11
- method = translate(request.action)
12
- code, _, body = @adaptor.process(method, request.address,
11
+ code, _, body = @adaptor.process(request.method, request.address,
13
12
  request.parameters, @headers)
14
- Response.new(code: code.to_i, body: body)
15
- end
16
-
17
- private
18
-
19
- def translate(action)
20
- @dictionary[action] or raise Error, 'Unknown action'
13
+ Response.new(code: code, content: @format.process(body.join))
21
14
  end
22
15
  end
23
16
  end
@@ -0,0 +1,14 @@
1
+ require_relative 'format/json'
2
+ require_relative 'format/yaml'
3
+
4
+ module Apitizer
5
+ module Connection
6
+ module Format
7
+ def self.build(name)
8
+ self.const_get(name.to_s.upcase).new
9
+ rescue NameError
10
+ raise Error, 'Unknown format'
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,9 +1,13 @@
1
1
  require 'json'
2
2
 
3
3
  module Apitizer
4
- module Processing
5
- module Parser
4
+ module Connection
5
+ module Format
6
6
  class JSON
7
+ def mime_type
8
+ 'application/json'
9
+ end
10
+
7
11
  def process(data)
8
12
  ::JSON.parse(data)
9
13
  rescue
@@ -1,9 +1,13 @@
1
1
  require 'yaml'
2
2
 
3
3
  module Apitizer
4
- module Processing
5
- module Parser
4
+ module Connection
5
+ module Format
6
6
  class YAML
7
+ def mime_type
8
+ 'application/x-yaml'
9
+ end
10
+
7
11
  def process(data)
8
12
  ::YAML.load(data)
9
13
  rescue