pdc 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +27 -0
  3. data/.gitignore +10 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +21 -0
  7. data/CODE_OF_CONDUCT.md +49 -0
  8. data/Gemfile +32 -0
  9. data/Guardfile +36 -0
  10. data/LICENSE +21 -0
  11. data/LICENSE.txt +21 -0
  12. data/README.md +41 -0
  13. data/Rakefile +11 -0
  14. data/bin/console +11 -0
  15. data/bin/setup +11 -0
  16. data/docs/.gitignore +3 -0
  17. data/docs/LICENSE_sphinx_deployment +27 -0
  18. data/docs/Makefile +179 -0
  19. data/docs/source/conf.py +257 -0
  20. data/docs/source/example.rst +10 -0
  21. data/docs/source/index.rst +23 -0
  22. data/docs/sphinx_deployment.mk +117 -0
  23. data/examples/active_attr.rb +33 -0
  24. data/examples/http_failures.rb +18 -0
  25. data/examples/local_pdc_dev.rb +15 -0
  26. data/examples/logger.rb +50 -0
  27. data/examples/pdc_curb_access_token.rb +36 -0
  28. data/examples/pdc_resource_tests.rb +173 -0
  29. data/examples/pdc_test_cache.rb +48 -0
  30. data/examples/prod_failures.rb +26 -0
  31. data/examples/prod_pdc.rb +14 -0
  32. data/lib/pdc/base.rb +14 -0
  33. data/lib/pdc/config.rb +157 -0
  34. data/lib/pdc/errors.rb +8 -0
  35. data/lib/pdc/http/errors.rb +43 -0
  36. data/lib/pdc/http/request/append_slash.rb +19 -0
  37. data/lib/pdc/http/request.rb +12 -0
  38. data/lib/pdc/http/response/pagination.rb +43 -0
  39. data/lib/pdc/http/response/parser.rb +62 -0
  40. data/lib/pdc/http/response/raise_error.rb +13 -0
  41. data/lib/pdc/http/response.rb +3 -0
  42. data/lib/pdc/http/result.rb +28 -0
  43. data/lib/pdc/http.rb +12 -0
  44. data/lib/pdc/logger.rb +19 -0
  45. data/lib/pdc/resource/attribute_modifier.rb +43 -0
  46. data/lib/pdc/resource/attribute_store.rb +22 -0
  47. data/lib/pdc/resource/attributes.rb +144 -0
  48. data/lib/pdc/resource/errors.rb +3 -0
  49. data/lib/pdc/resource/identity.rb +75 -0
  50. data/lib/pdc/resource/path.rb +63 -0
  51. data/lib/pdc/resource/per_thread_registry.rb +54 -0
  52. data/lib/pdc/resource/relation/finder.rb +24 -0
  53. data/lib/pdc/resource/relation/pagination.rb +33 -0
  54. data/lib/pdc/resource/relation/query.rb +14 -0
  55. data/lib/pdc/resource/relation.rb +81 -0
  56. data/lib/pdc/resource/rest_api.rb +34 -0
  57. data/lib/pdc/resource/scope_registry.rb +19 -0
  58. data/lib/pdc/resource/scopes.rb +29 -0
  59. data/lib/pdc/resource/value_parser.rb +31 -0
  60. data/lib/pdc/resource/wip.rb +0 -0
  61. data/lib/pdc/resource.rb +12 -0
  62. data/lib/pdc/v1/arch.rb +6 -0
  63. data/lib/pdc/v1/product.rb +5 -0
  64. data/lib/pdc/v1/release.rb +18 -0
  65. data/lib/pdc/v1/release_variant.rb +17 -0
  66. data/lib/pdc/v1.rb +8 -0
  67. data/lib/pdc/version.rb +3 -0
  68. data/lib/pdc.rb +25 -0
  69. data/pdc.gemspec +38 -0
  70. data/spec/fixtures/vcr/_page_count_returns_total_count.yml +141 -0
  71. data/spec/fixtures/vcr/brew_can_be_nil.yml +61 -0
  72. data/spec/fixtures/vcr/brew_may_be_present.yml +50 -0
  73. data/spec/fixtures/vcr/caches_multiple_response.yml +173 -0
  74. data/spec/fixtures/vcr/caches_response_with_a_query.yml +64 -0
  75. data/spec/fixtures/vcr/caches_response_with_multiple_query.yml +64 -0
  76. data/spec/fixtures/vcr/caches_response_without_query.yml +61 -0
  77. data/spec/fixtures/vcr/can_iterate_using_each.yml +187 -0
  78. data/spec/fixtures/vcr/fetches_variants_of_a_release.yml +663 -0
  79. data/spec/fixtures/vcr/must_return_number_of_resources.yml +61 -0
  80. data/spec/fixtures/vcr/preserves_the_filters.yml +49 -0
  81. data/spec/fixtures/vcr/returns_resources_on_that_page.yml +49 -0
  82. data/spec/fixtures/vcr/returns_the_total_count_and_not_items_in_page.yml +135 -0
  83. data/spec/fixtures/vcr/should_not_be_in_the_list_of_attributes.yml +95 -0
  84. data/spec/fixtures/vcr/works_with_where.yml +49 -0
  85. data/spec/pdc/config_spec.rb +115 -0
  86. data/spec/pdc/http/errors_spec.rb +58 -0
  87. data/spec/pdc/resource/attributes_spec.rb +231 -0
  88. data/spec/pdc/resource/cache_spec.rb +88 -0
  89. data/spec/pdc/resource/count_spec.rb +45 -0
  90. data/spec/pdc/resource/identity_spec.rb +96 -0
  91. data/spec/pdc/resource/pagination_spec.rb +51 -0
  92. data/spec/pdc/resource/path_spec.rb +30 -0
  93. data/spec/pdc/resource/relation_spec.rb +94 -0
  94. data/spec/pdc/resource/rest_api_spec.rb +23 -0
  95. data/spec/pdc/resource/value_parser_spec.rb +15 -0
  96. data/spec/pdc/resource/wip_spec.rb +0 -0
  97. data/spec/pdc/v1/arch_spec.rb +34 -0
  98. data/spec/pdc/v1/release_spec.rb +54 -0
  99. data/spec/pdc/version_spec.rb +5 -0
  100. data/spec/spec_helper.rb +39 -0
  101. data/spec/support/fixtures.rb +116 -0
  102. data/spec/support/vcr.rb +10 -0
  103. data/spec/support/webmock.rb +34 -0
  104. metadata +295 -0
