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
@@ -3,11 +3,11 @@ module Apitizer
3
3
  class Request
4
4
  extend Forwardable
5
5
 
6
- attr_reader :action, :path, :parameters
6
+ attr_reader :method, :path, :parameters
7
7
  def_delegator :path, :address
8
8
 
9
- def initialize(action:, path:, parameters: {})
10
- @action = action
9
+ def initialize(method:, path:, parameters: {})
10
+ @method = method
11
11
  @path = path
12
12
  @parameters = parameters
13
13
  end
@@ -1,11 +1,11 @@
1
1
  module Apitizer
2
2
  module Connection
3
3
  class Response
4
- attr_reader :code, :body
4
+ attr_reader :code, :content
5
5
 
6
- def initialize(code:, body:)
6
+ def initialize(code:, content:)
7
7
  @code = code
8
- @body = body
8
+ @content = content
9
9
  end
10
10
  end
11
11
  end
@@ -12,11 +12,11 @@ module Apitizer
12
12
  :delete => :delete
13
13
  },
14
14
  headers: {}
15
- }.freeze
15
+ }
16
16
 
17
- @actions = [ :index, :show, :create, :update, :delete ].freeze
18
- @collection_actions = [ :index, :create ].freeze
19
- @member_actions = [ :show, :update, :delete ].freeze
17
+ @actions = [ :index, :show, :create, :update, :delete ]
18
+ @collection_actions = [ :index, :create ]
19
+ @member_actions = [ :show, :update, :delete ]
20
20
 
21
21
  singleton_class.class_eval do
22
22
  attr_reader :defaults, :actions, :collection_actions, :member_actions
@@ -14,6 +14,10 @@ module Apitizer
14
14
  end
15
15
  end
16
16
 
17
+ def self.action_scope(action)
18
+ member_action?(action) ? :member : :collection
19
+ end
20
+
17
21
  def self.deep_merge(one, two)
18
22
  merger = Proc.new do |key, v1, v2|
19
23
  Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2
@@ -22,25 +26,45 @@ module Apitizer
22
26
  end
23
27
 
24
28
  def self.build_query(parameters)
25
- Rack::Utils.build_nested_query(prepare_parameters(parameters))
29
+ query = Rack::Utils.build_nested_query(prepare_parameters(parameters))
30
+ query.encode!('UTF-8')
26
31
  end
27
32
 
28
33
  private
29
34
 
30
- def self.prepare_parameters(parameters)
31
- # PATCH: https://github.com/rack/rack/issues/557
32
- Hash[
33
- parameters.map do |key, value|
34
- case value
35
- when Integer, TrueClass, FalseClass
36
- [ key, value.to_s ]
37
- when Hash
38
- [ key, prepare_parameters(value) ]
39
- else
40
- [ key, value ]
41
- end
35
+ def self.prepare_parameters(value)
36
+ # PATCH 1: https://github.com/rack/rack/issues/557
37
+ # PATCH 2: https://github.com/rack/rack/pull/698
38
+ case value
39
+ when NilClass, String
40
+ value
41
+ when Symbol, Integer, TrueClass, FalseClass
42
+ value.to_s
43
+ when Array
44
+ value = value.map { |v| prepare_parameters(v) }.compact
45
+ if value.any? { |v| v.is_a?(Hash) }
46
+ value = Hash[(0...value.length).to_a.zip(value)]
47
+ end
48
+ value.empty? ? nil : value
49
+ when Hash
50
+ value = Hash[
51
+ value.map do |k, v|
52
+ v = prepare_parameters(v)
53
+ v.nil? ? nil : [ k, v ]
54
+ end.compact
55
+ ]
56
+ value.empty? ? nil : value
57
+ else
58
+ if value.respond_to?(:to_a)
59
+ prepare_parameters(value.to_a)
60
+ elsif value.respond_to?(:to_h)
61
+ prepare_parameters(value.to_h)
62
+ else
63
+ raise ArgumentError, 'Unknown parameter class'
42
64
  end
43
- ]
65
+ end
44
66
  end
67
+
68
+ private_class_method :prepare_parameters
45
69
  end
