pdc 0.1.0

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 (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'