looker-sdk 0.0.5
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 +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
|