46
70
  end
@@ -6,8 +6,8 @@ module Apitizer
6
6
  def_delegator :@response, :code
7
7
  def_delegators :__getobj__, :is_a?, :kind_of?, :instance_of?
8
8
 
9
- def initialize(request:, response:, content:)
10
- super(content)
9
+ def initialize(request:, response:)
10
+ super(response.content)
11
11
  @request = request
12
12
  @response = response
13
13
  end
@@ -1,7 +1,7 @@
1
1
  require_relative 'routing/path'
2
2
  require_relative 'routing/node'
3
3
  require_relative 'routing/proxy'
4
- require_relative 'routing/mapper'
4
+ require_relative 'routing/map'
5
5
 
6
6
  module Apitizer
7
7
  module Routing
@@ -1,6 +1,6 @@
1
1
  module Apitizer
2
2
  module Routing
3
- class Mapper
3
+ class Map
4
4
  extend Forwardable
5
5
 
6
6
  def_delegator :@root, :define_address
@@ -11,8 +11,8 @@ module Apitizer
11
11
  end
12
12
 
13
13
  def trace(action, *arguments)
14
- path = @root.trace(*arguments)
15
- raise Error, 'Not permitted' unless path.permitted?(action)
14
+ path = @root.trace(*arguments) or raise Error, 'Not found'
15
+ raise Error, 'Not permitted' unless path.permit?(action)
16
16
  path
17
17
  end
18
18
 
@@ -21,13 +21,6 @@ module Apitizer
21
21
  proxy.instance_eval(&block)
22
22
  end
23
23
 
24
- def define_scope(path, parent: @root, &block)
25
- child = Node::Scope.new(path)
26
- parent.append(child)
27
- proxy = Proxy.new(self, parent: child)
28
- proxy.instance_eval(&block)
29
- end
30
-
31
24
  def define_resources(name, parent: @root, **options, &block)
32
25
  child = Node::Collection.new(name, **options)
33
26
  parent.append(child)
@@ -1,5 +1,4 @@
1
1
  require_relative 'node/base'
2
2
  require_relative 'node/root'
3
- require_relative 'node/scope'
4
3
  require_relative 'node/collection'
5
4
  require_relative 'node/operation'
@@ -7,36 +7,34 @@ module Apitizer
7
7
  end
8
8
 
9
9
  def trace(steps, path = Path.new)
10
- process(path, steps)
11
- advance(path)
10
+ return nil unless recognize?(steps)
12
11
 
12
+ steps, path = steps.clone, path.clone
13
+
14
+ walk(steps, path)
13
15
  return path if steps.empty?
14
16
 
15
- child = lookup(steps.first) or raise Error, 'Not found'
16
- child.trace(steps, path)
17
- end
17
+ children.each do |child|
18
+ branch = child.trace(steps, path)
19
+ return branch if branch
20
+ end
18
21
 
19
- def match(name)
22
+ nil
20
23
  end
21
24
 
22
- def process(path, steps)
25
+ def recognize?(steps)
23
26
  end
24
27
 
25
- def permitted?(action, path)
28
+ def permit?(action, on:)
26
29
  end
27
30
 
28
- protected
29
-
30
- def children
31
- @children ||= []
32
- end
31
+ private
33
32
 
34
- def lookup(name)
35
- children.find { |c| c.match(name) }
33
+ def walk(steps, path)
36
34
  end
37
35
 
38
- def advance(path)
39
- path.advance(self)
36
+ def children
37
+ @children ||= []
40
38
  end
41
39
  end
42
40
  end
@@ -2,31 +2,32 @@ module Apitizer
2
2
  module Routing
3
3
  module Node
4
4
  class Collection < Base
5
- def initialize(name, only: nil)
5
+ def initialize(name, only: nil, except: [])
6
6
  @name = name
7
- @actions = only && Array(only) || Apitizer.actions
8
- unless (@actions - Apitizer.actions).empty?
9
- raise Error, 'Not supported'
10
- end
7
+ @actions = (only && Array(only) || Apitizer.actions) - Array(except)
11
8
  end
12
9
 
13
- def match(name)
14
- @name == name
10
+ def recognize?(steps)
11
+ @name == steps.first
15
12
  end
16
13
 
17
- def process(path, steps)
18
- path << steps.shift # @name
19
- return path if steps.empty?
20
- path << steps.shift # id
14
+ def permit?(action, on:)
15
+ @actions.include?(action) && on == Helper.action_scope(action)
21
16
  end
22
17
 
23
- def permitted?(action, path)
24
- return false unless @actions.include?(action)
18
+ private
19
+
20
+ def walk(steps, path)
21
+ path.advance(steps.shift, node: self, on: :collection)
25
22
 
26
- id_present = path.steps.last != @name
27
- member_action = Helper.member_action?(action)
23
+ return if steps.empty?
24
+
25
+ children.each do |child|
26
+ next unless child.respond_to?(:on?) && child.on?(:collection)
27
+ return if child.recognize?(steps)
28
+ end
28
29
 
29
- id_present == member_action
30
+ path.advance(steps.shift, node: self, on: :member)
30
31
  end
31
32
  end
32
33
  end
@@ -2,29 +2,28 @@ module Apitizer
2
2
  module Routing
3
3
  module Node
4
4
  class Operation < Base
5
- def initialize(name, action:, on:, **options)
6
- # TODO: how about on == :collection?
7
- unless Apitizer.actions.include?(action) && on == :member
8
- raise Error, 'Not supported'
9
- end
5
+ def initialize(name, action:, on:)
10
6
  @name = name
11
7
  @action = action
8
+ @on = on
12
9
  end
13
10
 
14
- def match(name)
15
- if @name.is_a?(String) && @name =~ /^:/
16
- true
17
- else
18
- @name == name
19
- end
11
+ def recognize?(steps)
12
+ @name == steps.first || @name.to_s =~ /^:/
20
13
  end
21
14
 
22
- def process(path, steps)
23
- path << steps.shift # @name
15
+ def permit?(action, on:)
16
+ @action == action && @on == on
24
17
  end
25
18
 
26
- def permitted?(action, path)
27
- @action == action
19
+ def on?(on)
20
+ @on == on
21
+ end
22
+
23
+ private
24
+
25
+ def walk(steps, path)
26
+ path.advance(steps.shift, node: self, on: @on)
28
27
  end
29
28
  end
30
29
  end
@@ -2,12 +2,18 @@ module Apitizer
2
2
  module Routing
3
3
  module Node
4
4
  class Root < Base
5
+ def recognize?(steps)
6
+ true
7
+ end
8
+
5
9
  def define_address(address, *_)
6
10
  @address = address
7
11
  end
8
12
 
9
- def process(path, steps)
10
- path << @address unless @address.nil?
13
+ private
14
+
15
+ def walk(steps, path)
16
+ path.advance(@address, node: self) if @address
11
17
  end
12
18
  end
13
19
  end
@@ -1,25 +1,33 @@
1
1
  module Apitizer
2
2
  module Routing
3
3
  class Path
4
- extend Forwardable
5
-
6
4
  attr_reader :steps, :node
7
- def_delegators :steps, :<<
8
5
 
9
- def initialize
10
- @steps = []
6
+ def initialize(steps: [], node: nil)
7
+ @steps = steps
8
+ @node = node
11
9
  end
12
10
 
13
11
  def address
14
12
  @steps.map(&:to_s).join('/')
15
13
  end
16
14
 
17
- def advance(node)
15
+ def advance(step, node:, on: nil)
16
+ @steps << step
18
17
  @node = node
18
+ @on = on
19
+ end
20
+
21
+ def permit?(action)
22
+ @node && @node.permit?(action, on: @on)
23
+ end
24
+
25
+ def on?(on)
26
+ @on == on
19
27
  end
20
28
 
21
- def permitted?(action)
22
- @node && @node.permitted?(action, self)
29
+ def clone
30
+ self.class.new(steps: @steps.clone, node: @node)
23
31
  end
24
32
  end
25
33
  end
@@ -1,3 +1,3 @@
1
1
  module Apitizer
