apill 2.9.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 57ee7efdfee5d8f2b2425c1d8a1683983c0d5c3d
4
- data.tar.gz: 3c8ae2f88ec48122895c01a8306cf1b6a22dadb3
3
+ metadata.gz: c68aa89f529dc07063d53a0f2e53e210b606c1bb
4
+ data.tar.gz: 6fc4b20a268b92b45d8e2e3f0b6ae195b118827d
5
5
  SHA512:
6
- metadata.gz: 9c094f1dc4250418fcc89b54588e1585dfa66e5165dcee4c16b26444ddcb40315b01baf7874fa5f2957f678ab66dcddd7fa74e2d35d8f036af3e11175f859b7d
7
- data.tar.gz: 3a62bad4d957827247fed3eb561eed9c61df95e20b1bd54fe0153d62ea2c76d565b265df982182ee3ee2583129d1786f1de8bea45bab15180fa1de6d75716446
6
+ metadata.gz: 02544a5f552ca8205ccd6aff687b60460f001c06b49db580ebfc9ad2cc841e5af9af65440af2494d1ba3f4baf6c56b002d68ce1afc720b90c5b0192d62d7c509
7
+ data.tar.gz: 9ac237ebc9b635993885a286f8b22353c98c655efc68fde7c50056dba9cbc332298465e328bcc80c8cf23cc6a0f44eab59c21aa381943c06a43c54993fe8a4a5
@@ -2,25 +2,27 @@ require 'human_error'
2
2
 
3
3
  module Apill
4
4
  module Errors
5
- class InvalidApiRequestError < HumanError::Errors::RequestError
5
+ class InvalidApiRequestError < RuntimeError
6
+ include HumanError::Error
7
+
6
8
  attr_accessor :accept_header
7
9
 
8
10
  def http_status
9
11
  400
10
12
  end
11
13
 
12
- def developer_message
14
+ def title
15
+ 'Invalid API Request'
16
+ end
17
+
18
+ def detail
13
19
  'The accept header that you passed in the request cannot be parsed, ' \
14
20
  'please refer to the documentation to verify.'
15
21
  end
16
22
 
17
- def developer_details
23
+ def source
18
24
  { accept_header: accept_header }
19
25
  end
20
-
21
- def friendly_message
22
- "Sorry! We couldn't understand what you were trying to ask us to do."
23
- end
24
26
  end
25
27
  end
26
28
  end
@@ -2,25 +2,27 @@ require 'human_error'
2
2
 
3
3
  module Apill
4
4
  module Errors
5
- class InvalidSubdomainError < HumanError::Errors::RequestError
5
+ class InvalidSubdomainError < RuntimeError
6
+ include HumanError::Error
7
+
6
8
  attr_accessor :http_host
7
9
 
8
10
  def http_status
9
11
  404
10
12
  end
11
13
 
12
- def developer_message
14
+ def title
15
+ 'Invalid Subdomain'
16
+ end
17
+
18
+ def detail
13
19
  'The resource you attempted to access is either not authorized for the ' \
14
20
  'authenticated user or does not exist.'
15
21
  end
16
22
 
17
- def developer_details
23
+ def source
18
24
  { http_host: http_host }
19
25
  end
20
-
21
- def friendly_message
22
- 'Sorry! The resource you tried to access does not exist.'
23
- end
24
26
  end
25
27
  end
26
28
  end