@@ -0,0 +1,173 @@
1
+ require_relative '../lib/pdc'
2
+ require_relative '../spec/spec_helper'
3
+
4
+ WebMock.disable! # this uses real server
5
+
6
+ #### service config ####
7
+ def token
8
+ script_path = File.expand_path(File.dirname(__FILE__))
9
+ token_path = File.join(script_path, '.token', 'pdc.prod')
10
+ File.read(token_path).chomp.tap { |x| puts "Using token :#{x.ai}" }
11
+ rescue Errno::ENOENT => e
12
+ puts "Hey! did you forget to create #{token_path.ai} \n".red
13
+ raise e
14
+ end
15
+
16
+ #### server config ####
17
+
18
+ server = {
19
+ local: {
20
+ site: 'http://localhost:8000',
21
+ token: nil
22
+ },
23
+
24
+ prod: {
25
+ site: 'https://pdc.engineering.redhat.com',
26
+ token: ->() { token } # read token only if needed
27
+ }
28
+ }
29
+
30
+ # server to use
31
+ pdc = server[:prod]
32
+ pdc = server[:local]
33
+
34
+ PDC.configure do |config|
35
+ config.site = pdc[:site]
36
+ auth_token = pdc[:token]
37
+ config.token = pdc[:token].call if auth_token
38
+ config.requires_token = auth_token.present? # to_bool
39
+ end
40
+
41
+ #### product ####
42
+
43
+ describe PDC::V1::Product do
44
+ let(:product_class) { PDC::V1::Product }
45
+ it 'has_many product_versions' do
46
+ p1 = product_class.scoped.first
47
+ p1.product_versions.must_be_instance_of Array
48
+ # TODO: continue the after release and release-variants
49
+ end
50
+ end
51
+
52
+ #### Arch ####
53
+
54
+ describe PDC::V1::Arch do
55
+ let(:arch_class) { PDC::V1::Arch }
56
+
57
+ describe 'contents!' do
58
+ it 'only returns contents of a page' do
59
+ arches = arch_class.scoped.contents!
60
+ (0..arch_class.count).must_include arches.length
61
+
62
+ arches_pg1 = arch_class.page(1).contents!
63
+ arches_pg1.must_equal arches
64
+
65
+ arches_pg2 = arch_class.page(2).contents!
66
+ arches_pg1.wont_equal arches_pg2
67
+ end
68
+ end
69
+
70
+ describe '#page' do
71
+ it 'can access page(2)' do
72
+ arch_class.page(2).must_be_instance_of PDC::Resource::Relation
73
+ end
74
+
75
+ it 'must return resource on page(2).to_a' do
76
+ arches = arch_class.page(2).to_a
77
+ arches.first.must_be_instance_of arch_class
78
+ end
79
+ end
80
+
81
+ describe '#each_page' do
82
+ it 'must be able to iterate using map' do
83
+ resource_count = arch_class.count
84
+
85
+ arches = arch_class.scoped.map(&:name)
86
+ arches.must_be_instance_of Array
87
+ arches.first.must_be_instance_of String
88
+
89
+ arches.count.must_equal resource_count
90
+ end
91
+ end
92
+
93
+ describe '#page_size' do
94
+ it 'must return all records when page_size is -1' do
95
+ all_arches = arch_class.page_size(-1).to_a
96
+ all_arches_count = arch_class.count
97
+ all_arches.must_be_instance_of Array
98
+ all_arches.length.must_equal all_arches_count
99
+ end
100
+
101
+ it 'all! is the same as page_size(-1)' do
102
+ all_arches = arch_class.all!
103
+ all_arches_count = arch_class.count
104
+ all_arches.must_be_instance_of Array
105
+ all_arches.length.must_equal all_arches_count
106
+ end
107
+ end
108
+
109
+ describe '#find' do
110
+ end
111
+ end
112
+
113
+ ### TODO: debug - remove this
114
+ A = PDC::V1::Arch
115
+ R = PDC::V1::Release
116
+ RV = PDC::V1::ReleaseVariant
117
+ ###
118
+
119
+ describe PDC::V1::Release do
120
+ let(:release_class) { PDC::V1::Release }
121
+ let(:existing_release) { release_class.all!.first }
122
+
123
+ describe '#find' do
124
+ it 'must return a record' do
125
+ found = release_class.find(existing_release.release_id)
126
+ found.must_be_instance_of release_class
127
+ found.release_id.must_equal existing_release.release_id
128
+ end
129
+ end
130
+
131
+ describe '#variants' do
132
+ let(:sat_6_at_rhel_7) { release_class.find('satellite-6.0.4@rhel-7') }
133
+ let(:release_variant_class) { PDC::V1::ReleaseVariant }
134
+
135
+ it 'must be a HasMany association' do
136
+ skip 'TODO implement Assocations'
137
+ variants_relation = sat_6_at_rhel_7.variants
138
+ variants_relation.must_be_instance_of PDC::Resource::HasManyAssociation
139
+ end
140
+
141
+ it 'must be able to call all! to return all variants ' do
142
+ variants_relation = sat_6_at_rhel_7.variants
143
+ ap variants_relation.params
144
+ release_id = variants_relation.params[:release]
145
+ release_id.must_equal sat_6_at_rhel_7.id
146
+
147
+ all_variants = variants_relation.all!
148
+ expected = variants_relation.where(page_size: -1).to_a
149
+
150
+ all_variants.must_equal expected
151
+ end
152
+
153
+ it 'must return release variants for a release' do
154
+ variants_relation = sat_6_at_rhel_7.variants
155
+ variants = variants_relation.to_a
156
+ variants.must_be_instance_of Array
157
+ variants.length.must_be :>=, 1
158
+ variants.first.must_be_instance_of release_variant_class
159
+ end
160
+ end
161
+ end
162
+
163
+ describe PDC::V1::ReleaseVariant do
164
+ let(:release_class) { PDC::V1::Release }
165
+ let(:release_variant_class) { PDC::V1::ReleaseVariant }
166
+ let(:a_variant) { release_variant_class.all!.first }
167
+
168
+ describe '#release' do
169
+ it 'must fetch the release it belongs to' do
170
+ a_variant.release.must_be_instance_of release_class
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,48 @@
1
+ require 'benchmark'
2
+ require_relative '../lib/pdc'
3
+ require_relative '../spec/spec_helper'
4
+
5
+ WebMock.disable!
6
+ PDC.configure do |config|
7
+ # dev server
8
+ config.site = 'https://pdc.host.dev.eng.pek2.redhat.com/'
9
+ config.cache_store = ActiveSupport::Cache.lookup_store(
10
+ :file_store, [File.join(ENV['TMPDIR'] || '/tmp', 'cache')])
11
+ config.log_level = :info
12
+ end
13
+
14
+ ActiveSupport::Notifications.subscribe "http_cache.faraday" do |*args|
15
+ event = ActiveSupport::Notifications::Event.new(*args)
16
+ puts " >>> ".yellow + "cache: #{event.payload[:cache_status]}"
17
+ ap event.payload
18
+ end
19
+
20
+ def benchmark(description, opts = {}, &block)
21
+ puts "Running: #{description}"
22
+
23
+ initial = Benchmark.measure(&block)
24
+ repeat = Benchmark.measure(&block)
25
+
26
+ puts "Initial : #{initial.to_s.chomp} >> #{initial.real.round(2).ai}"
27
+ puts "Repeat : #{repeat.to_s.chomp} >> #{repeat.real.round(2).ai} \n"
28
+ end
29
+
30
+ def main
31
+ benchmark "fetch all" do
32
+ PDC::V1::Release.all
33
+ end
34
+
35
+ benchmark "fetch with page number" do
36
+ PDC::V1::Release.page(2).contents!
37
+ end
38
+
39
+ benchmark "fetch with page number and size" do
40
+ PDC::V1::Release.page(2).page_size(30).contents!
41
+ end
42
+
43
+ benchmark "fetch with page number and size and query condition" do
44
+ PDC::V1::Release.page(2).page_size(30).where(active: true).contents!
45
+ end
46
+ end
47
+
48
+ main if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,26 @@
1
+ require_relative '../lib/pdc'
2
+ require 'ap'
3
+
4
+ class PDC::V1::NonExistingResource < PDC::Base
5
+ end
6
+
7
+ def main
8
+ PDC.configure do |config|
9
+ config.site = 'https://pdc.engineering.redhat.com/'
10
+ # don't even bother fetching the token, this has to
11
+ # fail anyway
12
+ config.requires_token = false
13
+ config.log_level = :debug
14
+ end
15
+
16
+ begin
17
+ resources = PDC::V1::NonExistingResource.all.to_a
18
+ ap resources
19
+ rescue PDC::ResourceNotFound => e
20
+ ap e
21
+ puts "Got resource not found as expected: #{e.response.status}".yellowish
22
+ ap e.message
23
+ end
24
+ end
25
+
26
+ main if __FILE__ == $0
@@ -0,0 +1,14 @@
1
+ require './lib/pdc'
2
+ require 'ap'
3
+
4
+ def main
5
+ PDC.configure do |config|
6
+ config.site = 'https://pdc.engineering.redhat.com'
7
+ config.log_level = :debug
8
+ end
9
+
10
+ releases = PDC::V1::Release.all!.to_a
11
+ ap releases
12
+ end
13
+
14
+ main if __FILE__ == $0
data/lib/pdc/base.rb ADDED
@@ -0,0 +1,14 @@
1
+ module PDC
2
+ class Base
3
+ extend ActiveModel::Naming
4
+
5
+ include PDC::Logging
6
+ include PDC::Resource::Identity
7
+ include PDC::Resource::Attributes
8
+ include PDC::Resource::Scopes
9
+ include PDC::Resource::RestApi
10
+
11
+ scope :page, ->(value) { where(:page => value) }
12
+ scope :page_size, ->(value) { where(:page_size => value) }
13
+ end
14
+ end
data/lib/pdc/config.rb ADDED
@@ -0,0 +1,157 @@
1
+ require 'faraday-http-cache'
2
+ require 'curb'
3
+
4
+ module PDC
5
+ # This class is the main access point for all PDC::Resource instances.
6
+ #
7
+ # The client must be initialized with an options hash containing
8
+ # configuration options. The available options are:
9
+ #
10
+ # :site => 'http://localhost:2990',
11
+ # :ssl_verify_mode => OpenSSL::SSL::VERIFY_PEER,
12
+ # :use_ssl => true,
13
+ # :username => nil,
14
+ # :password => nil,
15
+ # :auth_type => :oauth
16
+ # :proxy_address => nil
17
+ # :proxy_port => nil
18
+ #
19
+ # See the PDC::Base class methods for all of the available methods on these accessor
20
+ # objects.
21
+
22
+ class << self
23
+ Config = Struct.new(
24
+ :site,
25
+ :api_root,
26
+
27
+ :use_ssl,
28
+ :ssl_verify_mode,
29
+
30
+ :requires_token,
31
+ :token_obtain_path,
32
+ :token,
33
+
34
+ :log_level,
35
+ :enable_logging,
36
+ :logger,
37
+
38
+ :cache_store,
39
+ :disable_caching,
40
+ ) do
41
+ def initialize
42
+ # site config
43
+ self.site = 'http://localhost:8000'
44
+ self.api_root = 'rest_api/'
45
+ self.use_ssl = true
46
+ self.ssl_verify_mode = OpenSSL::SSL::VERIFY_PEER
47
+
48
+ # token and authentication
49
+ self.requires_token = true
50
+ self.token_obtain_path = 'v1/auth/token/obtain/'
51
+ self.token = nil
52
+
53
+ # logger config
54
+ self.log_level = :warn
55
+ self.enable_logging = true
56
+ self.logger = PDC.logger
57
+
58
+ self.cache_store = nil
59
+ self.disable_caching = false
60
+ end
61
+ end
62
+
63
+ def configure
64
+ @config = Config.new
65
+ begin
66
+ yield(@config) if block_given?
67
+ rescue NoMethodError => e
68
+ raise ConfigError, e
69
+ end
70
+
71
+ apply_config
72
+ end
73
+
74
+ def config
75
+ @config ||= Config.new
76
+ end
77
+
78
+ def config=(new_config)
79
+ @config = new_config
80
+ apply_config
81
+ @config
82
+ end
83
+
84
+ def token_url
85
+ URI.join(api_url, config.token_obtain_path)
86
+ end
87
+
88
+ def api_url
89
+ URI.join(config.site, config.api_root)
90
+ end
91
+
92
+ def token
93
+ config.token || fetch_token
94
+ end
95
+
96
+ private
97
+
98
+ def apply_config
99
+ reset_logger
100
+ reset_base_connection
101
+ end
102
+
103
+ def reset_logger
104
+ PDC.logger = Logger.new(nil) unless config.enable_logging
105
+ logger.level = Logger.const_get(config.log_level.upcase)
106
+ end
107
+
108
+ # resets and returns the +Faraday+ +connection+ object
109
+ def reset_base_connection
110
+ headers = PDC::Request.default_headers
111
+ PDC::Base.connection = Faraday.new(url: api_url, headers: headers) do |c|
112
+ c.request :append_slash_to_path
113
+ c.request :authorization, 'Token', token if config.requires_token
114
+
115
+ c.response :logger, config.logger
116
+ c.response :pdc_paginator
117
+ c.response :pdc_json_parser
118
+ c.response :raise_error
119
+ c.response :pdc_raise_error
120
+
121
+ c.use FaradayMiddleware::FollowRedirects
122
+
123
+ unless config.disable_caching
124
+ c.use Faraday::HttpCache, store: cache_store,
125
+ logger: PDC.logger,
126
+ instrumenter: ActiveSupport::Notifications
127
+ end
128
+ c.adapter Faraday.default_adapter
129
+ end
130
+ end
131
+
132
+ def cache_store
133
+ config.cache_store || ActiveSupport::Cache.lookup_store(:memory_store)
134
+ end
135
+
136
+ def fetch_token
137
+ curl = Curl::Easy.new(token_url.to_s) do |request|
138
+ request.headers['Accept'] = 'application/json'
139
+ request.http_auth_types = :gssnegotiate
140
+
141
+ # The curl man page (http://curl.haxx.se/docs/manpage.html)
142
+ # specifes setting a fake username when using Negotiate auth,
143
+ # and use ':' in their example.
144
+ request.username = ':'
145
+ end
146
+ curl.perform
147
+ if curl.response_code != 200
148
+ logger.info "Obtain token from #{token_url} failed: #{curl.body_str}"
149
+ error = { token_url: token_url, body: curl.body, code: curl.response_code }
150
+ raise PDC::TokenFetchFailed, error
151
+ end
152
+ result = JSON.parse(curl.body_str)
153
+ curl.close
154
+ result['token']
155
+ end
156
+ end
157
+ end
data/lib/pdc/errors.rb ADDED
@@ -0,0 +1,8 @@
1
+ module PDC
2
+ # Base for all PDC generated errors
3
+ class Error < StandardError; end
4
+
5
+ class ConfigError < Error; end
6
+
7
+ class InvalidPathError < Error; end
8
+ end
@@ -0,0 +1,43 @@
1
+ require 'forwardable'
2
+
3
+ module PDC
4
+ class JsonParseError < Error; end
5
+
6
+ class ResponseError < Error
7
+ extend Forwardable
8
+
9
+ def_instance_delegators :response, :status, :body
10
+ attr_reader :response
11
+
12
+ def initialize(args = {})
13
+ @response = args[:response]
14
+ @args = args
15
+ end
16
+ end
17
+
18
+ class JsonError < ResponseError
19
+ def message
20
+ summary = detail || response.body
21
+ "Error: #{status} - #{summary}"
22
+ end
23
+
24
+ private
25
+
26
+ # returns details in json response if any, else nil
27
+ def detail
28
+ @detail ||= json[:detail]
29
+ end
30
+
31
+ # tries to parse response body as a json
32
+ def json
33
+ @json ||= begin
34
+ MultiJson.load(response.body, symbolize_keys: true)
35
+ rescue MultiJson::ParseError
36
+ {}
37
+ end
38
+ end
39
+ end
40
+
41
+ class ResourceNotFound < JsonError; end
42
+ class TokenFetchFailed < JsonError; end
43
+ end
@@ -0,0 +1,19 @@
1
+ module PDC
2
+ class Request::AppendSlashToPath < Faraday::Middleware
3
+ include PDC::Logging
4
+
5
+ Faraday::Request.register_middleware :append_slash_to_path => self
6
+
7
+ def call(env)
8
+ logger.debug "\n..... append slash ..........................................".green
9
+ logger.debug self.class
10
+ logger.debug env.url
11
+
12
+ path = env.url.path
13
+ env.url.path = path + '/' unless path.ends_with?('/')
14
+
15
+ logger.debug "... after adding / #{env.url.path}: #{env.url.ai}"
16
+ @app.call(env)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,12 @@
1
+ require 'pdc/http/request/append_slash'
2
+
3
+ module PDC
4
+ module Request
5
+ def self.default_headers
6
+ {
7
+ 'Accept' => 'application/json',
8
+ 'Content-Type' => 'application/json'
9
+ }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,43 @@
1
+ module PDC::Response
2
+ class Paginator < Faraday::Response::Middleware
3
+ include PDC::Logging
4
+
5
+ Faraday::Response.register_middleware :pdc_paginator => self
6
+
7
+ def parse(json)
8
+ logger.debug "\n.....paginate json .....................................".redish
9
+
10
+ metadata = json[:metadata]
11
+ logger.debug metadata
12
+ return json unless paginated?(metadata)
13
+
14
+ metadata[PDC::Resource::PAGINATION] = {
15
+ resource_count: metadata.delete(:count),
16
+
17
+ # TODO: decide if this is okay to discard the
18
+ # schema://host:port/ of the next and previous
19
+
20
+ next_page: request_uri(metadata.delete(:next)),
21
+ previous_page: request_uri(metadata.delete(:previous)),
22
+ }
23
+
24
+ logger.debug '... after parsing pagination data:'.green
25
+ logger.debug metadata
26
+ json
27
+ end
28
+
29
+ private
30
+
31
+ def request_uri(uri)
32
+ return unless uri
33
+ URI(uri).request_uri
34
+ end
35
+
36
+ def paginated?(metadata)
37
+ metadata[:count].is_a?(Numeric) &&
38
+ metadata.key?(:next) &&
39
+ metadata.key?(:previous)
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,62 @@
1
+ require 'multi_json'
2
+
3
+ module PDC::Response
4
+ # Converts body into JSON data, metadata and errors
5
+ class Parser < Faraday::Response::Middleware
6
+ include PDC::Logging
7
+
8
+ Faraday::Response.register_middleware :pdc_json_parser => self
9
+
10
+ def parse(body)
11
+ logger.debug "\n.....parse to json .....................................".yellow
12
+ logger.debug self.class
13
+
14
+ logger.debug "... parsing #{body.ai.truncate(55)}"
15
+ begin
16
+ json = MultiJson.load(body, symbolize_keys: true)
17
+ rescue MultiJson::ParseError => e
18
+ raise PDC::JsonParseError, e
19
+ end
20
+
21
+ res = {
22
+ data: extract_data(json), # Always an Array
23
+ errors: extract_errors(json), #
24
+ metadata: extract_metadata(json) # a hash
25
+ }
26
+ end
27
+
28
+ private
29
+
30
+ def extract_data(json)
31
+ return [] if error?(json)
32
+ return json[:results] if has_metadata?(json)
33
+ Array.wrap(json)
34
+ end
35
+
36
+ def extract_errors(json)
37
+ error?(json) ? json[:details] : []
38
+ end
39
+
40
+ def extract_metadata(json)
41
+ return json.except(:details, :results) if has_metadata?(json)
42
+ data_only?(json) ? { count: json.length, next: nil, previous: nil } : {}
43
+ end
44
+
45
+ def has_metadata?(json)
46
+ return false if data_only?(json) || error?(json)
47
+
48
+ json[:results].is_a?(Array) &&
49
+ json[:count].is_a?(Numeric) &&
50
+ json.key?(:next) &&
51
+ json.key?(:previous)
52
+ end
53
+
54
+ def error?(json)
55
+ json.is_a?(Hash) && json.keys == [:detail]
56
+ end
57
+
58
+ def data_only?(json)
59
+ json.is_a? Array
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,13 @@
1
+ module PDC
2
+ class Response::RaiseError < Faraday::Response::Middleware
3
+ Faraday::Response.register_middleware pdc_raise_error: self
4
+
5
+ def on_complete(env)
6
+ raise PDC::ResourceNotFound, response_values(env) if env[:status] == 404
7
+ end
8
+
9
+ def response_values(env)
10
+ { response: env.response, request: env.request }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ require 'pdc/http/response/parser'
2
+ require 'pdc/http/response/pagination'
3
+ require 'pdc/http/response/raise_error'
@@ -0,0 +1,28 @@
1
+ module PDC::Http
2
+ # encapsulates +http+ response
3
+ class Result
4
+ attr_reader :url, :body, :status
5
+
6
+ def initialize(response)
7
+ @body = HashWithIndifferentAccess.new(response.body)
8
+ @status = response.status
9
+ @url = response.env.url
10
+ end
11
+
12
+ def data
13
+ body[:data]
14
+ end
15
+
16
+ def metadata
17
+ body[:metadata] || {}
18
+ end
19
+
20
+ def pagination
21
+ metadata[PDC::Resource::PAGINATION] || {}
22
+ end
23
+
24
+ def errors
25
+ body[:errors] || []
26
+ end
27
+ end
28
+ end
data/lib/pdc/http.rb ADDED
@@ -0,0 +1,12 @@
1
+ module PDC
2
+ module Request
3
+ end
4
+
5
+ module Response
6
+ end
7
+ end
8
+
9
+ require 'pdc/http/errors'
10
+ require 'pdc/http/result'
11
+ require 'pdc/http/request'
12
+ require 'pdc/http/response'