apitizer 0.0.1

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 +7 -0
  2. data/.gitignore +5 -0
  3. data/.rspec +2 -0
  4. data/CHANGELOG.md +1 -0
  5. data/Gemfile +3 -0
  6. data/Guardfile +7 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +91 -0
  9. data/Rakefile +7 -0
  10. data/apitizer.gemspec +32 -0
  11. data/lib/apitizer.rb +14 -0
  12. data/lib/apitizer/base.rb +61 -0
  13. data/lib/apitizer/connection.rb +10 -0
  14. data/lib/apitizer/connection/adaptor.rb +13 -0
  15. data/lib/apitizer/connection/adaptor/standard.rb +34 -0
  16. data/lib/apitizer/connection/dispatcher.rb +24 -0
  17. data/lib/apitizer/connection/request.rb +16 -0
  18. data/lib/apitizer/connection/response.rb +12 -0
  19. data/lib/apitizer/core.rb +24 -0
  20. data/lib/apitizer/helper.rb +46 -0
  21. data/lib/apitizer/processing.rb +8 -0
  22. data/lib/apitizer/processing/parser.rb +14 -0
  23. data/lib/apitizer/processing/parser/json.rb +15 -0
  24. data/lib/apitizer/processing/parser/yaml.rb +15 -0
  25. data/lib/apitizer/processing/translator.rb +13 -0
  26. data/lib/apitizer/result.rb +15 -0
  27. data/lib/apitizer/routing.rb +10 -0
  28. data/lib/apitizer/routing/mapper.rb +47 -0
  29. data/lib/apitizer/routing/node.rb +5 -0
  30. data/lib/apitizer/routing/node/base.rb +44 -0
  31. data/lib/apitizer/routing/node/collection.rb +34 -0
  32. data/lib/apitizer/routing/node/operation.rb +32 -0
  33. data/lib/apitizer/routing/node/root.rb +15 -0
  34. data/lib/apitizer/routing/node/scope.rb +19 -0
  35. data/lib/apitizer/routing/path.rb +26 -0
  36. data/lib/apitizer/routing/proxy.rb +17 -0
  37. data/lib/apitizer/version.rb +3 -0
  38. data/spec/apitizer/base_spec.rb +71 -0
  39. data/spec/apitizer/connection/adaptor_spec.rb +24 -0
  40. data/spec/apitizer/connection/dispatcher_spec.rb +39 -0
  41. data/spec/apitizer/helper_spec.rb +87 -0
  42. data/spec/apitizer/processing/parser_spec.rb +23 -0
  43. data/spec/apitizer/result_spec.rb +19 -0
  44. data/spec/apitizer/routing/mapper_spec.rb +80 -0
  45. data/spec/apitizer/routing/node_spec.rb +63 -0
  46. data/spec/apitizer/routing/path_spec.rb +102 -0
  47. data/spec/spec_helper.rb +11 -0
  48. data/spec/support/factory_helper.rb +23 -0
  49. data/spec/support/resource_helper.rb +18 -0
  50. metadata +203 -0
