karatekit 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.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +38 -0
- data/MIT-LICENSE.md +20 -0
- data/README.md +7 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/karatekit.gemspec +31 -0
- data/lib/karatekit/arguments.rb +14 -0
- data/lib/karatekit/authentication.rb +15 -0
- data/lib/karatekit/client/event_details.rb +27 -0
- data/lib/karatekit/client/event_parts.rb +27 -0
- data/lib/karatekit/client/events.rb +27 -0
- data/lib/karatekit/client/instructor_details.rb +27 -0
- data/lib/karatekit/client/instructor_group_members.rb +27 -0
- data/lib/karatekit/client/instructor_groups.rb +27 -0
- data/lib/karatekit/client/instructors.rb +27 -0
- data/lib/karatekit/client/locations.rb +27 -0
- data/lib/karatekit/client/posts.rb +27 -0
- data/lib/karatekit/client/products.rb +27 -0
- data/lib/karatekit/client/rate_limit.rb +53 -0
- data/lib/karatekit/client/sessions.rb +27 -0
- data/lib/karatekit/client.rb +78 -0
- data/lib/karatekit/configurable.rb +80 -0
- data/lib/karatekit/connection.rb +191 -0
- data/lib/karatekit/default.rb +97 -0
- data/lib/karatekit/error.rb +244 -0
- data/lib/karatekit/rate_limit.rb +33 -0
- data/lib/karatekit/response/raise_error.rb +21 -0
- data/lib/karatekit/version.rb +3 -0
- data/lib/karatekit/warnable.rb +16 -0
- data/lib/karatekit.rb +35 -0
- metadata +154 -0
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'karatekit/connection'
|
2
|
+
require 'karatekit/warnable'
|
3
|
+
require 'karatekit/arguments'
|
4
|
+
require 'karatekit/configurable'
|
5
|
+
require 'karatekit/authentication'
|
6
|
+
require 'karatekit/rate_limit'
|
7
|
+
|
8
|
+
require 'karatekit/client/locations'
|
9
|
+
require 'karatekit/client/products'
|
10
|
+
require 'karatekit/client/sessions'
|
11
|
+
require 'karatekit/client/events'
|
12
|
+
require 'karatekit/client/event_details'
|
13
|
+
require 'karatekit/client/event_parts'
|
14
|
+
require 'karatekit/client/posts'
|
15
|
+
require 'karatekit/client/instructors'
|
16
|
+
require 'karatekit/client/instructor_details'
|
17
|
+
require 'karatekit/client/instructor_groups'
|
18
|
+
require 'karatekit/client/instructor_group_members'
|
19
|
+
require 'karatekit/client/rate_limit'
|
20
|
+
|
21
|
+
module Karatekit
|
22
|
+
|
23
|
+
# Client for the kampfsport.center API
|
24
|
+
#
|
25
|
+
# @see https://developer.kampfsport.center
|
26
|
+
class Client
|
27
|
+
|
28
|
+
include Karatekit::Authentication
|
29
|
+
include Karatekit::Configurable
|
30
|
+
include Karatekit::Connection
|
31
|
+
include Karatekit::Warnable
|
32
|
+
|
33
|
+
include Karatekit::Client::Locations
|
34
|
+
include Karatekit::Client::Products
|
35
|
+
include Karatekit::Client::Sessions
|
36
|
+
include Karatekit::Client::Events
|
37
|
+
include Karatekit::Client::EventDetails
|
38
|
+
include Karatekit::Client::EventParts
|
39
|
+
include Karatekit::Client::Posts
|
40
|
+
include Karatekit::Client::Instructors
|
41
|
+
include Karatekit::Client::InstructorDetails
|
42
|
+
include Karatekit::Client::InstructorGroups
|
43
|
+
include Karatekit::Client::InstructorGroupMembers
|
44
|
+
include Karatekit::Client::RateLimit
|
45
|
+
|
46
|
+
# Header keys that can be passed in options hash to {#get},{#head}
|
47
|
+
CONVENIENCE_HEADERS = Set.new([:accept, :content_type])
|
48
|
+
|
49
|
+
def initialize(options = {})
|
50
|
+
# Use options passed in, but fall back to module defaults
|
51
|
+
Karatekit::Configurable.keys.each do |key|
|
52
|
+
value = options.key?(key) ? options[key] : Karatekit.instance_variable_get(:"@#{key}")
|
53
|
+
instance_variable_set(:"@#{key}", value)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Text representation of the client, masking tokens and passwords
|
58
|
+
#
|
59
|
+
# @return [String]
|
60
|
+
def inspect
|
61
|
+
inspected = super
|
62
|
+
|
63
|
+
if @access_token
|
64
|
+
inspected = inspected.gsub! @access_token, "#{'*'*36}#{@access_token[36..-1]}"
|
65
|
+
end
|
66
|
+
|
67
|
+
inspected
|
68
|
+
end
|
69
|
+
|
70
|
+
# Set OAuth access token for authentication
|
71
|
+
#
|
72
|
+
# @param value [String] 40 character kampfsport.center OAuth access token
|
73
|
+
def access_token=(value)
|
74
|
+
reset_agent
|
75
|
+
@access_token = value
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Karatekit
|
2
|
+
|
3
|
+
# Configuration options for {Client}, defaulting to values
|
4
|
+
# in {Default}
|
5
|
+
module Configurable
|
6
|
+
# @!attribute [w] access_token
|
7
|
+
# @see https://developer.github.com/v3/oauth/
|
8
|
+
# @return [String] OAuth2 access token for authentication
|
9
|
+
# @!attribute api_endpoint
|
10
|
+
# @return [String] Base URL for API requests. default: https://api.github.com/
|
11
|
+
# @!attribute auto_paginate
|
12
|
+
# @return [Boolean] Auto fetch next page of results until rate limit reached
|
13
|
+
# @!attribute connection_options
|
14
|
+
# @see https://github.com/lostisland/faraday
|
15
|
+
# @return [Hash] Configure connection options for Faraday
|
16
|
+
# @!attribute middleware
|
17
|
+
# @see https://github.com/lostisland/faraday
|
18
|
+
# @return [Faraday::Builder or Faraday::RackBuilder] Configure middleware for Faraday
|
19
|
+
# @!attribute per_page
|
20
|
+
# @return [String] Configure page size for paginated results. API default: 30
|
21
|
+
# @!attribute user_agent
|
22
|
+
# @return [String] Configure User-Agent header for requests.
|
23
|
+
|
24
|
+
attr_accessor :access_token, :auto_paginate,
|
25
|
+
:connection_options, :default_media_type,
|
26
|
+
:middleware,
|
27
|
+
:per_page, :user_agent
|
28
|
+
attr_writer :api_endpoint
|
29
|
+
|
30
|
+
class << self
|
31
|
+
|
32
|
+
# List of configurable keys for {Karatekit::Client}
|
33
|
+
# @return [Array] of option keys
|
34
|
+
def keys
|
35
|
+
@keys ||= [
|
36
|
+
:access_token,
|
37
|
+
:api_endpoint,
|
38
|
+
:default_media_type,
|
39
|
+
:auto_paginate,
|
40
|
+
:connection_options,
|
41
|
+
:middleware,
|
42
|
+
:per_page,
|
43
|
+
:user_agent,
|
44
|
+
]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Set configuration options using a block
|
49
|
+
def configure
|
50
|
+
yield self
|
51
|
+
end
|
52
|
+
|
53
|
+
# Reset configuration options to default values
|
54
|
+
def reset!
|
55
|
+
Karatekit::Configurable.keys.each do |key|
|
56
|
+
instance_variable_set(:"@#{key}", Karatekit::Default.options[key])
|
57
|
+
end
|
58
|
+
self
|
59
|
+
end
|
60
|
+
alias setup reset!
|
61
|
+
|
62
|
+
# Compares client options to a Hash of requested options
|
63
|
+
#
|
64
|
+
# @param opts [Hash] Options to compare with current client options
|
65
|
+
# @return [Boolean]
|
66
|
+
def same_options?(opts)
|
67
|
+
opts.hash == options.hash
|
68
|
+
end
|
69
|
+
|
70
|
+
def api_endpoint
|
71
|
+
File.join(@api_endpoint, "")
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def options
|
77
|
+
Hash[Karatekit::Configurable.keys.map{|key| [key, instance_variable_get(:"@#{key}")]}]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
require 'sawyer'
|
2
|
+
require 'karatekit/authentication'
|
3
|
+
module Karatekit
|
4
|
+
|
5
|
+
# Network layer for API clients.
|
6
|
+
module Connection
|
7
|
+
|
8
|
+
include Karatekit::Authentication
|
9
|
+
|
10
|
+
# Header keys that can be passed in options hash to {#get},{#head}
|
11
|
+
CONVENIENCE_HEADERS = Set.new([:accept, :content_type])
|
12
|
+
|
13
|
+
# Make a HTTP GET request
|
14
|
+
#
|
15
|
+
# @param url [String] The path, relative to {#api_endpoint}
|
16
|
+
# @param options [Hash] Query and header params for request
|
17
|
+
# @return [Sawyer::Resource]
|
18
|
+
def get(url, options = {})
|
19
|
+
request :get, url, parse_query_and_convenience_headers(options)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Make a HTTP POST request
|
23
|
+
#
|
24
|
+
# @param url [String] The path, relative to {#api_endpoint}
|
25
|
+
# @param options [Hash] Body and header params for request
|
26
|
+
# @return [Sawyer::Resource]
|
27
|
+
def post(url, options = {})
|
28
|
+
request :post, url, options
|
29
|
+
end
|
30
|
+
|
31
|
+
# Make a HTTP PUT request
|
32
|
+
#
|
33
|
+
# @param url [String] The path, relative to {#api_endpoint}
|
34
|
+
# @param options [Hash] Body and header params for request
|
35
|
+
# @return [Sawyer::Resource]
|
36
|
+
def put(url, options = {})
|
37
|
+
request :put, url, options
|
38
|
+
end
|
39
|
+
|
40
|
+
# Make a HTTP PATCH request
|
41
|
+
#
|
42
|
+
# @param url [String] The path, relative to {#api_endpoint}
|
43
|
+
# @param options [Hash] Body and header params for request
|
44
|
+
# @return [Sawyer::Resource]
|
45
|
+
def patch(url, options = {})
|
46
|
+
request :patch, url, options
|
47
|
+
end
|
48
|
+
|
49
|
+
# Make a HTTP DELETE request
|
50
|
+
#
|
51
|
+
# @param url [String] The path, relative to {#api_endpoint}
|
52
|
+
# @param options [Hash] Query and header params for request
|
53
|
+
# @return [Sawyer::Resource]
|
54
|
+
def delete(url, options = {})
|
55
|
+
request :delete, url, options
|
56
|
+
end
|
57
|
+
|
58
|
+
# Make a HTTP HEAD request
|
59
|
+
#
|
60
|
+
# @param url [String] The path, relative to {#api_endpoint}
|
61
|
+
# @param options [Hash] Query and header params for request
|
62
|
+
# @return [Sawyer::Resource]
|
63
|
+
def head(url, options = {})
|
64
|
+
request :head, url, parse_query_and_convenience_headers(options)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Make one or more HTTP GET requests, optionally fetching
|
68
|
+
# the next page of results from URL in Link response header based
|
69
|
+
# on value in {#auto_paginate}.
|
70
|
+
#
|
71
|
+
# @param url [String] The path, relative to {#api_endpoint}
|
72
|
+
# @param options [Hash] Query and header params for request
|
73
|
+
# @param block [Block] Block to perform the data concatination of the
|
74
|
+
# multiple requests. The block is called with two parameters, the first
|
75
|
+
# contains the contents of the requests so far and the second parameter
|
76
|
+
# contains the latest response.
|
77
|
+
# @return [Sawyer::Resource]
|
78
|
+
def paginate(url, options = {}, &block)
|
79
|
+
opts = parse_query_and_convenience_headers(options)
|
80
|
+
if @auto_paginate || @per_page
|
81
|
+
opts[:query][:per_page] ||= @per_page || (@auto_paginate ? 100 : nil)
|
82
|
+
end
|
83
|
+
|
84
|
+
data = request(:get, url, opts.dup)
|
85
|
+
|
86
|
+
if @auto_paginate
|
87
|
+
while @last_response.rels[:next] && rate_limit.remaining > 0
|
88
|
+
@last_response = @last_response.rels[:next].get(:headers => opts[:headers])
|
89
|
+
if block_given?
|
90
|
+
yield(data, @last_response)
|
91
|
+
else
|
92
|
+
data.concat(@last_response.data) if @last_response.data.is_a?(Array)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
data
|
99
|
+
end
|
100
|
+
|
101
|
+
# Hypermedia agent for the kampfsport.center API
|
102
|
+
#
|
103
|
+
# @return [Sawyer::Agent]
|
104
|
+
def agent
|
105
|
+
@agent ||= Sawyer::Agent.new(endpoint, sawyer_options) do |http|
|
106
|
+
http.headers[:accept] = default_media_type
|
107
|
+
http.headers[:content_type] = "application/json"
|
108
|
+
http.headers[:user_agent] = user_agent
|
109
|
+
http.authorization 'Token', @access_token
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Fetch the root resource for the API
|
114
|
+
#
|
115
|
+
# @return [Sawyer::Resource]
|
116
|
+
def root
|
117
|
+
get "/"
|
118
|
+
end
|
119
|
+
|
120
|
+
# Response for last HTTP request
|
121
|
+
#
|
122
|
+
# @return [Sawyer::Response]
|
123
|
+
def last_response
|
124
|
+
@last_response if defined? @last_response
|
125
|
+
end
|
126
|
+
|
127
|
+
protected
|
128
|
+
|
129
|
+
def endpoint
|
130
|
+
api_endpoint
|
131
|
+
end
|
132
|
+
|
133
|
+
private
|
134
|
+
|
135
|
+
def reset_agent
|
136
|
+
@agent = nil
|
137
|
+
end
|
138
|
+
|
139
|
+
def request(method, path, data, options = {})
|
140
|
+
if data.is_a?(Hash)
|
141
|
+
options[:query] = data.delete(:query) || {}
|
142
|
+
options[:headers] = data.delete(:headers) || {}
|
143
|
+
if accept = data.delete(:accept)
|
144
|
+
options[:headers][:accept] = accept
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
@last_response = response = agent.call(method, Addressable::URI.parse(path.to_s).normalize.to_s, data, options)
|
149
|
+
response.data
|
150
|
+
end
|
151
|
+
|
152
|
+
# Executes the request, checking if it was successful
|
153
|
+
#
|
154
|
+
# @return [Boolean] True on success, false otherwise
|
155
|
+
def boolean_from_response(method, path, options = {})
|
156
|
+
request(method, path, options)
|
157
|
+
@last_response.status == 204
|
158
|
+
rescue Karatekit::NotFound
|
159
|
+
false
|
160
|
+
end
|
161
|
+
|
162
|
+
def sawyer_options
|
163
|
+
opts = {
|
164
|
+
:links_parser => Sawyer::LinkParsers::Simple.new
|
165
|
+
}
|
166
|
+
conn_opts = @connection_options
|
167
|
+
conn_opts[:builder] = @middleware if @middleware
|
168
|
+
conn_opts[:proxy] = @proxy if @proxy
|
169
|
+
conn_opts[:ssl] = { :verify_mode => @ssl_verify_mode } if @ssl_verify_mode
|
170
|
+
opts[:faraday] = Faraday.new(conn_opts)
|
171
|
+
|
172
|
+
opts
|
173
|
+
end
|
174
|
+
|
175
|
+
def parse_query_and_convenience_headers(options)
|
176
|
+
options = options.dup
|
177
|
+
headers = options.delete(:headers) { Hash.new }
|
178
|
+
CONVENIENCE_HEADERS.each do |h|
|
179
|
+
if header = options.delete(h)
|
180
|
+
headers[h] = header
|
181
|
+
end
|
182
|
+
end
|
183
|
+
query = options.delete(:query)
|
184
|
+
opts = {:query => options}
|
185
|
+
opts[:query].merge!(query) if query && query.is_a?(Hash)
|
186
|
+
opts[:headers] = headers unless headers.empty?
|
187
|
+
|
188
|
+
opts
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# require 'karatekit/middleware/follow_redirects'
|
2
|
+
require 'karatekit/response/raise_error'
|
3
|
+
# require 'karatekit/response/feed_parser'
|
4
|
+
require 'karatekit/version'
|
5
|
+
|
6
|
+
module Karatekit
|
7
|
+
|
8
|
+
# Default configuration options for {Client}
|
9
|
+
module Default
|
10
|
+
|
11
|
+
# Default API endpoint
|
12
|
+
API_ENDPOINT = "https://api.kampfsport.center".freeze
|
13
|
+
|
14
|
+
# Default User Agent header string
|
15
|
+
USER_AGENT = "Karatekit Ruby Gem #{Karatekit::VERSION}".freeze
|
16
|
+
|
17
|
+
# Default media type
|
18
|
+
MEDIA_TYPE = "application/json".freeze
|
19
|
+
|
20
|
+
# In Faraday 0.9, Faraday::Builder was renamed to Faraday::RackBuilder
|
21
|
+
RACK_BUILDER_CLASS = defined?(Faraday::RackBuilder) ? Faraday::RackBuilder : Faraday::Builder
|
22
|
+
|
23
|
+
# Default Faraday middleware stack
|
24
|
+
MIDDLEWARE = RACK_BUILDER_CLASS.new do |builder|
|
25
|
+
builder.use Faraday::Request::Retry, exceptions: [Karatekit::ServerError]
|
26
|
+
# builder.use Karatekit::Middleware::FollowRedirects
|
27
|
+
builder.use Karatekit::Response::RaiseError
|
28
|
+
# builder.use Karatekit::Response::FeedParser
|
29
|
+
builder.adapter Faraday.default_adapter
|
30
|
+
end
|
31
|
+
|
32
|
+
class << self
|
33
|
+
|
34
|
+
# Configuration options
|
35
|
+
# @return [Hash]
|
36
|
+
def options
|
37
|
+
Hash[Karatekit::Configurable.keys.map{|key| [key, send(key)]}]
|
38
|
+
end
|
39
|
+
|
40
|
+
# Default access token from ENV
|
41
|
+
# @return [String]
|
42
|
+
def access_token
|
43
|
+
ENV['KARATEKIT_ACCESS_TOKEN']
|
44
|
+
end
|
45
|
+
|
46
|
+
# Default API endpoint from ENV or {API_ENDPOINT}
|
47
|
+
# @return [String]
|
48
|
+
def api_endpoint
|
49
|
+
ENV['KARATEKIT_API_ENDPOINT'] || API_ENDPOINT
|
50
|
+
end
|
51
|
+
|
52
|
+
# Default pagination preference from ENV
|
53
|
+
# @return [String]
|
54
|
+
def auto_paginate
|
55
|
+
ENV['KARATEKIT_AUTO_PAGINATE']
|
56
|
+
end
|
57
|
+
|
58
|
+
# Default options for Faraday::Connection
|
59
|
+
# @return [Hash]
|
60
|
+
def connection_options
|
61
|
+
{
|
62
|
+
:headers => {
|
63
|
+
:accept => default_media_type,
|
64
|
+
:user_agent => user_agent
|
65
|
+
}
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
# Default media type from {MEDIA_TYPE}
|
70
|
+
# @return [String]
|
71
|
+
def default_media_type
|
72
|
+
MEDIA_TYPE
|
73
|
+
end
|
74
|
+
|
75
|
+
# Default middleware stack for Faraday::Connection
|
76
|
+
# from {MIDDLEWARE}
|
77
|
+
# @return [Faraday::RackBuilder or Faraday::Builder]
|
78
|
+
def middleware
|
79
|
+
MIDDLEWARE
|
80
|
+
end
|
81
|
+
|
82
|
+
# Default pagination page size from ENV
|
83
|
+
# @return [Integer] Page size
|
84
|
+
def per_page
|
85
|
+
page_size = ENV['KARATEKIT_PER_PAGE']
|
86
|
+
|
87
|
+
page_size.to_i if page_size
|
88
|
+
end
|
89
|
+
|
90
|
+
# Default User-Agent header string from ENV or {USER_AGENT}
|
91
|
+
# @return [String]
|
92
|
+
def user_agent
|
93
|
+
ENV['KARATEKIT_USER_AGENT'] || USER_AGENT
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,244 @@
|
|
1
|
+
module Karatekit
|
2
|
+
# Custom error class for rescuing from all kampfsport.center errors
|
3
|
+
class Error < StandardError
|
4
|
+
|
5
|
+
# Returns the appropriate Karatekit::Error subclass based
|
6
|
+
# on status and response message
|
7
|
+
#
|
8
|
+
# @param [Hash] response HTTP response
|
9
|
+
# @return [Karatekit::Error]
|
10
|
+
def self.from_response(response)
|
11
|
+
status = response[:status].to_i
|
12
|
+
body = response[:body].to_s
|
13
|
+
headers = response[:response_headers]
|
14
|
+
|
15
|
+
if klass = case status
|
16
|
+
when 400 then Karatekit::BadRequest
|
17
|
+
when 401 then error_for_401(headers)
|
18
|
+
when 403 then error_for_403(body)
|
19
|
+
when 404 then error_for_404(body)
|
20
|
+
when 405 then Karatekit::MethodNotAllowed
|
21
|
+
when 406 then Karatekit::NotAcceptable
|
22
|
+
when 409 then Karatekit::Conflict
|
23
|
+
when 415 then Karatekit::UnsupportedMediaType
|
24
|
+
when 422 then Karatekit::UnprocessableEntity
|
25
|
+
when 451 then Karatekit::UnavailableForLegalReasons
|
26
|
+
when 400..499 then Karatekit::ClientError
|
27
|
+
when 500 then Karatekit::InternalServerError
|
28
|
+
when 501 then Karatekit::NotImplemented
|
29
|
+
when 502 then Karatekit::BadGateway
|
30
|
+
when 503 then Karatekit::ServiceUnavailable
|
31
|
+
when 500..599 then Karatekit::ServerError
|
32
|
+
end
|
33
|
+
klass.new(response)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def initialize(response=nil)
|
38
|
+
@response = response
|
39
|
+
super(build_error_message)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Documentation URL returned by the API for some errors
|
43
|
+
#
|
44
|
+
# @return [String]
|
45
|
+
def documentation_url
|
46
|
+
data[:documentation_url] if data.is_a? Hash
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns most appropriate error for 401 HTTP status code
|
50
|
+
# @private
|
51
|
+
def self.error_for_401(headers)
|
52
|
+
Karatekit::Unauthorized
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns most appropriate error for 403 HTTP status code
|
56
|
+
# @private
|
57
|
+
def self.error_for_403(body)
|
58
|
+
if body =~ /rate limit exceeded/i
|
59
|
+
Karatekit::TooManyRequests
|
60
|
+
elsif body =~ /login attempts exceeded/i
|
61
|
+
Karatekit::TooManyLoginAttempts
|
62
|
+
elsif body =~ /abuse/i
|
63
|
+
Karatekit::AbuseDetected
|
64
|
+
elsif body =~ /email address must be verified/i
|
65
|
+
Karatekit::UnverifiedEmail
|
66
|
+
elsif body =~ /account was suspended/i
|
67
|
+
Karatekit::AccountSuspended
|
68
|
+
else
|
69
|
+
Karatekit::Forbidden
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Return most appropriate error for 404 HTTP status code
|
74
|
+
# @private
|
75
|
+
def self.error_for_404(body)
|
76
|
+
Karatekit::NotFound
|
77
|
+
end
|
78
|
+
|
79
|
+
# Array of validation errors
|
80
|
+
# @return [Array<Hash>] Error info
|
81
|
+
def errors
|
82
|
+
if data && data.is_a?(Hash)
|
83
|
+
data[:errors] || []
|
84
|
+
else
|
85
|
+
[]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Status code returned by the kampfsport.center server.
|
90
|
+
#
|
91
|
+
# @return [Integer]
|
92
|
+
def response_status
|
93
|
+
@response[:status]
|
94
|
+
end
|
95
|
+
|
96
|
+
# Headers returned by the kampfsport.center server.
|
97
|
+
#
|
98
|
+
# @return [Hash]
|
99
|
+
def response_headers
|
100
|
+
@response[:response_headers]
|
101
|
+
end
|
102
|
+
|
103
|
+
# Body returned by the kampfsport.center server.
|
104
|
+
#
|
105
|
+
# @return [String]
|
106
|
+
def response_body
|
107
|
+
@response[:body]
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def data
|
113
|
+
@data ||=
|
114
|
+
if (body = @response[:body]) && !body.empty?
|
115
|
+
if body.is_a?(String) &&
|
116
|
+
@response[:response_headers] &&
|
117
|
+
@response[:response_headers][:content_type] =~ /json/
|
118
|
+
|
119
|
+
Sawyer::Agent.serializer.decode(body)
|
120
|
+
else
|
121
|
+
body
|
122
|
+
end
|
123
|
+
else
|
124
|
+
nil
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def response_message
|
129
|
+
case data
|
130
|
+
when Hash
|
131
|
+
data[:message]
|
132
|
+
when String
|
133
|
+
data
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def response_error
|
138
|
+
"Error: #{data[:error]}" if data.is_a?(Hash) && data[:error]
|
139
|
+
end
|
140
|
+
|
141
|
+
def response_error_summary
|
142
|
+
return nil unless data.is_a?(Hash) && !Array(data[:errors]).empty?
|
143
|
+
|
144
|
+
summary = "\nError summary:\n"
|
145
|
+
summary << data[:errors].map do |error|
|
146
|
+
if error.is_a? Hash
|
147
|
+
error.map { |k,v| " #{k}: #{v}" }
|
148
|
+
else
|
149
|
+
" #{error}"
|
150
|
+
end
|
151
|
+
end.join("\n")
|
152
|
+
|
153
|
+
summary
|
154
|
+
end
|
155
|
+
|
156
|
+
def build_error_message
|
157
|
+
return nil if @response.nil?
|
158
|
+
|
159
|
+
message = "#{@response[:method].to_s.upcase} "
|
160
|
+
message << redact_url(@response[:url].to_s) + ": "
|
161
|
+
message << "#{@response[:status]} - "
|
162
|
+
message << "#{response_message}" unless response_message.nil?
|
163
|
+
message << "#{response_error}" unless response_error.nil?
|
164
|
+
message << "#{response_error_summary}" unless response_error_summary.nil?
|
165
|
+
message << " // See: #{documentation_url}" unless documentation_url.nil?
|
166
|
+
message
|
167
|
+
end
|
168
|
+
|
169
|
+
def redact_url(url_string)
|
170
|
+
%w[client_secret access_token].each do |token|
|
171
|
+
url_string.gsub!(/#{token}=\S+/, "#{token}=(redacted)") if url_string.include? token
|
172
|
+
end
|
173
|
+
url_string
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# Raised on errors in the 400-499 range
|
178
|
+
class ClientError < Error; end
|
179
|
+
|
180
|
+
# Raised when kampfsport.center returns a 400 HTTP status code
|
181
|
+
class BadRequest < ClientError; end
|
182
|
+
|
183
|
+
# Raised when kampfsport.center returns a 401 HTTP status code
|
184
|
+
class Unauthorized < ClientError; end
|
185
|
+
|
186
|
+
# Raised when kampfsport.center returns a 403 HTTP status code
|
187
|
+
class Forbidden < ClientError; end
|
188
|
+
|
189
|
+
# Raised when kampfsport.center returns a 403 HTTP status code
|
190
|
+
# and body matches 'rate limit exceeded'
|
191
|
+
class TooManyRequests < Forbidden; end
|
192
|
+
|
193
|
+
# Raised when kampfsport.center returns a 403 HTTP status code
|
194
|
+
# and body matches 'login attempts exceeded'
|
195
|
+
class TooManyLoginAttempts < Forbidden; end
|
196
|
+
|
197
|
+
# Raised when kampfsport.center returns a 403 HTTP status code
|
198
|
+
# and body matches 'abuse'
|
199
|
+
class AbuseDetected < Forbidden; end
|
200
|
+
|
201
|
+
# Raised when kampfsport.center returns a 403 HTTP status code
|
202
|
+
# and body matches 'email address must be verified'
|
203
|
+
class UnverifiedEmail < Forbidden; end
|
204
|
+
|
205
|
+
# Raised when kampfsport.center returns a 403 HTTP status code
|
206
|
+
# and body matches 'account was suspended'
|
207
|
+
class AccountSuspended < Forbidden; end
|
208
|
+
|
209
|
+
# Raised when kampfsport.center returns a 404 HTTP status code
|
210
|
+
class NotFound < ClientError; end
|
211
|
+
|
212
|
+
# Raised when kampfsport.center returns a 405 HTTP status code
|
213
|
+
class MethodNotAllowed < ClientError; end
|
214
|
+
|
215
|
+
# Raised when kampfsport.center returns a 406 HTTP status code
|
216
|
+
class NotAcceptable < ClientError; end
|
217
|
+
|
218
|
+
# Raised when kampfsport.center returns a 409 HTTP status code
|
219
|
+
class Conflict < ClientError; end
|
220
|
+
|
221
|
+
# Raised when kampfsport.center returns a 414 HTTP status code
|
222
|
+
class UnsupportedMediaType < ClientError; end
|
223
|
+
|
224
|
+
# Raised when kampfsport.center returns a 422 HTTP status code
|
225
|
+
class UnprocessableEntity < ClientError; end
|
226
|
+
|
227
|
+
# Raised on errors in the 500-599 range
|
228
|
+
class ServerError < Error; end
|
229
|
+
|
230
|
+
# Raised when kampfsport.center returns a 500 HTTP status code
|
231
|
+
class InternalServerError < ServerError; end
|
232
|
+
|
233
|
+
# Raised when kampfsport.center returns a 501 HTTP status code
|
234
|
+
class NotImplemented < ServerError; end
|
235
|
+
|
236
|
+
# Raised when kampfsport.center returns a 502 HTTP status code
|
237
|
+
class BadGateway < ServerError; end
|
238
|
+
|
239
|
+
# Raised when kampfsport.center returns a 503 HTTP status code
|
240
|
+
class ServiceUnavailable < ServerError; end
|
241
|
+
|
242
|
+
# Raised when client fails to provide valid Content-Type
|
243
|
+
class MissingContentType < ArgumentError; end
|
244
|
+
end
|