smartsheet 2.77.0 → 2.101.1
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 +4 -4
- data/.gitignore +12 -12
- data/.rubocop.yml +4 -4
- data/.travis.yml +29 -15
- data/.yardopts +3 -3
- data/ADVANCED.md +78 -78
- data/CHANGELOG.md +133 -112
- data/Gemfile +6 -6
- data/LICENSE +202 -202
- data/README.md +248 -246
- data/Rakefile +29 -29
- data/bin/console +14 -14
- data/bin/setup +8 -8
- data/lib/smartsheet.rb +2 -2
- data/lib/smartsheet/api/body_builder.rb +25 -25
- data/lib/smartsheet/api/endpoint_spec.rb +54 -54
- data/lib/smartsheet/api/faraday_adapter/faraday_net_client.rb +45 -45
- data/lib/smartsheet/api/faraday_adapter/faraday_response.rb +70 -70
- data/lib/smartsheet/api/faraday_adapter/middleware/faraday_error_translator.rb +20 -20
- data/lib/smartsheet/api/faraday_adapter/middleware/response_parser.rb +25 -25
- data/lib/smartsheet/api/file_spec.rb +55 -55
- data/lib/smartsheet/api/header_builder.rb +96 -96
- data/lib/smartsheet/api/request.rb +42 -42
- data/lib/smartsheet/api/request_client.rb +43 -43
- data/lib/smartsheet/api/request_logger.rb +182 -182
- data/lib/smartsheet/api/request_spec.rb +57 -57
- data/lib/smartsheet/api/response_net_client_decorator.rb +54 -54
- data/lib/smartsheet/api/retry_logic.rb +40 -40
- data/lib/smartsheet/api/retry_net_client_decorator.rb +37 -37
- data/lib/smartsheet/api/url_builder.rb +25 -25
- data/lib/smartsheet/client.rb +193 -193
- data/lib/smartsheet/constants.rb +18 -18
- data/lib/smartsheet/endpoints/contacts/contacts.rb +30 -30
- data/lib/smartsheet/endpoints/events/events.rb +20 -20
- data/lib/smartsheet/endpoints/favorites/favorites.rb +159 -159
- data/lib/smartsheet/endpoints/folders/folders.rb +125 -125
- data/lib/smartsheet/endpoints/groups/groups.rb +83 -83
- data/lib/smartsheet/endpoints/home/home.rb +20 -20
- data/lib/smartsheet/endpoints/reports/reports.rb +100 -100
- data/lib/smartsheet/endpoints/reports/reports_share.rb +69 -69
- data/lib/smartsheet/endpoints/search/search.rb +30 -30
- data/lib/smartsheet/endpoints/server_info/server_info.rb +21 -21
- data/lib/smartsheet/endpoints/share/share.rb +58 -58
- data/lib/smartsheet/endpoints/sheets/automation_rules.rb +55 -55
- data/lib/smartsheet/endpoints/sheets/cells.rb +82 -82
- data/lib/smartsheet/endpoints/sheets/columns.rb +66 -66
- data/lib/smartsheet/endpoints/sheets/comments.rb +64 -64
- data/lib/smartsheet/endpoints/sheets/comments_attachments.rb +78 -78
- data/lib/smartsheet/endpoints/sheets/cross_sheet_references.rb +45 -45
- data/lib/smartsheet/endpoints/sheets/discussions.rb +84 -84
- data/lib/smartsheet/endpoints/sheets/discussions_attachments.rb +22 -22
- data/lib/smartsheet/endpoints/sheets/rows.rb +106 -106
- data/lib/smartsheet/endpoints/sheets/rows_attachments.rb +92 -92
- data/lib/smartsheet/endpoints/sheets/sheets.rb +514 -510
- data/lib/smartsheet/endpoints/sheets/sheets_attachments.rb +174 -174
- data/lib/smartsheet/endpoints/sheets/sheets_share.rb +69 -69
- data/lib/smartsheet/endpoints/sheets/sheets_summaries.rb +123 -0
- data/lib/smartsheet/endpoints/sights/sights.rb +101 -101
- data/lib/smartsheet/endpoints/sights/sights_share.rb +69 -69
- data/lib/smartsheet/endpoints/templates/templates.rb +29 -29
- data/lib/smartsheet/endpoints/token/token.rb +70 -66
- data/lib/smartsheet/endpoints/update_requests/sent_update_requests.rb +44 -44
- data/lib/smartsheet/endpoints/update_requests/update_requests.rb +74 -74
- data/lib/smartsheet/endpoints/users/alternate_emails.rb +79 -79
- data/lib/smartsheet/endpoints/users/users.rb +123 -77
- data/lib/smartsheet/endpoints/webhooks/webhooks.rb +71 -71
- data/lib/smartsheet/endpoints/workspaces/workspaces.rb +87 -87
- data/lib/smartsheet/endpoints/workspaces/workspaces_share.rb +70 -70
- data/lib/smartsheet/error.rb +69 -69
- data/lib/smartsheet/general_request.rb +74 -74
- data/lib/smartsheet/version.rb +5 -5
- data/smartsheet.gemspec +54 -54
- metadata +24 -12
@@ -1,44 +1,44 @@
|
|
1
|
-
require 'smartsheet/version'
|
2
|
-
require 'smartsheet/error'
|
3
|
-
|
4
|
-
module Smartsheet
|
5
|
-
module API
|
6
|
-
# Composes {EndpointSpec endpoint specifications} and {RequestSpec request specifications} to
|
7
|
-
# form a single {Request} that it submits to the provided client
|
8
|
-
class RequestClient
|
9
|
-
def initialize(
|
10
|
-
token,
|
11
|
-
client,
|
12
|
-
base_url,
|
13
|
-
app_user_agent: nil,
|
14
|
-
assume_user: nil,
|
15
|
-
logger: MuteRequestLogger.new
|
16
|
-
)
|
17
|
-
@token = token
|
18
|
-
@client = client
|
19
|
-
@app_user_agent = app_user_agent
|
20
|
-
@assume_user = assume_user
|
21
|
-
@logger = logger
|
22
|
-
@base_url = base_url
|
23
|
-
end
|
24
|
-
|
25
|
-
def make_request(endpoint_spec, request_spec)
|
26
|
-
request = Request.new(
|
27
|
-
token,
|
28
|
-
endpoint_spec,
|
29
|
-
request_spec,
|
30
|
-
base_url,
|
31
|
-
app_user_agent: app_user_agent,
|
32
|
-
assume_user: assume_user
|
33
|
-
)
|
34
|
-
|
35
|
-
logger.log_request(request)
|
36
|
-
client.make_request(request)
|
37
|
-
end
|
38
|
-
|
39
|
-
private
|
40
|
-
|
41
|
-
attr_reader :token, :client, :app_user_agent, :assume_user, :logger, :base_url
|
42
|
-
end
|
43
|
-
end
|
1
|
+
require 'smartsheet/version'
|
2
|
+
require 'smartsheet/error'
|
3
|
+
|
4
|
+
module Smartsheet
|
5
|
+
module API
|
6
|
+
# Composes {EndpointSpec endpoint specifications} and {RequestSpec request specifications} to
|
7
|
+
# form a single {Request} that it submits to the provided client
|
8
|
+
class RequestClient
|
9
|
+
def initialize(
|
10
|
+
token,
|
11
|
+
client,
|
12
|
+
base_url,
|
13
|
+
app_user_agent: nil,
|
14
|
+
assume_user: nil,
|
15
|
+
logger: MuteRequestLogger.new
|
16
|
+
)
|
17
|
+
@token = token
|
18
|
+
@client = client
|
19
|
+
@app_user_agent = app_user_agent
|
20
|
+
@assume_user = assume_user
|
21
|
+
@logger = logger
|
22
|
+
@base_url = base_url
|
23
|
+
end
|
24
|
+
|
25
|
+
def make_request(endpoint_spec, request_spec)
|
26
|
+
request = Request.new(
|
27
|
+
token,
|
28
|
+
endpoint_spec,
|
29
|
+
request_spec,
|
30
|
+
base_url,
|
31
|
+
app_user_agent: app_user_agent,
|
32
|
+
assume_user: assume_user
|
33
|
+
)
|
34
|
+
|
35
|
+
logger.log_request(request)
|
36
|
+
client.make_request(request)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
attr_reader :token, :client, :app_user_agent, :assume_user, :logger, :base_url
|
42
|
+
end
|
43
|
+
end
|
44
44
|
end
|
@@ -1,183 +1,183 @@
|
|
1
|
-
require 'logger'
|
2
|
-
|
3
|
-
module Smartsheet
|
4
|
-
module API
|
5
|
-
# Censors strings and hash values for select blacklisted keys
|
6
|
-
class Censor
|
7
|
-
EXPOSED_CHARS = 4
|
8
|
-
KEY_TO_STRING = ->(k){ k.to_s }
|
9
|
-
KEY_TO_DOWNCASE_STRING = ->(k){ k.to_s.downcase }
|
10
|
-
|
11
|
-
def initialize(*blacklist)
|
12
|
-
@blacklist = Set.new(blacklist)
|
13
|
-
end
|
14
|
-
|
15
|
-
def censor_hash(h, case_insensitive: false)
|
16
|
-
if case_insensitive
|
17
|
-
_censor_hash(h, KEY_TO_DOWNCASE_STRING, downcased_blacklist)
|
18
|
-
else
|
19
|
-
_censor_hash(h, KEY_TO_STRING, blacklist)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def censor(str)
|
24
|
-
total_length = str.length
|
25
|
-
censored_length = [total_length - EXPOSED_CHARS, 0].max
|
26
|
-
('*' * censored_length) + str[censored_length...total_length]
|
27
|
-
end
|
28
|
-
|
29
|
-
private
|
30
|
-
|
31
|
-
def _censor_hash(h, key_transform, cased_blacklist)
|
32
|
-
h.collect do |(k, v)|
|
33
|
-
new_v =
|
34
|
-
cased_blacklist.include?(key_transform.call(k)) ?
|
35
|
-
censor(v) :
|
36
|
-
v
|
37
|
-
|
38
|
-
[k, new_v]
|
39
|
-
end.to_h
|
40
|
-
end
|
41
|
-
|
42
|
-
def downcased_blacklist
|
43
|
-
blacklist.collect { |x| x.downcase }
|
44
|
-
end
|
45
|
-
|
46
|
-
attr_reader :blacklist
|
47
|
-
end
|
48
|
-
|
49
|
-
# Logs request and response information, while censoring OAuth-relevant keys
|
50
|
-
class RequestLogger
|
51
|
-
QUERY_PARAM_CENSOR = Censor.new 'code', 'client_id', 'hash', 'refresh_token'
|
52
|
-
HEADER_CENSOR = Censor.new 'authorization'
|
53
|
-
PAYLOAD_CENSOR = Censor.new 'access_token', 'refresh_token'
|
54
|
-
|
55
|
-
TRUNCATED_BODY_LENGTH = 1024
|
56
|
-
|
57
|
-
def initialize(logger, log_full_body:)
|
58
|
-
@logger = logger
|
59
|
-
@log_full_body = log_full_body
|
60
|
-
end
|
61
|
-
|
62
|
-
def log_request(request)
|
63
|
-
log_request_basics(Logger::INFO, request)
|
64
|
-
log_headers('Request', request)
|
65
|
-
log_body('Request', request.body)
|
66
|
-
end
|
67
|
-
|
68
|
-
def log_retry_attempt(request, response, attempt_num)
|
69
|
-
logger.warn { "Request attempt #{attempt_num} failed" }
|
70
|
-
log_request_basics(Logger::WARN, request)
|
71
|
-
log_api_error(Logger::WARN, response)
|
72
|
-
end
|
73
|
-
|
74
|
-
def log_retry_failure(num_tries)
|
75
|
-
try_word = num_tries == 1 ? 'try' : 'tries'
|
76
|
-
logger.error { "Request failed after #{num_tries} #{try_word}" }
|
77
|
-
end
|
78
|
-
|
79
|
-
def log_successful_response(response)
|
80
|
-
log_status(Logger::INFO, response)
|
81
|
-
log_headers('Response', response)
|
82
|
-
log_body('Response', response.result)
|
83
|
-
end
|
84
|
-
|
85
|
-
def log_api_error_response(request, error)
|
86
|
-
log_request_basics(Logger::ERROR, request)
|
87
|
-
log_api_error(Logger::ERROR, error)
|
88
|
-
end
|
89
|
-
|
90
|
-
def log_http_error_response(request, error)
|
91
|
-
log_request_basics(Logger::ERROR, request)
|
92
|
-
log_http_error(Logger::ERROR, error)
|
93
|
-
end
|
94
|
-
|
95
|
-
private
|
96
|
-
|
97
|
-
attr_reader :logger, :log_full_body
|
98
|
-
|
99
|
-
def log_request_basics(level, request)
|
100
|
-
logger.log(level) { "Request: #{request.method.upcase} #{build_logging_url(request)}" }
|
101
|
-
end
|
102
|
-
|
103
|
-
def build_logging_url(request)
|
104
|
-
query_params = QUERY_PARAM_CENSOR.censor_hash(request.params)
|
105
|
-
query_param_str =
|
106
|
-
if query_params.empty?
|
107
|
-
''
|
108
|
-
else
|
109
|
-
'?' + query_params.collect { |(k, v)| "#{k}=#{v}" }.join('&') # TODO: URI Encoding
|
110
|
-
end
|
111
|
-
request.url + query_param_str
|
112
|
-
end
|
113
|
-
|
114
|
-
def log_api_error(level, response)
|
115
|
-
log_status(level, response)
|
116
|
-
logger.log(level) do
|
117
|
-
"#{response.error_code}: #{response.message} - Ref ID: #{response.ref_id}"
|
118
|
-
end
|
119
|
-
log_headers('Response', response)
|
120
|
-
end
|
121
|
-
|
122
|
-
def log_http_error(level, response)
|
123
|
-
log_status(level, response)
|
124
|
-
log_headers('Response', response)
|
125
|
-
end
|
126
|
-
|
127
|
-
def log_status(level, response)
|
128
|
-
logger.log(level) { "Response: #{response.status_code} #{response.reason_phrase}" }
|
129
|
-
end
|
130
|
-
|
131
|
-
def log_headers(context, req_or_resp)
|
132
|
-
censored_hash = HEADER_CENSOR.censor_hash(req_or_resp.headers, case_insensitive: true)
|
133
|
-
logger.debug { "#{context} Headers: #{censored_hash}" }
|
134
|
-
end
|
135
|
-
|
136
|
-
def log_body(context, body)
|
137
|
-
return unless body
|
138
|
-
|
139
|
-
body_str =
|
140
|
-
if body.is_a? String
|
141
|
-
body
|
142
|
-
elsif body.is_a? Hash
|
143
|
-
PAYLOAD_CENSOR.censor_hash(body).to_s
|
144
|
-
else
|
145
|
-
'<Binary body>'
|
146
|
-
end
|
147
|
-
|
148
|
-
body_str = truncate_body(body_str) unless log_full_body
|
149
|
-
|
150
|
-
logger.debug "#{context} Body: #{body_str}"
|
151
|
-
end
|
152
|
-
|
153
|
-
def truncate_body(body_str)
|
154
|
-
if body_str.length > TRUNCATED_BODY_LENGTH
|
155
|
-
body_str[0...TRUNCATED_BODY_LENGTH] + '...'
|
156
|
-
else
|
157
|
-
body_str
|
158
|
-
end
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
# Stubs all request logging methods by doing nothing (see {RequestLogger})
|
163
|
-
class MuteRequestLogger
|
164
|
-
def log_request(request)
|
165
|
-
end
|
166
|
-
|
167
|
-
def log_retry_attempt(request, response, attempt_num)
|
168
|
-
end
|
169
|
-
|
170
|
-
def log_retry_failure(num_retries)
|
171
|
-
end
|
172
|
-
|
173
|
-
def log_successful_response(response)
|
174
|
-
end
|
175
|
-
|
176
|
-
def log_api_error_response(request, error)
|
177
|
-
end
|
178
|
-
|
179
|
-
def log_http_error_response(request, error)
|
180
|
-
end
|
181
|
-
end
|
182
|
-
end
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Smartsheet
|
4
|
+
module API
|
5
|
+
# Censors strings and hash values for select blacklisted keys
|
6
|
+
class Censor
|
7
|
+
EXPOSED_CHARS = 4
|
8
|
+
KEY_TO_STRING = ->(k){ k.to_s }
|
9
|
+
KEY_TO_DOWNCASE_STRING = ->(k){ k.to_s.downcase }
|
10
|
+
|
11
|
+
def initialize(*blacklist)
|
12
|
+
@blacklist = Set.new(blacklist)
|
13
|
+
end
|
14
|
+
|
15
|
+
def censor_hash(h, case_insensitive: false)
|
16
|
+
if case_insensitive
|
17
|
+
_censor_hash(h, KEY_TO_DOWNCASE_STRING, downcased_blacklist)
|
18
|
+
else
|
19
|
+
_censor_hash(h, KEY_TO_STRING, blacklist)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def censor(str)
|
24
|
+
total_length = str.length
|
25
|
+
censored_length = [total_length - EXPOSED_CHARS, 0].max
|
26
|
+
('*' * censored_length) + str[censored_length...total_length]
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def _censor_hash(h, key_transform, cased_blacklist)
|
32
|
+
h.collect do |(k, v)|
|
33
|
+
new_v =
|
34
|
+
cased_blacklist.include?(key_transform.call(k)) ?
|
35
|
+
censor(v) :
|
36
|
+
v
|
37
|
+
|
38
|
+
[k, new_v]
|
39
|
+
end.to_h
|
40
|
+
end
|
41
|
+
|
42
|
+
def downcased_blacklist
|
43
|
+
blacklist.collect { |x| x.downcase }
|
44
|
+
end
|
45
|
+
|
46
|
+
attr_reader :blacklist
|
47
|
+
end
|
48
|
+
|
49
|
+
# Logs request and response information, while censoring OAuth-relevant keys
|
50
|
+
class RequestLogger
|
51
|
+
QUERY_PARAM_CENSOR = Censor.new 'code', 'client_id', 'hash', 'refresh_token'
|
52
|
+
HEADER_CENSOR = Censor.new 'authorization'
|
53
|
+
PAYLOAD_CENSOR = Censor.new 'access_token', 'refresh_token'
|
54
|
+
|
55
|
+
TRUNCATED_BODY_LENGTH = 1024
|
56
|
+
|
57
|
+
def initialize(logger, log_full_body:)
|
58
|
+
@logger = logger
|
59
|
+
@log_full_body = log_full_body
|
60
|
+
end
|
61
|
+
|
62
|
+
def log_request(request)
|
63
|
+
log_request_basics(Logger::INFO, request)
|
64
|
+
log_headers('Request', request)
|
65
|
+
log_body('Request', request.body)
|
66
|
+
end
|
67
|
+
|
68
|
+
def log_retry_attempt(request, response, attempt_num)
|
69
|
+
logger.warn { "Request attempt #{attempt_num} failed" }
|
70
|
+
log_request_basics(Logger::WARN, request)
|
71
|
+
log_api_error(Logger::WARN, response)
|
72
|
+
end
|
73
|
+
|
74
|
+
def log_retry_failure(num_tries)
|
75
|
+
try_word = num_tries == 1 ? 'try' : 'tries'
|
76
|
+
logger.error { "Request failed after #{num_tries} #{try_word}" }
|
77
|
+
end
|
78
|
+
|
79
|
+
def log_successful_response(response)
|
80
|
+
log_status(Logger::INFO, response)
|
81
|
+
log_headers('Response', response)
|
82
|
+
log_body('Response', response.result)
|
83
|
+
end
|
84
|
+
|
85
|
+
def log_api_error_response(request, error)
|
86
|
+
log_request_basics(Logger::ERROR, request)
|
87
|
+
log_api_error(Logger::ERROR, error)
|
88
|
+
end
|
89
|
+
|
90
|
+
def log_http_error_response(request, error)
|
91
|
+
log_request_basics(Logger::ERROR, request)
|
92
|
+
log_http_error(Logger::ERROR, error)
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
attr_reader :logger, :log_full_body
|
98
|
+
|
99
|
+
def log_request_basics(level, request)
|
100
|
+
logger.log(level) { "Request: #{request.method.upcase} #{build_logging_url(request)}" }
|
101
|
+
end
|
102
|
+
|
103
|
+
def build_logging_url(request)
|
104
|
+
query_params = QUERY_PARAM_CENSOR.censor_hash(request.params)
|
105
|
+
query_param_str =
|
106
|
+
if query_params.empty?
|
107
|
+
''
|
108
|
+
else
|
109
|
+
'?' + query_params.collect { |(k, v)| "#{k}=#{v}" }.join('&') # TODO: URI Encoding
|
110
|
+
end
|
111
|
+
request.url + query_param_str
|
112
|
+
end
|
113
|
+
|
114
|
+
def log_api_error(level, response)
|
115
|
+
log_status(level, response)
|
116
|
+
logger.log(level) do
|
117
|
+
"#{response.error_code}: #{response.message} - Ref ID: #{response.ref_id}"
|
118
|
+
end
|
119
|
+
log_headers('Response', response)
|
120
|
+
end
|
121
|
+
|
122
|
+
def log_http_error(level, response)
|
123
|
+
log_status(level, response)
|
124
|
+
log_headers('Response', response)
|
125
|
+
end
|
126
|
+
|
127
|
+
def log_status(level, response)
|
128
|
+
logger.log(level) { "Response: #{response.status_code} #{response.reason_phrase}" }
|
129
|
+
end
|
130
|
+
|
131
|
+
def log_headers(context, req_or_resp)
|
132
|
+
censored_hash = HEADER_CENSOR.censor_hash(req_or_resp.headers, case_insensitive: true)
|
133
|
+
logger.debug { "#{context} Headers: #{censored_hash}" }
|
134
|
+
end
|
135
|
+
|
136
|
+
def log_body(context, body)
|
137
|
+
return unless body
|
138
|
+
|
139
|
+
body_str =
|
140
|
+
if body.is_a? String
|
141
|
+
body
|
142
|
+
elsif body.is_a? Hash
|
143
|
+
PAYLOAD_CENSOR.censor_hash(body).to_s
|
144
|
+
else
|
145
|
+
'<Binary body>'
|
146
|
+
end
|
147
|
+
|
148
|
+
body_str = truncate_body(body_str) unless log_full_body
|
149
|
+
|
150
|
+
logger.debug "#{context} Body: #{body_str}"
|
151
|
+
end
|
152
|
+
|
153
|
+
def truncate_body(body_str)
|
154
|
+
if body_str.length > TRUNCATED_BODY_LENGTH
|
155
|
+
body_str[0...TRUNCATED_BODY_LENGTH] + '...'
|
156
|
+
else
|
157
|
+
body_str
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# Stubs all request logging methods by doing nothing (see {RequestLogger})
|
163
|
+
class MuteRequestLogger
|
164
|
+
def log_request(request)
|
165
|
+
end
|
166
|
+
|
167
|
+
def log_retry_attempt(request, response, attempt_num)
|
168
|
+
end
|
169
|
+
|
170
|
+
def log_retry_failure(num_retries)
|
171
|
+
end
|
172
|
+
|
173
|
+
def log_successful_response(response)
|
174
|
+
end
|
175
|
+
|
176
|
+
def log_api_error_response(request, error)
|
177
|
+
end
|
178
|
+
|
179
|
+
def log_http_error_response(request, error)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
183
|
end
|