apitizer 0.0.1 → 0.0.2

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.
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