@@ -0,0 +1,23 @@
1
+ module Apill
2
+ class Parameters
3
+ class Filter
4
+ attr_accessor :raw_parameters
5
+
6
+ def initialize(raw_parameters)
7
+ self.raw_parameters = raw_parameters
8
+ end
9
+
10
+ def each_with_object(memoized)
11
+ raw_parameters.each do |raw_parameter|
12
+ next if raw_parameter[0] == 'query' ||
13
+ raw_parameter[1] == '' ||
14
+ raw_parameter[1].nil?
15
+
16
+ memoized = yield raw_parameter[0], raw_parameter[1], memoized
17
+ end
18
+
19
+ memoized
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ module Apill
2
+ class Parameters
3
+ class Index
4
+ DEFAULT_QUERY = '*'
5
+
6
+ attr_accessor :raw_parameters
7
+
8
+ def initialize(raw_parameters)
9
+ self.raw_parameters = raw_parameters
10
+ end
11
+
12
+ def query
13
+ raw_parameters['query'] || raw_parameters['q']
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,22 @@
1
+ module Apill
2
+ class Parameters
3
+ class Page
4
+ DEFAULT_STARTING_PAGE = 1
5
+ DEFAULT_PAGE_SIZE = 25
6
+
7
+ attr_accessor :raw_parameters
8
+
9
+ def initialize(raw_parameters)
10
+ self.raw_parameters = raw_parameters
11
+ end
12
+
13
+ def page_number
14
+ raw_parameters['number'] || DEFAULT_STARTING_PAGE
15
+ end
16
+
17
+ def per_page
18
+ raw_parameters['size'] || DEFAULT_PAGE_SIZE
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,27 @@
1
+ module Apill
2
+ class Parameters
3
+ class Sort
4
+ DESCENDING_PREFIX = '-'
5
+
6
+ attr_accessor :raw_parameters
7
+
8
+ def initialize(raw_parameters)
9
+ self.raw_parameters = raw_parameters ? raw_parameters.split(',') : ['-created_at']
10
+ end
11
+
12
+ def to_h
13
+ @to_h ||= Hash[to_a]
14
+ end
15
+
16
+ def to_a
17
+ @to_a ||= raw_parameters.map do |raw_parameter|
18
+ if raw_parameter.start_with?(DESCENDING_PREFIX)
19
+ [raw_parameter[1..-1], 'desc']
20
+ else
21
+ [raw_parameter, 'asc']
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,42 @@
1
+ require 'apill/resource/processors/filtering'
2
+ require 'apill/resource/processors/sorting'
3
+ require 'apill/resource/processors/paging'
4
+ require 'apill/resource/processors/indexing'
5
+
6
+ module Apill
7
+ module Resource
8
+ class Model
9
+ DEFAULT_PROCESSORS = %w{filtering sorting paging indexing}
10
+
11
+ attr_accessor :resource,
12
+ :parameters,
13
+ :processors
14
+
15
+ def initialize(resource:, parameters:, **options)
16
+ self.resource = resource
17
+ self.parameters = parameters
18
+ self.processors = options.fetch(:processors, DEFAULT_PROCESSORS)
19
+ end
20
+
21
+ def processed
22
+ @processed ||= \
23
+ processors.inject(resource) do |processed_resource, processor|
24
+ processor.processed(processed_resource, parameters)
25
+ end
26
+ end
27
+
28
+ def meta
29
+ @meta ||= \
30
+ processors.inject({}) do |metadata, processor|
31
+ metadata.merge processor.meta(processed, parameters)
32
+ end
33
+ end
34
+
35
+ def processors=(other)
36
+ @processors = other.map do |processor|
37
+ Object.const_get "::Apill::Resource::Processors::#{processor.capitalize}"
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,63 @@
1
+ require 'apill/parameters/filter'
2
+
3
+ module Apill
4
+ module Resource
5
+ module Processors
6
+ class Filtering
7
+ attr_accessor :resource,
8
+ :parameters
9
+
10
+ def initialize(resource, parameters = {})
11
+ self.resource = resource
12
+ self.parameters = Parameters::Filter.new(parameters['filter'] || {})
13
+ end
14
+
15
+ def self.processed(*attrs)
16
+ new(*attrs).processed
17
+ end
18
+
19
+ def self.meta(*_attrs)
20
+ {}
21
+ end
22
+
23
+ def processed
24
+ parameters.each_with_object(resource) do |name, value, filtered_resource|
25
+ filter_method = filter_method_for(name)
26
+
27
+ if !filter_method
28
+ filtered_resource
29
+ elsif filter_method.arity == 0
30
+ filtered_resource.public_send(filter_method.name)
31
+ else
32
+ filtered_resource.public_send(filter_method.name, value)
33
+ end
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def filter_method_for(filter_item)
40
+ filter_method_name = filter_method_name_for(filter_item)
41
+
42
+ resource_class.method(filter_method_name) if filter_method_name
43
+ end
44
+
45
+ def filter_method_name_for(filter_item)
46
+ if resource_class.respond_to? "for_#{filter_item}"
47
+ "for_#{filter_item}"
48
+ elsif resource_class.respond_to? filter_item
49
+ filter_item
50
+ end
51
+ end
52
+
53
+ def resource_class
54
+ @resource_class ||= if resource.respond_to? :klass
55
+ resource.klass
56
+ else
57
+ resource
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,32 @@
1
+ require 'apill/parameters/index'
2
+
3
+ module Apill
4
+ module Resource
5
+ module Processors
6
+ class Indexing
7
+ attr_accessor :resource,
8
+ :parameters
9
+
10
+ def initialize(resource, parameters)
11
+ self.resource = resource
12
+ self.parameters = Parameters::Index.new(parameters['filter'] || {})
13
+ end
14
+
15
+ def self.processed(*attrs)
16
+ new(*attrs).processed
17
+ end
18
+
19
+ def self.meta(*_attrs)
20
+ {}
21
+ end
22
+
23
+ def processed
24
+ return resource unless resource.respond_to?(:for_query) &&
25
+ parameters.query
26
+
27
+ resource.for_query(parameters.query)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,39 @@
1
+ require 'apill/parameters/page'
2
+
3
+ module Apill
4
+ module Resource
5
+ module Processors
6
+ class Paging
7
+ attr_accessor :resource,
8
+ :parameters
9
+
10
+ def initialize(resource, parameters = {})
11
+ self.resource = resource
12
+ self.parameters = Parameters::Page.new(parameters['page'] || {})
13
+ end
14
+
15
+ def self.processed(*attrs)
16
+ new(*attrs).processed
17
+ end
18
+
19
+ def self.meta(*attrs)
20
+ new(*attrs).meta
21
+ end
22
+
23
+ def processed
24
+ resource.page(parameters.page_number).
25
+ per(parameters.per_page)
26
+ end
27
+
28
+ def meta
29
+ {
30
+ 'total-pages' => processed.total_pages,
31
+ 'current-page' => processed.current_page,
32
+ 'previous-page' => processed.prev_page,
33
+ 'next-page' => processed.next_page,
34
+ }
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,35 @@
1
+ require 'apill/parameters/sort'
2
+
3
+ module Apill
4
+ module Resource
5
+ module Processors
6
+ class Sorting
7
+ attr_accessor :resource,
8
+ :parameters
9
+
10
+ def initialize(resource, parameters = {})
11
+ self.resource = resource
12
+ self.parameters = Parameters::Sort.new(parameters['sort'])
13
+ end
14
+
15
+ def self.processed(*attrs)
16
+ new(*attrs).processed
17
+ end
18
+
19
+ def self.meta(*attrs)
20
+ new(*attrs).meta
21
+ end
22
+
23
+ def processed
24
+ resource.order(parameters.to_h)
25
+ end
26
+
27
+ def meta
28
+ {
29
+ 'sort' => parameters.to_h,
30
+ }
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,68 @@
1
+ require 'apill/resource/model'
2
+ require 'human_error/rescuable_resource'
3
+
4
+ module Apill
5
+ module Resource
6
+ module ClassMethods
7
+ def plural_resource_name
8
+ name[/(\w+)Controller\z/, 1].
9
+ underscore.
10
+ pluralize.
11
+ downcase
12
+ end
13
+
14
+ def singular_resource_name
15
+ name[/(\w+)Controller\z/, 1].
16
+ underscore.
17
+ singularize.
18
+ downcase
19
+ end
20
+ end
21
+
22
+ def self.included(base)
23
+ base.extend ClassMethods
24
+ base.include HumanError::RescuableResource
25
+ base.include HumanError::VerifiableResource
26
+ end
27
+
28
+ def api_resource
29
+ @resource ||= Resource::Model.new(
30
+ resource: public_send(self.class.plural_resource_name),
31
+ parameters: api_resource_params)
32
+ end
33
+
34
+ def api_resource_params
35
+ params.permit(sort: String,
36
+ page: %i{
37
+ number
38
+ size
39
+ offset
40
+ limit
41
+ cursor
42
+ },
43
+ filter: api_filterable_parameters)
44
+ end
45
+
46
+ def api_filterable_parameters
47
+ @api_filterable_parameters ||= begin
48
+ filter_params = params.fetch(:filter, {})
49
+ scalar_params = [:query]
50
+ array_params = {}
51
+
52
+ api_filterable_attributes.each do |api_filterable_attribute|
53
+ if filter_params[api_filterable_attribute].class == Array
54
+ array_params[api_filterable_attribute] = []
55
+ else
56
+ scalar_params << api_filterable_attribute
57
+ end
58
+ end
59
+
60
+ scalar_params << array_params
61
+ end
62
+ end
63
+
64
+ def api_filterable_attributes
65
+ []
66
+ end
67
+ end
68
+ end
data/lib/apill/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Apill
2
- VERSION = '2.9.0'
2
+ VERSION = '3.0.0'
3
3
  end
