apill 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: