apill 2.9.0 → 3.0.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.
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