data/lib/apill.rb CHANGED
@@ -1,15 +1,10 @@
1
- require 'kaminari'
2
-
3
1
  require 'apill/version'
4
2
 
5
3
  require 'apill/configuration'
6
4
  require 'apill/matchers/accept_header_matcher'
7
5
  require 'apill/matchers/subdomain_matcher'
8
6
  require 'apill/matchers/version_matcher'
9
- require 'apill/mixins/indexable'
10
- require 'apill/mixins/sortable'
11
- require 'apill/mixins/queryable'
12
- require 'apill/mixins/pageable'
7
+ require 'apill/resource'
13
8
  require 'apill/serializers/json_api'
14
9
 
15
10
  require 'apill/middleware/api_request'
@@ -10,29 +10,20 @@ describe InvalidApiRequestError do
10
10
  expect(error.http_status).to eql 400
11
11
  end
12
12
 
13
- it 'has a code of 1007' do
14
- expect(error.code).to eql 1007
13
+ it 'has a code' do
14
+ expect(error.code).to eql 'errors.invalid_api_request_error'
15
15
  end
16
16
 
17
- it 'has a knowledgebase article ID of 1234567890' do
18
- expect(error.knowledgebase_article_id).to eql '1234567890'
17
+ it 'can output the detail' do
18
+ expect(error.detail).to eql 'The accept header that you passed in the ' \
19
+ 'request cannot be parsed, please refer to ' \
20
+ 'the documentation to verify.'
19
21
  end