2
- VERSION = '0.0.1'
2
+ VERSION = '0.0.2'
3
3
  end
@@ -1,13 +1,13 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Apitizer::Base do
3
+ RSpec.describe Apitizer::Base do
4
4
  extend ResourceHelper
5
5
 
6
6
  let(:subject_class) { Apitizer::Base }
7
7
  let(:address) { 'https://service.com/api' }
8
8
 
9
- def stub_request(method, address)
10
- stub_http_request(method, "https://service.com/api/#{ address }").
9
+ def stub_request(method, path)
10
+ stub_http_request(method, "#{ address }/#{ path }").
11
11
  to_return(code: '200', body: '{}')
12
12
  end
13
13
 
@@ -17,16 +17,20 @@ describe Apitizer::Base do
17
17
  end
18
18
 
19
19
  it 'draws a routing map when a block is given' do
20
- scope_name = address
21
- subject = subject_class.new { scope(scope_name) { resources(:articles) } }
20
+ prefix = address
21
+ subject = subject_class.new do
22
+ address(prefix)
23
+ resources(:articles)
24
+ end
22
25
  stub_request(:get, 'articles')
23
26
  expect { subject.process(:index, :articles) }.not_to raise_error
24
27
  end
25
28
 
26
29
  it 'customizes the mapping between the REST actions and HTTP verbs' do
27
- scope_name = address
30
+ prefix = address
28
31
  subject = subject_class.new(dictionary: { update: :delete }) do
29
- scope(scope_name) { resources(:articles) }
32
+ address(prefix)
33
+ resources(:articles)
30
34
  end
31
35
  stub = stub_request(:delete, 'articles/xxx')
32
36
  subject.process(:update, :articles, 'xxx')
@@ -34,37 +38,41 @@ describe Apitizer::Base do
34
38
  end
35
39
  end
36
40
 
37
- describe '#process' do
38
- subject do
39
- scope_name = address
40
- subject_class.new { scope(scope_name) { resources(:articles) } }
41
- end
42
-
43
- restful_collection_actions.each do |action|
44
- method = rest_http_dictionary[action]
45
-
46
- it "is capable of #{ action } actions" do
47
- stub_request(method, 'articles')
48
- expect { subject.process(action, :articles) }.not_to raise_error
41
+ describe '#define' do
42
+ it 'is another way of drawing a routing map' do
43
+ prefix = address
44
+ subject = subject_class.new
45
+ subject.define do
46
+ address(prefix)
47
+ resources(:articles)
49
48
  end
49
+ stub_request(:get, 'articles')
50
+ expect { subject.process(:index, :articles) }.not_to raise_error
51
+ end
52
+ end
50
53
 
51
- it "is capable of #{ action } actions via alias" do
52
- stub_request(method, 'articles')
53
- expect { subject.send(action, :articles) }.not_to raise_error
54
+ describe '#process' do
55
+ subject do
56
+ prefix = address
57
+ subject_class.new do
58
+ address(prefix)
59
+ resources(:articles)
54
60
  end
55
61
  end
56
62
 
57
- restful_member_actions.each do |action|
63
+ restful_actions.each do |action|
58
64
  method = rest_http_dictionary[action]
65
+ steps = [ :articles ]
66
+ steps << 'xxx' if restful_member_actions.include?(action)
59
67
 
60
68
  it "is capable of #{ action } actions" do
61
- stub_request(method, 'articles/xxx')
62
- expect { subject.process(action, :articles, 'xxx') }.not_to raise_error
69
+ stub_request(method, steps.join('/'))
70
+ expect { subject.process(action, *steps) }.not_to raise_error
63
71
  end
64
72
 
65
- it "is capable of #{ action } actions via alias" do
66
- stub_request(method, 'articles/xxx')
67
- expect { subject.send(action, :articles, 'xxx') }.not_to raise_error
73
+ it "is capable of #{ action } actions via the corresponding alias" do
74
+ stub_request(method, steps.join('/'))
75
+ expect { subject.send(action, *steps) }.not_to raise_error
68
76
  end
69
77
  end
70
78
  end