looker-sdk 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +52 -0
- data/.ruby-gemset +1 -0
- data/.travis.yml +16 -0
- data/Gemfile +22 -0
- data/LICENSE +21 -0
- data/Rakefile +37 -0
- data/authentication.md +104 -0
- data/examples/add_delete_users.rb +94 -0
- data/examples/change_credentials_email_address_for_users.rb +23 -0
- data/examples/create_credentials_email_for_users.rb +19 -0
- data/examples/delete_all_user_sessions.rb +15 -0
- data/examples/delete_credentials_google_for_users.rb +19 -0
- data/examples/generate_password_reset_tokens_for_users.rb +19 -0
- data/examples/ldap_roles_test.rb +50 -0
- data/examples/me.rb +3 -0
- data/examples/refresh_user_notification_addresses.rb +10 -0
- data/examples/roles_and_users_with_permission.rb +22 -0
- data/examples/sdk_setup.rb +21 -0
- data/examples/streaming_downloads.rb +20 -0
- data/examples/users_with_credentials_email.rb +6 -0
- data/examples/users_with_credentials_google.rb +8 -0
- data/examples/users_with_credentials_google_without_credentials_email.rb +6 -0
- data/lib/looker-sdk.rb +32 -0
- data/lib/looker-sdk/authentication.rb +104 -0
- data/lib/looker-sdk/client.rb +445 -0
- data/lib/looker-sdk/client/dynamic.rb +107 -0
- data/lib/looker-sdk/configurable.rb +116 -0
- data/lib/looker-sdk/default.rb +148 -0
- data/lib/looker-sdk/error.rb +235 -0
- data/lib/looker-sdk/rate_limit.rb +33 -0
- data/lib/looker-sdk/response/raise_error.rb +20 -0
- data/lib/looker-sdk/sawyer_patch.rb +33 -0
- data/lib/looker-sdk/version.rb +7 -0
- data/looker-sdk.gemspec +27 -0
- data/readme.md +117 -0
- data/shell/.gitignore +41 -0
- data/shell/Gemfile +6 -0
- data/shell/readme.md +18 -0
- data/shell/shell.rb +37 -0
- data/streaming.md +59 -0
- data/test/helper.rb +46 -0
- data/test/looker/swagger.json +1998 -0
- data/test/looker/test_client.rb +258 -0
- data/test/looker/test_dynamic_client.rb +158 -0
- data/test/looker/test_dynamic_client_agent.rb +131 -0
- data/test/looker/user.json +1 -0
- metadata +107 -0
@@ -0,0 +1,107 @@
|
|
1
|
+
module LookerSDK
|
2
|
+
class Client
|
3
|
+
|
4
|
+
module Dynamic
|
5
|
+
|
6
|
+
attr_accessor :dynamic
|
7
|
+
|
8
|
+
def try_load_swagger
|
9
|
+
resp = get('swagger.json') rescue nil
|
10
|
+
resp && last_response && last_response.status == 200 && last_response.data && last_response.data.to_attrs
|
11
|
+
end
|
12
|
+
|
13
|
+
# If a given client is created with ':shared_swagger => true' then it will try to
|
14
|
+
# use a globally sharable @@operations hash built from one fetch of the swagger.json for the
|
15
|
+
# given api_endpoint. This is an optimization for the cases where many sdk clients get created and
|
16
|
+
# destroyed (perhaps with different access tokens) while all talking to the same endpoints. This cuts
|
17
|
+
# down overhead for such cases considerably.
|
18
|
+
|
19
|
+
@@sharable_operations = Hash.new
|
20
|
+
|
21
|
+
def clear_swagger
|
22
|
+
@swagger = @operations = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def load_swagger
|
26
|
+
# We only need the swagger if we are going to be building our own 'operations' hash
|
27
|
+
return if shared_swagger && @@sharable_operations[api_endpoint]
|
28
|
+
# Try to load w/o authenticating. Else, authenticate and try again.
|
29
|
+
@swagger ||= without_authentication {try_load_swagger} || try_load_swagger
|
30
|
+
end
|
31
|
+
|
32
|
+
def operations
|
33
|
+
return @@sharable_operations[api_endpoint] if shared_swagger && @@sharable_operations[api_endpoint]
|
34
|
+
|
35
|
+
return nil unless @swagger
|
36
|
+
@operations ||= Hash[
|
37
|
+
@swagger[:paths].map do |path_name, path_info|
|
38
|
+
path_info.map do |method, route_info|
|
39
|
+
route = @swagger[:basePath].to_s + path_name.to_s
|
40
|
+
[route_info[:operationId], {:route => route, :method => method, :info => route_info}]
|
41
|
+
end
|
42
|
+
end.reduce(:+)
|
43
|
+
].freeze
|
44
|
+
|
45
|
+
shared_swagger ? (@@sharable_operations[api_endpoint] = @operations) : @operations
|
46
|
+
end
|
47
|
+
|
48
|
+
def method_link(entry)
|
49
|
+
uri = URI.parse(api_endpoint)
|
50
|
+
"#{uri.scheme}://#{uri.host}:#{uri.port}/api-docs/index.html#!/#{entry[:info][:tags].first}/#{entry[:info][:operationId]}" rescue "http://docs.looker.com/"
|
51
|
+
end
|
52
|
+
|
53
|
+
# Callers can explicitly 'invoke' remote methods or let 'method_missing' do the trick.
|
54
|
+
# If nothing else, this gives clients a way to deal with potential conflicts between remote method
|
55
|
+
# names and names of methods on client itself.
|
56
|
+
def invoke(method_name, *args, &block)
|
57
|
+
entry = find_entry(method_name) || raise(NameError, "undefined remote method '#{method_name}'")
|
58
|
+
invoke_remote(entry, method_name, *args, &block)
|
59
|
+
end
|
60
|
+
|
61
|
+
def method_missing(method_name, *args, &block)
|
62
|
+
entry = find_entry(method_name) || (return super)
|
63
|
+
invoke_remote(entry, method_name, *args, &block)
|
64
|
+
end
|
65
|
+
|
66
|
+
def respond_to?(method_name, include_private=false)
|
67
|
+
!!find_entry(method_name) || super
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def find_entry(method_name)
|
73
|
+
operations && operations[method_name.to_s] if dynamic
|
74
|
+
end
|
75
|
+
|
76
|
+
def invoke_remote(entry, method_name, *args, &block)
|
77
|
+
args = (args || []).dup
|
78
|
+
route = entry[:route].to_s.dup
|
79
|
+
params = (entry[:info][:parameters] || []).select {|param| param[:in] == 'path'}
|
80
|
+
body_param = (entry[:info][:parameters] || []).select {|param| param[:in] == 'body'}.first
|
81
|
+
|
82
|
+
params_passed = args.length
|
83
|
+
params_required = params.length + (body_param && body_param[:required] ? 1 : 0)
|
84
|
+
unless params_passed >= params_required
|
85
|
+
raise ArgumentError.new("wrong number of arguments (#{params_passed} for #{params_required}) in call to '#{method_name}'. See '#{method_link(entry)}'")
|
86
|
+
end
|
87
|
+
|
88
|
+
# substitute the actual params into the route template
|
89
|
+
params.each {|param| route.sub!("{#{param[:name]}}", args.shift.to_s) }
|
90
|
+
|
91
|
+
a = args.length > 0 ? args[0] : {}
|
92
|
+
b = args.length > 1 ? args[1] : {}
|
93
|
+
|
94
|
+
method = entry[:method].to_sym
|
95
|
+
case method
|
96
|
+
when :get then get(route, a, &block)
|
97
|
+
when :post then post(route, a, merge_content_type_if_body(a, b), &block)
|
98
|
+
when :put then put(route, a, merge_content_type_if_body(a, b), &block)
|
99
|
+
when :patch then patch(route, a, merge_content_type_if_body(a, b), &block)
|
100
|
+
when :delete then delete(route, a) ; @raw_responses ? last_response : delete_succeeded?
|
101
|
+
else raise "unsupported method '#{method}' in call to '#{method_name}'. See '#{method_link(entry)}'"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module LookerSDK
|
2
|
+
|
3
|
+
# Configuration options for {Client}, defaulting to values
|
4
|
+
# in {Default}
|
5
|
+
module Configurable
|
6
|
+
# @!attribute [w] access_token
|
7
|
+
# @see look TODO docs link
|
8
|
+
# @return [String] OAuth2 access token for authentication
|
9
|
+
# @!attribute api_endpoint
|
10
|
+
# @return [String] Base URL for API requests. default: https://api.looker.com/ look TODO: this is the wrong url... what's the right one? Also update all other references to "api.looker.com"
|
11
|
+
# @!attribute auto_paginate
|
12
|
+
# @return [Boolean] Auto fetch next page of results until rate limit reached
|
13
|
+
# @!attribute client_id
|
14
|
+
# @see look TODO docs link
|
15
|
+
# @return [String] Configure OAuth app key
|
16
|
+
# @!attribute [w] client_secret
|
17
|
+
# @see look TODO docs link
|
18
|
+
# @return [String] Configure OAuth app secret
|
19
|
+
# @!attribute default_media_type
|
20
|
+
# @see look TODO docs link
|
21
|
+
# @return [String] Configure preferred media type (for API versioning, for example)
|
22
|
+
# @!attribute connection_options
|
23
|
+
# @see https://github.com/lostisland/faraday
|
24
|
+
# @return [Hash] Configure connection options for Faraday
|
25
|
+
# @!attribute middleware
|
26
|
+
# @see https://github.com/lostisland/faraday
|
27
|
+
# @return [Faraday::Builder or Faraday::RackBuilder] Configure middleware for Faraday
|
28
|
+
# @!attribute netrc
|
29
|
+
# @return [Boolean] Instruct Looker to get credentials from .netrc file
|
30
|
+
# @!attribute netrc_file
|
31
|
+
# @return [String] Path to .netrc file. default: ~/.netrc
|
32
|
+
# @!attribute per_page
|
33
|
+
# @return [String] Configure page size for paginated results. API default: 30
|
34
|
+
# @!attribute proxy
|
35
|
+
# @see https://github.com/lostisland/faraday
|
36
|
+
# @return [String] URI for proxy server
|
37
|
+
# @!attribute user_agent
|
38
|
+
# @return [String] Configure User-Agent header for requests.
|
39
|
+
# @!attribute web_endpoint
|
40
|
+
# @return [String] Base URL for web URLs. default: https://<client>.looker.com/ look TODO is this correct?
|
41
|
+
|
42
|
+
attr_accessor :access_token, :auto_paginate, :client_id,
|
43
|
+
:client_secret, :default_media_type, :connection_options,
|
44
|
+
:middleware, :netrc, :netrc_file,
|
45
|
+
:per_page, :proxy, :user_agent, :faraday, :swagger, :shared_swagger, :raw_responses
|
46
|
+
attr_writer :web_endpoint, :api_endpoint
|
47
|
+
|
48
|
+
class << self
|
49
|
+
|
50
|
+
# List of configurable keys for {LookerSDK::Client}
|
51
|
+
# @return [Array] of option keys
|
52
|
+
def keys
|
53
|
+
@keys ||= [
|
54
|
+
:access_token,
|
55
|
+
:api_endpoint,
|
56
|
+
:auto_paginate,
|
57
|
+
:client_id,
|
58
|
+
:client_secret,
|
59
|
+
:connection_options,
|
60
|
+
:default_media_type,
|
61
|
+
:middleware,
|
62
|
+
:netrc,
|
63
|
+
:netrc_file,
|
64
|
+
:per_page,
|
65
|
+
:proxy,
|
66
|
+
:user_agent,
|
67
|
+
:faraday,
|
68
|
+
:shared_swagger,
|
69
|
+
:swagger,
|
70
|
+
:raw_responses,
|
71
|
+
:web_endpoint
|
72
|
+
]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Set configuration options using a block
|
77
|
+
def configure
|
78
|
+
yield self
|
79
|
+
end
|
80
|
+
|
81
|
+
# Reset configuration options to default values
|
82
|
+
def reset!
|
83
|
+
LookerSDK::Configurable.keys.each do |key|
|
84
|
+
instance_variable_set(:"@#{key}", LookerSDK::Default.options[key])
|
85
|
+
end
|
86
|
+
self
|
87
|
+
end
|
88
|
+
alias setup reset!
|
89
|
+
|
90
|
+
def api_endpoint
|
91
|
+
File.join(@api_endpoint, "")
|
92
|
+
end
|
93
|
+
|
94
|
+
# Base URL for generated web URLs
|
95
|
+
#
|
96
|
+
# @return [String] Default: https://<client>.looker.com/ look TODO is this correct?
|
97
|
+
def web_endpoint
|
98
|
+
File.join(@web_endpoint, "")
|
99
|
+
end
|
100
|
+
|
101
|
+
def netrc?
|
102
|
+
!!@netrc
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
def options
|
108
|
+
Hash[LookerSDK::Configurable.keys.map{|key| [key, instance_variable_get(:"@#{key}")]}]
|
109
|
+
end
|
110
|
+
|
111
|
+
def fetch_client_id_and_secret(overrides = {})
|
112
|
+
opts = options.merge(overrides)
|
113
|
+
opts.values_at :client_id, :client_secret
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'looker-sdk/response/raise_error'
|
2
|
+
require 'looker-sdk/version'
|
3
|
+
|
4
|
+
module LookerSDK
|
5
|
+
|
6
|
+
# Default configuration options for {Client}
|
7
|
+
module Default
|
8
|
+
|
9
|
+
# Default API endpoint look TODO update this as needed
|
10
|
+
API_ENDPOINT = "https://localhost:19999/api/3.0/".freeze
|
11
|
+
|
12
|
+
# Default User Agent header string
|
13
|
+
USER_AGENT = "Looker Ruby Gem #{LookerSDK::VERSION}".freeze
|
14
|
+
|
15
|
+
# Default media type
|
16
|
+
MEDIA_TYPE = "application/json"
|
17
|
+
|
18
|
+
# Default WEB endpoint
|
19
|
+
WEB_ENDPOINT = "https://localhost:9999".freeze # look TODO update this
|
20
|
+
|
21
|
+
# In Faraday 0.9, Faraday::Builder was renamed to Faraday::RackBuilder
|
22
|
+
RACK_BUILDER_CLASS = defined?(Faraday::RackBuilder) ? Faraday::RackBuilder : Faraday::Builder
|
23
|
+
|
24
|
+
# Default Faraday middleware stack
|
25
|
+
MIDDLEWARE = RACK_BUILDER_CLASS.new do |builder|
|
26
|
+
builder.use LookerSDK::Response::RaiseError
|
27
|
+
builder.adapter Faraday.default_adapter
|
28
|
+
end
|
29
|
+
|
30
|
+
class << self
|
31
|
+
|
32
|
+
# Configuration options
|
33
|
+
# @return [Hash]
|
34
|
+
def options
|
35
|
+
Hash[LookerSDK::Configurable.keys.map{|key| [key, send(key)]}]
|
36
|
+
end
|
37
|
+
|
38
|
+
# Default access token from ENV
|
39
|
+
# @return [String]
|
40
|
+
def access_token
|
41
|
+
ENV['LOOKER_ACCESS_TOKEN']
|
42
|
+
end
|
43
|
+
|
44
|
+
# Default API endpoint from ENV or {API_ENDPOINT}
|
45
|
+
# @return [String]
|
46
|
+
def api_endpoint
|
47
|
+
ENV['LOOKER_API_ENDPOINT'] || API_ENDPOINT
|
48
|
+
end
|
49
|
+
|
50
|
+
# Default pagination preference from ENV
|
51
|
+
# @return [String]
|
52
|
+
def auto_paginate
|
53
|
+
ENV['LOOKER_AUTO_PAGINATE']
|
54
|
+
end
|
55
|
+
|
56
|
+
# Default OAuth app key from ENV
|
57
|
+
# @return [String]
|
58
|
+
def client_id
|
59
|
+
ENV['LOOKER_CLIENT_ID']
|
60
|
+
end
|
61
|
+
|
62
|
+
# Default OAuth app secret from ENV
|
63
|
+
# @return [String]
|
64
|
+
def client_secret
|
65
|
+
ENV['LOOKER_SECRET']
|
66
|
+
end
|
67
|
+
|
68
|
+
# Default options for Faraday::Connection
|
69
|
+
# @return [Hash]
|
70
|
+
def connection_options
|
71
|
+
{
|
72
|
+
:headers => {
|
73
|
+
:accept => default_media_type,
|
74
|
+
:user_agent => user_agent
|
75
|
+
}
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
# Default media type from ENV or {MEDIA_TYPE}
|
80
|
+
# @return [String]
|
81
|
+
def default_media_type
|
82
|
+
ENV['LOOKER_DEFAULT_MEDIA_TYPE'] || MEDIA_TYPE
|
83
|
+
end
|
84
|
+
|
85
|
+
# Default middleware stack for Faraday::Connection
|
86
|
+
# from {MIDDLEWARE}
|
87
|
+
# @return [String]
|
88
|
+
def middleware
|
89
|
+
MIDDLEWARE
|
90
|
+
end
|
91
|
+
|
92
|
+
def faraday
|
93
|
+
nil
|
94
|
+
end
|
95
|
+
|
96
|
+
def swagger
|
97
|
+
nil
|
98
|
+
end
|
99
|
+
|
100
|
+
def shared_swagger
|
101
|
+
false
|
102
|
+
end
|
103
|
+
|
104
|
+
def raw_responses
|
105
|
+
false
|
106
|
+
end
|
107
|
+
|
108
|
+
# Default pagination page size from ENV
|
109
|
+
# @return [Fixnum] Page size
|
110
|
+
def per_page
|
111
|
+
page_size = ENV['LOOKER_PER_PAGE']
|
112
|
+
|
113
|
+
page_size.to_i if page_size
|
114
|
+
end
|
115
|
+
|
116
|
+
# Default proxy server URI for Faraday connection from ENV
|
117
|
+
# @return [String]
|
118
|
+
def proxy
|
119
|
+
ENV['LOOKER_PROXY']
|
120
|
+
end
|
121
|
+
|
122
|
+
# Default User-Agent header string from ENV or {USER_AGENT}
|
123
|
+
# @return [String]
|
124
|
+
def user_agent
|
125
|
+
ENV['LOOKER_USER_AGENT'] || USER_AGENT
|
126
|
+
end
|
127
|
+
|
128
|
+
# Default web endpoint from ENV or {WEB_ENDPOINT}
|
129
|
+
# @return [String]
|
130
|
+
def web_endpoint
|
131
|
+
ENV['LOOKER_WEB_ENDPOINT'] || WEB_ENDPOINT
|
132
|
+
end
|
133
|
+
|
134
|
+
# Default behavior for reading .netrc file
|
135
|
+
# @return [Boolean]
|
136
|
+
def netrc
|
137
|
+
ENV['LOOKER_NETRC'] || false
|
138
|
+
end
|
139
|
+
|
140
|
+
# Default path for .netrc file
|
141
|
+
# @return [String]
|
142
|
+
def netrc_file
|
143
|
+
ENV['LOOKER_NETRC_FILE'] || File.join(ENV['HOME'].to_s, '.netrc')
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,235 @@
|
|
1
|
+
module LookerSDK
|
2
|
+
class Error < StandardError
|
3
|
+
|
4
|
+
# Returns the appropriate LookerSDK::Error sublcass based
|
5
|
+
# on status and response message
|
6
|
+
#
|
7
|
+
# @param [Hash] response HTTP response
|
8
|
+
# @return [LookerSDK::Error]
|
9
|
+
def self.from_response(response)
|
10
|
+
status = response[:status].to_i
|
11
|
+
body = response[:body].to_s
|
12
|
+
headers = response[:response_headers]
|
13
|
+
|
14
|
+
if klass = case status
|
15
|
+
when 400 then LookerSDK::BadRequest
|
16
|
+
when 401 then error_for_401(headers)
|
17
|
+
when 403 then error_for_403(body)
|
18
|
+
when 404 then LookerSDK::NotFound
|
19
|
+
when 405 then LookerSDK::MethodNotAllowed
|
20
|
+
when 406 then LookerSDK::NotAcceptable
|
21
|
+
when 409 then LookerSDK::Conflict
|
22
|
+
when 415 then LookerSDK::UnsupportedMediaType
|
23
|
+
when 422 then LookerSDK::UnprocessableEntity
|
24
|
+
when 400..499 then LookerSDK::ClientError
|
25
|
+
when 500 then LookerSDK::InternalServerError
|
26
|
+
when 501 then LookerSDK::NotImplemented
|
27
|
+
when 502 then LookerSDK::BadGateway
|
28
|
+
when 503 then LookerSDK::ServiceUnavailable
|
29
|
+
when 500..599 then LookerSDK::ServerError
|
30
|
+
end
|
31
|
+
klass.new(response)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize(response=nil)
|
36
|
+
@response = response
|
37
|
+
super(build_error_message)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Documentation URL returned by the API for some errors
|
41
|
+
#
|
42
|
+
# @return [String]
|
43
|
+
def documentation_url
|
44
|
+
data[:documentation_url] if data.is_a? Hash
|
45
|
+
end
|
46
|
+
|
47
|
+
# Message string returned by the API for some errors
|
48
|
+
#
|
49
|
+
# @return [String]
|
50
|
+
def message
|
51
|
+
response_message
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns most appropriate error for 401 HTTP status code
|
55
|
+
# @private
|
56
|
+
def self.error_for_401(headers)
|
57
|
+
if LookerSDK::OneTimePasswordRequired.required_header(headers)
|
58
|
+
LookerSDK::OneTimePasswordRequired
|
59
|
+
else
|
60
|
+
LookerSDK::Unauthorized
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns most appropriate error for 403 HTTP status code
|
65
|
+
# @private
|
66
|
+
def self.error_for_403(body)
|
67
|
+
if body =~ /rate limit exceeded/i
|
68
|
+
LookerSDK::TooManyRequests
|
69
|
+
elsif body =~ /login attempts exceeded/i
|
70
|
+
LookerSDK::TooManyLoginAttempts
|
71
|
+
else
|
72
|
+
LookerSDK::Forbidden
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Array of validation errors
|
77
|
+
# @return [Array<Hash>] Error info
|
78
|
+
def errors
|
79
|
+
if data && data.is_a?(Hash)
|
80
|
+
data[:errors] || []
|
81
|
+
else
|
82
|
+
[]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def data
|
89
|
+
@data ||=
|
90
|
+
if (body = @response[:body]) && !body.empty?
|
91
|
+
if body.is_a?(String) &&
|
92
|
+
@response[:response_headers] &&
|
93
|
+
@response[:response_headers][:content_type] =~ /json/
|
94
|
+
|
95
|
+
Sawyer::Agent.serializer.decode(body)
|
96
|
+
else
|
97
|
+
body
|
98
|
+
end
|
99
|
+
else
|
100
|
+
nil
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def response_message
|
105
|
+
case data
|
106
|
+
when Hash
|
107
|
+
data[:message]
|
108
|
+
when String
|
109
|
+
data
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def response_error
|
114
|
+
"Error: #{data[:error]}" if data.is_a?(Hash) && data[:error]
|
115
|
+
end
|
116
|
+
|
117
|
+
def response_error_summary
|
118
|
+
return nil unless data.is_a?(Hash) && !Array(data[:errors]).empty?
|
119
|
+
|
120
|
+
summary = "\nError summary:\n"
|
121
|
+
summary << data[:errors].map do |hash|
|
122
|
+
hash.map { |k,v| " #{k}: #{v}" }
|
123
|
+
end.join("\n")
|
124
|
+
|
125
|
+
summary
|
126
|
+
end
|
127
|
+
|
128
|
+
def build_error_message
|
129
|
+
return nil if @response.nil?
|
130
|
+
|
131
|
+
message = "#{@response[:method].to_s.upcase} "
|
132
|
+
message << redact_url(@response[:url].to_s) + ": "
|
133
|
+
message << "#{@response[:status]} - "
|
134
|
+
message << "#{response_message}" unless response_message.nil?
|
135
|
+
message << "#{response_error}" unless response_error.nil?
|
136
|
+
message << "#{response_error_summary}" unless response_error_summary.nil?
|
137
|
+
message << " // See: #{documentation_url}" unless documentation_url.nil?
|
138
|
+
message
|
139
|
+
end
|
140
|
+
|
141
|
+
def redact_url(url_string)
|
142
|
+
%w[client_secret access_token].each do |token|
|
143
|
+
url_string.gsub!(/#{token}=\S+/, "#{token}=(redacted)") if url_string.include? token
|
144
|
+
end
|
145
|
+
url_string
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Raised on errors in the 400-499 range
|
150
|
+
class ClientError < Error; end
|
151
|
+
|
152
|
+
# Raised when API returns a 400 HTTP status code
|
153
|
+
class BadRequest < ClientError; end
|
154
|
+
|
155
|
+
# Raised when API returns a 401 HTTP status code
|
156
|
+
class Unauthorized < ClientError; end
|
157
|
+
|
158
|
+
# Raised when API returns a 401 HTTP status code
|
159
|
+
# and headers include "X-Looker-OTP" look TODO do we want to support this?
|
160
|
+
class OneTimePasswordRequired < ClientError
|
161
|
+
#@private
|
162
|
+
OTP_DELIVERY_PATTERN = /required; (\w+)/i
|
163
|
+
|
164
|
+
#@private
|
165
|
+
def self.required_header(headers)
|
166
|
+
OTP_DELIVERY_PATTERN.match headers['X-Looker-OTP'].to_s
|
167
|
+
end
|
168
|
+
|
169
|
+
# Delivery method for the user's OTP
|
170
|
+
#
|
171
|
+
# @return [String]
|
172
|
+
def password_delivery
|
173
|
+
@password_delivery ||= delivery_method_from_header
|
174
|
+
end
|
175
|
+
|
176
|
+
private
|
177
|
+
|
178
|
+
def delivery_method_from_header
|
179
|
+
if match = self.class.required_header(@response[:response_headers])
|
180
|
+
match[1]
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Raised when Looker returns a 403 HTTP status code
|
186
|
+
class Forbidden < ClientError; end
|
187
|
+
|
188
|
+
# Raised when Looker returns a 403 HTTP status code
|
189
|
+
# and body matches 'rate limit exceeded'
|
190
|
+
class TooManyRequests < Forbidden; end
|
191
|
+
|
192
|
+
# Raised when Looker returns a 403 HTTP status code
|
193
|
+
# and body matches 'login attempts exceeded'
|
194
|
+
class TooManyLoginAttempts < Forbidden; end
|
195
|
+
|
196
|
+
# Raised when Looker returns a 404 HTTP status code
|
197
|
+
class NotFound < ClientError; end
|
198
|
+
|
199
|
+
# Raised when Looker returns a 405 HTTP status code
|
200
|
+
class MethodNotAllowed < ClientError; end
|
201
|
+
|
202
|
+
# Raised when Looker returns a 406 HTTP status code
|
203
|
+
class NotAcceptable < ClientError; end
|
204
|
+
|
205
|
+
# Raised when Looker returns a 409 HTTP status code
|
206
|
+
class Conflict < ClientError; end
|
207
|
+
|
208
|
+
# Raised when Looker returns a 414 HTTP status code
|
209
|
+
class UnsupportedMediaType < ClientError; end
|
210
|
+
|
211
|
+
# Raised when Looker returns a 422 HTTP status code
|
212
|
+
class UnprocessableEntity < ClientError; end
|
213
|
+
|
214
|
+
# Raised on errors in the 500-599 range
|
215
|
+
class ServerError < Error; end
|
216
|
+
|
217
|
+
# Raised when Looker returns a 500 HTTP status code
|
218
|
+
class InternalServerError < ServerError; end
|
219
|
+
|
220
|
+
# Raised when Looker returns a 501 HTTP status code
|
221
|
+
class NotImplemented < ServerError; end
|
222
|
+
|
223
|
+
# Raised when Looker returns a 502 HTTP status code
|
224
|
+
class BadGateway < ServerError; end
|
225
|
+
|
226
|
+
# Raised when Looker returns a 503 HTTP status code
|
227
|
+
class ServiceUnavailable < ServerError; end
|
228
|
+
|
229
|
+
# Raised when client fails to provide valid Content-Type
|
230
|
+
class MissingContentType < ArgumentError; end
|
231
|
+
|
232
|
+
# Raised when a method requires an application client_id
|
233
|
+
# and secret but none is provided
|
234
|
+
class ApplicationCredentialsRequired < StandardError; end
|
235
|
+
end
|