20
22
 
21
- it 'can output the developer message' do
22
- expect(error.developer_message).to eql 'The accept header that you passed in the ' \
23
- 'request cannot be parsed, please refer to ' \
24
- 'the documentation to verify.'
25
- end
26
-
27
- it 'can output the developer details' do
23
+ it 'can output the source' do
28
24
  error = InvalidApiRequestError.new accept_header: 'foo'
29
25
 
30
- expect(error.developer_details).to eql(accept_header: 'foo')
31
- end
32
-
33
- it 'can output the friendly message' do
34
- expect(error.friendly_message).to eql "Sorry! We couldn't understand what you were " \
35
- 'trying to ask us to do.'
26
+ expect(error.source).to eql(accept_header: 'foo')
36
27
  end
37
28
  end
38
29
  end
@@ -10,29 +10,20 @@ describe InvalidSubdomainError do
10
10
  expect(error.http_status).to eql 404
11
11
  end
12
12
 
13
- it 'has a code of 1010' do
14
- expect(error.code).to eql 1010
13
+ it 'has a code' do
14
+ expect(error.code).to eql 'errors.invalid_subdomain_error'
15
15
  end
16
16
 
17
- it 'has a knowledgebase article ID of 1234567890' do
18
- expect(error.knowledgebase_article_id).to eql '1234567890'
19
- end
20
-
21
- it 'can output the developer message' do
22
- expect(error.developer_message).to eql \
17
+ it 'can output the detail' do
18
+ expect(error.detail).to eql \
23
19
  'The resource you attempted to access is either not authorized for the ' \