@@ -0,0 +1,8 @@
1
+ require_relative 'processing/parser'
2
+ require_relative 'processing/translator'
3
+
4
+ module Apitizer
5
+ module Processing
6
+ Error = Class.new(Apitizer::Error)
7
+ end
8
+ end
@@ -0,0 +1,14 @@
1
+ require_relative 'parser/json'
2
+ require_relative 'parser/yaml'
3
+
4
+ module Apitizer
5
+ module Processing
6
+ module Parser
7
+ def self.build(format)
8
+ self.const_get(format.to_s.upcase).new
9
+ rescue NameError
10
+ raise Error, 'Unknown format'
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ require 'json'
2
+
3
+ module Apitizer
4
+ module Processing
5
+ module Parser
6
+ class JSON
7
+ def process(data)
8
+ ::JSON.parse(data)
9
+ rescue
10
+ raise Error, 'Unable to parse'
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ require 'yaml'
2
+
3
+ module Apitizer
4
+ module Processing
5
+ module Parser
6
+ class YAML
7
+ def process(data)
8
+ ::YAML.load(data)
9
+ rescue
10
+ raise Error, 'Unable to parse'
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ module Apitizer
2
+ module Processing
3
+ class Translator
4
+ def initialize(format:)
5
+ @parser = Parser.build(format)
6
+ end
7
+
8
+ def process(response)
9
+ @parser.process(response.body)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ module Apitizer
2
+ class Result < SimpleDelegator
3
+ extend Forwardable
4
+
5
+ def_delegator :@request, :path
6
+ def_delegator :@response, :code
7
+ def_delegators :__getobj__, :is_a?, :kind_of?, :instance_of?
8
+
9
+ def initialize(request:, response:, content:)
10
+ super(content)
11
+ @request = request
12
+ @response = response
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ require_relative 'routing/path'
2
+ require_relative 'routing/node'
3
+ require_relative 'routing/proxy'
4
+ require_relative 'routing/mapper'
5
+
6
+ module Apitizer
7
+ module Routing
8
+ Error = Class.new(Apitizer::Error)
9
+ end
10
+ end
@@ -0,0 +1,47 @@
1
+ module Apitizer
2
+ module Routing
3
+ class Mapper
4
+ extend Forwardable
5
+
6
+ def_delegator :@root, :define_address
7
+
8
+ def initialize(&block)
9
+ @root = Node::Root.new
10
+ define(&block) if block_given?
11
+ end
12
+
13
+ def trace(action, *arguments)
14
+ path = @root.trace(*arguments)
15
+ raise Error, 'Not permitted' unless path.permitted?(action)
16
+ path
17
+ end
18
+
19
+ def define(&block)
20
+ proxy = Proxy.new(self)
21
+ proxy.instance_eval(&block)
22
+ end
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
+ def define_resources(name, parent: @root, **options, &block)
32
+ child = Node::Collection.new(name, **options)
33
+ parent.append(child)
34
+ return unless block_given?
35
+ proxy = Proxy.new(self, parent: child)
36
+ proxy.instance_eval(&block)
37
+ end
38
+
39
+ Apitizer.actions.each do |action|
40
+ define_method "define_#{ action }" do |name, parent:, **options|
41
+ child = Node::Operation.new(name, action: action, **options)
42
+ parent.append(child)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,5 @@
1
+ require_relative 'node/base'
2
+ require_relative 'node/root'
3
+ require_relative 'node/scope'
4
+ require_relative 'node/collection'
5
+ require_relative 'node/operation'
@@ -0,0 +1,44 @@
1
+ module Apitizer
2
+ module Routing
3
+ module Node
4
+ class Base
5
+ def append(child)
6
+ children << child
7
+ end
8
+
9
+ def trace(steps, path = Path.new)
10
+ process(path, steps)
11
+ advance(path)
12
+
13
+ return path if steps.empty?
14
+
15
+ child = lookup(steps.first) or raise Error, 'Not found'
16
+ child.trace(steps, path)
17
+ end
18
+
19
+ def match(name)
20
+ end
21
+
22
+ def process(path, steps)
23
+ end
24
+
25
+ def permitted?(action, path)
26
+ end
27
+
28
+ protected
29
+
30
+ def children
31
+ @children ||= []
32
+ end
33
+
34
+ def lookup(name)
35
+ children.find { |c| c.match(name) }
36
+ end
37
+
38
+ def advance(path)
39
+ path.advance(self)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,34 @@
1
+ module Apitizer
2
+ module Routing
3
+ module Node
4
+ class Collection < Base
5
+ def initialize(name, only: nil)
6
+ @name = name
7
+ @actions = only && Array(only) || Apitizer.actions
8
+ unless (@actions - Apitizer.actions).empty?
9
+ raise Error, 'Not supported'
10
+ end
11
+ end
12
+
13
+ def match(name)
14
+ @name == name
15
+ end
16
+
17
+ def process(path, steps)
18
+ path << steps.shift # @name
19
+ return path if steps.empty?
20
+ path << steps.shift # id
21
+ end
22
+
23
+ def permitted?(action, path)
24
+ return false unless @actions.include?(action)
25
+
26
+ id_present = path.steps.last != @name
27
+ member_action = Helper.member_action?(action)
28
+
29
+ id_present == member_action
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,32 @@
1
+ module Apitizer
2
+ module Routing
3
+ module Node
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
10
+ @name = name
11
+ @action = action
12
+ end
13
+
14
+ def match(name)
15
+ if @name.is_a?(String) && @name =~ /^:/
16
+ true
17
+ else
18
+ @name == name
19
+ end
20
+ end
21
+
22
+ def process(path, steps)
23
+ path << steps.shift # @name
24
+ end
25
+
26
+ def permitted?(action, path)
27
+ @action == action
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,15 @@
1
+ module Apitizer
2
+ module Routing
3
+ module Node
4
+ class Root < Base
5
+ def define_address(address, *_)
6
+ @address = address
7
+ end
8
+
9
+ def process(path, steps)
10
+ path << @address unless @address.nil?
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ module Apitizer
2
+ module Routing
3
+ module Node
4
+ class Scope < Base
5
+ def initialize(steps)
6
+ @steps = Array(steps)
7
+ end
8
+
9
+ def match(name)
10
+ not lookup(name).nil?
11
+ end
12
+
13
+ def process(path, steps)
14
+ @steps.each { |step| path << step }
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,26 @@
1
+ module Apitizer
2
+ module Routing
3
+ class Path
4
+ extend Forwardable
5
+
6
+ attr_reader :steps, :node
7
+ def_delegators :steps, :<<
8
+
9
+ def initialize
10
+ @steps = []
11
+ end
12
+
13
+ def address
14
+ @steps.map(&:to_s).join('/')
15
+ end
16
+
17
+ def advance(node)
18
+ @node = node
19
+ end
20
+
21
+ def permitted?(action)
22
+ @node && @node.permitted?(action, self)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,17 @@
1
+ module Apitizer
2
+ module Routing
3
+ class Proxy
4
+ def initialize(owner, **options)
5
+ @owner = owner
6
+ @options = options
7
+ end
8
+
9
+ def method_missing(name, *arguments, **options, &block)
10
+ name = :"define_#{ name }"
11
+ return super unless @owner.respond_to?(name)
12
+ # NOTE: https://bugs.ruby-lang.org/issues/9776
13
+ @owner.send(name, *arguments, **options, **@options, &block)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ module Apitizer
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+
3
+ describe Apitizer::Base do
4
+ extend ResourceHelper
5
+
6
+ let(:subject_class) { Apitizer::Base }
7
+ let(:address) { 'https://service.com/api' }
8
+
9
+ def stub_request(method, address)
10
+ stub_http_request(method, "https://service.com/api/#{ address }").
11
+ to_return(code: '200', body: '{}')
12
+ end
13
+
14
+ describe '#new' do
15
+ it 'does not require any arguments' do
16
+ expect { subject_class.new }.not_to raise_error
17
+ end
18
+
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) } }
22
+ stub_request(:get, 'articles')
23
+ expect { subject.process(:index, :articles) }.not_to raise_error
24
+ end
25
+
26
+ it 'customizes the mapping between the REST actions and HTTP verbs' do
27
+ scope_name = address
28
+ subject = subject_class.new(dictionary: { update: :delete }) do
29
+ scope(scope_name) { resources(:articles) }
30
+ end
31
+ stub = stub_request(:delete, 'articles/xxx')
32
+ subject.process(:update, :articles, 'xxx')
33
+ expect(stub).to have_been_requested
34
+ end
35
+ end
36
+
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
49
+ end
50
+
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
+ end
55
+ end
56
+
57
+ restful_member_actions.each do |action|
58
+ method = rest_http_dictionary[action]
59
+
60
+ it "is capable of #{ action } actions" do
61
+ stub_request(method, 'articles/xxx')
62
+ expect { subject.process(action, :articles, 'xxx') }.not_to raise_error
63
+ end
64
+
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
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe Apitizer::Connection::Adaptor do
4
+ let(:parent_module) { Apitizer::Connection }
5
+ let(:address) { 'https://service.com/api/v1/json/articles' }
6
+
7
+ [ 'Standard' ].each do |adaptor|
8
+ subject { parent_module::Adaptor.const_get(adaptor).new }
9
+
10
+ describe "#{ adaptor }.process" do
11
+ it 'returns the code, headers, and body of the response' do
12
+ stub_http_request(:get, address).to_return(
13
+ code: '200', body: 'Hej!', headers: { 'a' => 'b' } )
14
+ response = subject.process(:get, address)
15
+ expect(response).to eq([ '200', { 'a' => [ 'b' ] }, 'Hej!' ])
16
+ end
17
+
18
+ it 'raises exceptions when encounters unknown methods' do
19
+ expect { subject.process(:smile, address) }.to \
20
+ raise_error(parent_module::Error, /Invalid method/i)
21
+ end
22
+ end
23
+ end
24
+ end