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