24
20
  'authenticated user or does not exist.'
25
21
  end
26
22
 
27
- it 'can output the developer details' do
23
+ it 'can output the source' do
28
24
  error = InvalidSubdomainError.new http_host: 'foo'
29
25
 
30
- expect(error.developer_details).to eql(http_host: 'foo')
31
- end
32
-
33
- it 'can output the friendly message' do
34
- expect(error.friendly_message).to eql \
35
- 'Sorry! The resource you tried to access does not exist.'
26
+ expect(error.source).to eql(http_host: 'foo')
36
27
  end
37
28
  end
38
29
  end
@@ -3,43 +3,39 @@ require 'apill/responses/invalid_subdomain_response'
3
3
 
4
4
  module Apill
5
5
  module Responses
6
- describe InvalidSubdomainResponse do
7
- before(:each) do
8
- HumanError.configuration.api_error_documentation_url = 'http://error.com'
9
- HumanError.configuration.knowledgebase_url = 'http://knowledge.com'
10
- HumanError.configuration.api_version = '1'
11
- end
12
-
6
+ describe InvalidSubdomainResponse, singletons: HumanError::Configuration do
13
7
  it 'returns the proper response' do
14
- request = { 'HTTP_HOST' => 'api.example.com' }
15
- response = InvalidSubdomainResponse.call(request)
8
+ HumanError.configuration.url_mappings = {
9
+ 'external_documentation_urls' => {
10
+ 'errors.invalid_subdomain_response' => 'http://example.com/foo',
11
+ },
12
+ 'developer_documentation_urls' => {
13
+ 'errors.invalid_subdomain_response' => 'http://example.com/foo',
14
+ },
15
+ }
16
+
17
+ request = { 'HTTP_HOST' => 'api.example.com' }
18
+ status, headers, response = InvalidSubdomainResponse.call(request)
16
19
 
17
- expect(response).to eql(
18
- [
19
- 404,
20
- {},
21
- [
22
- '{' \
23
- '"error":' \
24
- '{' \
25
- '"status":404,' \
26
- '"code":1010,' \
27
- '"developer_documentation_uri":"http://error.com/1010?version=1",' \
28
- '"customer_support_uri":"http://knowledge.com/1234567890",' \
29
- '"developer_message_key":"errors.invalid.subdomain.error.developer",' \
30
- '"developer_message":"The resource you attempted to access is either not ' \
31
- 'authorized for the authenticated user or does not ' \
32
- 'exist.",' \
33
- '"developer_details":' \
34
- '{' \
35
- '"http_host":"api.example.com"' \
36
- '},' \
37
- '"friendly_message_key":"errors.invalid.subdomain.error.friendly",' \
38
- '"friendly_message":"Sorry! The resource you tried to access does not ' \
39
- 'exist."' \
40
- '}' \
41
- '}',
42
- ],
20
+ expect(status).to eql 404
21
+ expect(headers).to eql({})
22
+ expect(JSON.load(response[0])).to include(
23
+ 'errors' => [
24
+ include(
25
+ 'id' => match(/[a-f0-9\-]+/),
26
+ 'links' => {
27
+ 'about' => nil,
28
+ 'documentation' => nil,
29
+ },
30
+ 'status' => 404,
31
+ 'code' => 'errors.invalid_subdomain_error',
32
+ 'title' => 'Invalid Subdomain',
33
+ 'detail' => 'The resource you attempted to access is either not authorized ' \
34
+ 'for the authenticated user or does not exist.',
35
+ 'source' => {
36
+ 'http_host' => 'api.example.com',
37
+ },
38
+ ),
43
39
  ],
44
40
  )
45
41
  end