apill 1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8b6e98aa489a5ae613535da00d756a01f5a43a21
4
+ data.tar.gz: 701aae7dade873f7fbb73dc58f43654dda96797e
5
+ SHA512:
6
+ metadata.gz: 04627afe9df9d0af43799c794836461cb70587d3877039469d3cb179843d9ee72cd46ef7544921eeeb05d4ca89a03127b90779398637024ed082557bfdb277a2
7
+ data.tar.gz: fd1903a45d3ab8af84448a0f530b63f2d2b97bd2f3a0f3b60d53648a81fd3d0299d2f9bbd656c50e24c0319762a594cab4dc7129afca1dbc8a9491277be2accc
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Jeff Felchner
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,29 @@
1
+ # Apill
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'apill'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install apill
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it ( http://github.com/<my-github-username>/apill/fork )
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,7 @@
1
+ require 'apill/version'
2
+
3
+ require 'apill/matchers/accept_header_matcher'
4
+ require 'apill/matchers/invalid_api_request_matcher'
5
+ require 'apill/matchers/version_matcher'
6
+
7
+ require 'apill/responses/invalid_api_request_response'
@@ -0,0 +1,41 @@
1
+ module Apill
2
+ class AcceptHeader
3
+ attr_accessor :application,
4
+ :raw_accept_header
5
+
6
+ def initialize(application:, header:)
7
+ self.application = application
8
+ self.raw_accept_header = header
9
+ end
10
+
11
+ def version
12
+ accept_header_data[2]
13
+ end
14
+
15
+ def content_type
16
+ accept_header_data[1]
17
+ end
18
+
19
+ def valid?
20
+ !accept_header_data.nil?
21
+ end
22
+
23
+ private
24
+
25
+ def accept_header_data
26
+ raw_accept_header.match(accept_header_format)
27
+ end
28
+
29
+ def accept_header_format
30
+ %r{\Aapplication/#{application_vendor}(?:\+(\w+))?(?:;version=(#{version_format}))?\z}
31
+ end
32
+
33
+ def application_vendor
34
+ "vnd\\.#{application}"
35
+ end
36
+
37
+ def version_format
38
+ "\\d+(?:\\.\\d+){0,2}(?:beta(?:\\d*))?"
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,25 @@
1
+ require 'human_error'
2
+
3
+ module Apill
4
+ module Errors
5
+ class InvalidApiRequestError < HumanError::Errors::RequestError
6
+ attr_accessor :accept_header
7
+
8
+ def http_status
9
+ 400
10
+ end
11
+
12
+ def developer_message
13
+ 'The accept header that you passed in the request cannot be parsed, please refer to the documentation to verify.'
14
+ end
15
+
16
+ def developer_details
17
+ { accept_header: accept_header }
18
+ end
19
+
20
+ def friendly_message
21
+ "Sorry! We couldn't understand what you were trying to ask us to do."
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,16 @@
1
+ require 'apill/matchers/generic_matcher'
2
+
3
+ module Apill
4
+ module Matchers
5
+ class AcceptHeaderMatcher
6
+ include Apill::Matchers::GenericMatcher
7
+
8
+ def matches?(request)
9
+ super
10
+
11
+ request.subdomains.first == 'api' &&
12
+ accept_header.valid?
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,21 @@
1
+ require 'apill/accept_header'
2
+
3
+ module Apill
4
+ module Matchers
5
+ module GenericMatcher
6
+ attr_accessor :application,
7
+ :accept_header
8
+
9
+ def initialize(**args)
10
+ args.each do |variable, value|
11
+ self.send("#{variable}=", value)
12
+ end
13
+ end
14
+
15
+ def matches?(request)
16
+ self.accept_header = Apill::AcceptHeader.new(application: application,
17
+ header: request.headers['Accept'])
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ require 'apill/matchers/accept_header_matcher'
2
+
3
+ module Apill
4
+ module Matchers
5
+ class InvalidApiRequestMatcher < AcceptHeaderMatcher
6
+ def matches?(request)
7
+ !super
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,28 @@
1
+ require 'apill/matchers/generic_matcher'
2
+ require 'apill/errors/invalid_api_request_error'
3
+
4
+ module Apill
5
+ module Matchers
6
+ class VersionMatcher
7
+ include Apill::Matchers::GenericMatcher
8
+
9
+ attr_accessor :version_constraint,
10
+ :default_version
11
+
12
+ def matches?(request)
13
+ super
14
+
15
+ raise Apill::Errors::InvalidApiRequestError unless accept_header.valid?
16
+
17
+ request.subdomains.first == 'api' &&
18
+ requested_version == version_constraint
19
+ end
20
+
21
+ private
22
+
23
+ def requested_version
24
+ accept_header.version || default_version
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ require 'apill/errors/invalid_api_request_error'
2
+
3
+ module Apill
4
+ module Responses
5
+ class InvalidApiRequestResponse
6
+ def self.call(env)
7
+ error = Apill::Errors::InvalidApiRequestError.new
8
+
9
+ [
10
+ error.http_status, # HTTP Status Code
11
+ {}, # Response Headers
12
+ error.to_json, # Message
13
+ ]
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ module Apill
2
+ VERSION = '1.1.0'
3
+ end
@@ -0,0 +1,94 @@
1
+ require 'rspectacular'
2
+ require 'apill/accept_header'
3
+
4
+ module Apill
5
+ describe AcceptHeader do
6
+ it 'can validate an accept header with all the pieces of information' do
7
+ header = AcceptHeader.new(application: 'matrix',
8
+ header: 'application/vnd.matrix+zion;version=1.0.0')
9
+
10
+ expect(header).to be_valid
11
+ end
12
+
13
+ it 'does not validate an accept header without passing an application' do
14
+ header = AcceptHeader.new(application: '',
15
+ header: 'application/vnd.matrix+zion;version=1.0.0')
16
+
17
+ expect(header).not_to be_valid
18
+ end
19
+
20
+ it 'does not validate an accept header without an application in the header' do
21
+ header = AcceptHeader.new(application: 'matrix',
22
+ header: 'application/vnd.+zion;version=1.0.0')
23
+
24
+ expect(header).not_to be_valid
25
+
26
+ header = AcceptHeader.new(application: 'matrix',
27
+ header: 'application/+zion;version=1.0.0')
28
+
29
+ expect(header).not_to be_valid
30
+
31
+ header = AcceptHeader.new(application: 'matrix',
32
+ header: 'application/matrix+zion;version=1.0.0')
33
+
34
+ expect(header).not_to be_valid
35
+ end
36
+
37
+ it 'does not validate an accept header with an invalid version' do
38
+ header = AcceptHeader.new(application: 'matrix',
39
+ header: 'application/vnd.matrix+zion;version=10..0')
40
+
41
+ expect(header).not_to be_valid
42
+
43
+ header = AcceptHeader.new(application: 'matrix',
44
+ header: 'application/vnd.matrix+zion;version=neo')
45
+
46
+ expect(header).not_to be_valid
47
+
48
+ header = AcceptHeader.new(application: 'matrix',
49
+ header: 'application/vnd.matrix+zion;version=')
50
+
51
+ expect(header).not_to be_valid
52
+
53
+ header = AcceptHeader.new(application: 'matrix',
54
+ header: 'application/vnd.matrix+zion;10.0')
55
+
56
+ expect(header).not_to be_valid
57
+ end
58
+
59
+ it 'does validate an accept header even with a missing content type' do
60
+ header = AcceptHeader.new(application: 'matrix',
61
+ header: 'application/vnd.matrix;version=10.0')
62
+
63
+ expect(header).to be_valid
64
+ end
65
+
66
+ it 'does validate an accept header with only the minimal information' do
67
+ header = AcceptHeader.new(application: 'matrix',
68
+ header: 'application/vnd.matrix')
69
+
70
+ expect(header).to be_valid
71
+ end
72
+
73
+ it 'does validate an accept header with only a content type but no version' do
74
+ header = AcceptHeader.new(application: 'matrix',
75
+ header: 'application/vnd.matrix+zion')
76
+
77
+ expect(header).to be_valid
78
+ end
79
+
80
+ it 'can extract version information from an accept header' do
81
+ header = AcceptHeader.new(application: 'matrix',
82
+ header: 'application/vnd.matrix+zion;version=10.0.0beta1')
83
+
84
+ expect(header.version).to eql '10.0.0beta1'
85
+ end
86
+
87
+ it 'can extract the content type from an accept header' do
88
+ header = AcceptHeader.new(application: 'matrix',
89
+ header: 'application/vnd.matrix+zion;version=10.0.0beta1')
90
+
91
+ expect(header.content_type).to eql 'zion'
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,36 @@
1
+ require 'rspectacular'
2
+ require 'apill/errors/invalid_api_request_error'
3
+
4
+ module Apill
5
+ module Errors
6
+ describe InvalidApiRequestError do
7
+ let(:error) { InvalidApiRequestError.new }
8
+
9
+ it 'has a status of 400' do
10
+ expect(error.http_status).to eql 400
11
+ end
12
+
13
+ it 'has a code of 1007' do
14
+ expect(error.code).to eql 1007
15
+ end
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 'The accept header that you passed in the request cannot be parsed, please refer to the documentation to verify.'
23
+ end
24
+
25
+ it 'can output the developer details' do
26
+ error = InvalidApiRequestError.new accept_header: 'foo'
27
+
28
+ expect(error.developer_details).to eql(accept_header: 'foo')
29
+ end
30
+
31
+ it 'can output the friendly message' do
32
+ expect(error.friendly_message).to eql "Sorry! We couldn't understand what you were trying to ask us to do."
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,45 @@
1
+ require 'ostruct'
2
+ require 'rspectacular'
3
+ require 'apill/matchers/accept_header_matcher'
4
+
5
+ module Apill
6
+ module Matchers
7
+ describe AcceptHeaderMatcher do
8
+ it 'matches if the subdomain is API and the accept header is valid' do
9
+ request = OpenStruct.new(headers: { 'Accept' => 'application/vnd.matrix+zion;version=1.0.0' },
10
+ subdomains: [ 'api' ])
11
+
12
+ matcher = AcceptHeaderMatcher.new(application: 'matrix')
13
+
14
+ expect(matcher.matches?(request)).to be_truthy
15
+ end
16
+
17
+ it 'does not match if the subdomain is not API but the accept header is valid' do
18
+ request = OpenStruct.new(headers: { 'Accept' => 'application/vnd.matrix+zion' },
19
+ subdomains: [ 'not-api' ])
20
+
21
+ matcher = AcceptHeaderMatcher.new(application: 'matrix')
22
+
23
+ expect(matcher.matches?(request)).to be_falsey
24
+ end
25
+
26
+ it 'does not match if the subdomain is API but the accept header is invalid' do
27
+ request = OpenStruct.new(headers: { 'Accept' => 'application/vnd.' },
28
+ subdomains: [ 'api' ])
29
+
30
+ matcher = AcceptHeaderMatcher.new(application: 'matrix')
31
+
32
+ expect(matcher.matches?(request)).to be_falsey
33
+ end
34
+
35
+ it 'does not match if neither the subdomain is API nor the accept header is valid' do
36
+ request = OpenStruct.new(headers: { 'Accept' => 'application/vnd.' },
37
+ subdomains: [ 'not-api' ])
38
+
39
+ matcher = AcceptHeaderMatcher.new(application: 'matrix')
40
+
41
+ expect(matcher.matches?(request)).to be_falsey
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,18 @@
1
+ require 'ostruct'
2
+ require 'rspectacular'
3
+ require 'apill/matchers/invalid_api_request_matcher'
4
+
5
+ module Apill
6
+ module Matchers
7
+ describe InvalidApiRequestMatcher do
8
+ it 'is the inverse of whether the accept header matches' do
9
+ request = OpenStruct.new(headers: { 'Accept' => 'application/vnd.matrix+zion;version=1.0.0' },
10
+ subdomains: [ 'api' ])
11
+
12
+ matcher = InvalidApiRequestMatcher.new(application: 'matrix')
13
+
14
+ expect(matcher.matches?(request)).to be_falsey
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,85 @@
1
+ require 'ostruct'
2
+ require 'rspectacular'
3
+ require 'apill/matchers/version_matcher'
4
+
5
+ module Apill
6
+ module Matchers
7
+ describe VersionMatcher do
8
+ it 'raises an error when the accept header is not valid' do
9
+ request = OpenStruct.new(headers: { 'Accept' => 'matrix' },
10
+ subdomains: [ 'api' ])
11
+
12
+ matcher = VersionMatcher.new
13
+
14
+ expect { matcher.matches?(request) }.to raise_error Apill::Errors::InvalidApiRequestError
15
+ end
16
+
17
+ context 'when the version is passed in the accept header' do
18
+ it 'does not match if the subdomain is not API but the request version is equal to the version constraint' do
19
+ request = OpenStruct.new(headers: { 'Accept' => 'application/vnd.matrix+zion;version=10.0' },
20
+ subdomains: [ 'not-api' ])
21
+
22
+ matcher = VersionMatcher.new(application: 'matrix',
23
+ version_constraint: '10.0')
24
+
25
+ expect(matcher.matches?(request)).to be_falsey
26
+ end
27
+
28
+ it 'does not match if the subdomain is API but the requested version does not equal the version constraint' do
29
+ request = OpenStruct.new(headers: { 'Accept' => 'application/vnd.matrix+zion;version=10.0' },
30
+ subdomains: [ 'api' ])
31
+
32
+ matcher = VersionMatcher.new(application: 'matrix',
33
+ version_constraint: '10.1')
34
+
35
+ expect(matcher.matches?(request)).to be_falsey
36
+ end
37
+
38
+ it 'does match if the subdomain is API and the requested version equals the version constraint' do
39
+ request = OpenStruct.new(headers: { 'Accept' => 'application/vnd.matrix+zion;version=10.0' },
40
+ subdomains: [ 'api' ])
41
+
42
+ matcher = VersionMatcher.new(application: 'matrix',
43
+ version_constraint: '10.0')
44
+
45
+ expect(matcher.matches?(request)).to be_truthy
46
+ end
47
+ end
48
+
49
+ context 'when the version is not passed in the accept header' do
50
+ it 'does not match if the subdomain is not API but the request version is equal to the version constraint' do
51
+ request = OpenStruct.new(headers: { 'Accept' => 'application/vnd.matrix+zion' },
52
+ subdomains: [ 'not-api' ])
53
+
54
+ matcher = VersionMatcher.new(application: 'matrix',
55
+ version_constraint: '10.0',
56
+ default_version: '10.0')
57
+
58
+ expect(matcher.matches?(request)).to be_falsey
59
+ end
60
+
61
+ it 'does not match if the subdomain is API but the requested version does not equal the version constraint' do
62
+ request = OpenStruct.new(headers: { 'Accept' => 'application/vnd.matrix+zion' },
63
+ subdomains: [ 'api' ])
64
+
65
+ matcher = VersionMatcher.new(application: 'matrix',
66
+ version_constraint: '10.1',
67
+ default_version: '10.0')
68
+
69
+ expect(matcher.matches?(request)).to be_falsey
70
+ end
71
+
72
+ it 'does match if the subdomain is API and the requested version equals the version constraint' do
73
+ request = OpenStruct.new(headers: { 'Accept' => 'application/vnd.matrix+zion' },
74
+ subdomains: [ 'api' ])
75
+
76
+ matcher = VersionMatcher.new(application: 'matrix',
77
+ version_constraint: '10.0',
78
+ default_version: '10.0')
79
+
80
+ expect(matcher.matches?(request)).to be_truthy
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: apill
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ platform: ruby
6
+ authors:
7
+ - jfelchner
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-04-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: human_error
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 3.0beta
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 3.0beta
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspectacular
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.23'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.23'
55
+ description: ''
56
+ email: accounts+git@thekompanee.com
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files:
60
+ - README.md
61
+ - LICENSE
62
+ files:
63
+ - LICENSE
64
+ - README.md
65
+ - Rakefile
66
+ - lib/apill.rb
67
+ - lib/apill/accept_header.rb
68
+ - lib/apill/errors/invalid_api_request_error.rb
69
+ - lib/apill/matchers/accept_header_matcher.rb
70
+ - lib/apill/matchers/generic_matcher.rb
71
+ - lib/apill/matchers/invalid_api_request_matcher.rb
72
+ - lib/apill/matchers/version_matcher.rb
73
+ - lib/apill/responses/invalid_api_request_response.rb
74
+ - lib/apill/version.rb
75
+ - spec/lib/apill/accept_header_spec.rb
76
+ - spec/lib/apill/errors/invalid_api_request_error_spec.rb
77
+ - spec/lib/apill/matchers/accept_header_matcher_spec.rb
78
+ - spec/lib/apill/matchers/invalid_api_request_matcher_spec.rb
79
+ - spec/lib/apill/matchers/version_matcher_spec.rb
80
+ homepage: https://github.com/jfelchner/apill
81
+ licenses: []
82
+ metadata: {}
83
+ post_install_message:
84
+ rdoc_options:
85
+ - "--charset = UTF-8"
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ requirements: []
99
+ rubyforge_project: apill
100
+ rubygems_version: 2.2.2
101
+ signing_key:
102
+ specification_version: 4
103
+ summary: Common API functionality
104
+ test_files:
105
+ - spec/lib/apill/accept_header_spec.rb
106
+ - spec/lib/apill/errors/invalid_api_request_error_spec.rb
107
+ - spec/lib/apill/matchers/accept_header_matcher_spec.rb
108
+ - spec/lib/apill/matchers/invalid_api_request_matcher_spec.rb
109
+ - spec/lib/apill/matchers/version_matcher_spec.rb
110
+ has_rdoc: