gapic-common 0.10.0 → 0.15.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 +4 -4
- data/CHANGELOG.md +50 -0
- data/lib/gapic/call_options/error_codes.rb +71 -0
- data/lib/gapic/call_options/retry_policy.rb +20 -28
- data/lib/gapic/call_options.rb +33 -0
- data/lib/gapic/common/version.rb +1 -1
- data/lib/gapic/generic_lro/operation.rb +2 -1
- data/lib/gapic/grpc/errors.rb +60 -0
- data/lib/gapic/grpc/service_stub/rpc_call.rb +17 -6
- data/lib/gapic/grpc/service_stub.rb +15 -15
- data/lib/gapic/grpc.rb +1 -0
- data/lib/gapic/headers.rb +5 -3
- data/lib/gapic/paged_enumerable.rb +3 -3
- data/lib/gapic/rest/client_stub.rb +85 -16
- data/lib/gapic/rest/error.rb +74 -11
- data/lib/gapic/rest/faraday_middleware.rb +25 -4
- data/lib/gapic/rest/grpc_transcoder/http_binding.rb +54 -1
- data/lib/gapic/rest/grpc_transcoder.rb +18 -44
- data/lib/gapic/rest/server_stream.rb +101 -0
- data/lib/gapic/rest/threaded_enumerator.rb +72 -0
- data/lib/gapic/rest.rb +2 -0
- metadata +6 -2
@@ -32,15 +32,17 @@ module Gapic
|
|
32
32
|
# @param credentials [Google::Auth::Credentials]
|
33
33
|
# Credentials to send with calls in form of a googleauth credentials object.
|
34
34
|
# (see the [googleauth docs](https://googleapis.dev/ruby/googleauth/latest/index.html))
|
35
|
+
# @param numeric_enums [Boolean] Whether to signal the server to JSON-encode enums as ints
|
35
36
|
#
|
36
37
|
# @yield [Faraday::Connection]
|
37
38
|
#
|
38
|
-
def initialize endpoint:, credentials:
|
39
|
+
def initialize endpoint:, credentials:, numeric_enums: false
|
39
40
|
@endpoint = endpoint
|
40
41
|
@endpoint = "https://#{endpoint}" unless /^https?:/.match? endpoint
|
41
42
|
@endpoint = @endpoint.sub %r{/$}, ""
|
42
43
|
|
43
44
|
@credentials = credentials
|
45
|
+
@numeric_enums = numeric_enums
|
44
46
|
|
45
47
|
@connection = Faraday.new url: @endpoint do |conn|
|
46
48
|
conn.headers = { "Content-Type" => "application/json" }
|
@@ -58,8 +60,8 @@ module Gapic
|
|
58
60
|
#
|
59
61
|
# @param uri [String] uri to send this request to
|
60
62
|
# @param params [Hash] query string parameters for the request
|
61
|
-
# @param options [::Gapic::CallOptions] gapic options to be applied
|
62
|
-
#
|
63
|
+
# @param options [::Gapic::CallOptions,Hash] gapic options to be applied
|
64
|
+
# to the REST call. Currently only timeout and headers are supported.
|
63
65
|
# @return [Faraday::Response]
|
64
66
|
def make_get_request uri:, params: {}, options: {}
|
65
67
|
make_http_request :get, uri: uri, body: nil, params: params, options: options
|
@@ -70,8 +72,8 @@ module Gapic
|
|
70
72
|
#
|
71
73
|
# @param uri [String] uri to send this request to
|
72
74
|
# @param params [Hash] query string parameters for the request
|
73
|
-
# @param options [::Gapic::CallOptions] gapic options to be applied
|
74
|
-
#
|
75
|
+
# @param options [::Gapic::CallOptions,Hash] gapic options to be applied
|
76
|
+
# to the REST call. Currently only timeout and headers are supported.
|
75
77
|
# @return [Faraday::Response]
|
76
78
|
def make_delete_request uri:, params: {}, options: {}
|
77
79
|
make_http_request :delete, uri: uri, body: nil, params: params, options: options
|
@@ -83,8 +85,8 @@ module Gapic
|
|
83
85
|
# @param uri [String] uri to send this request to
|
84
86
|
# @param body [String] a body to send with the request, nil for requests without a body
|
85
87
|
# @param params [Hash] query string parameters for the request
|
86
|
-
# @param options [::Gapic::CallOptions] gapic options to be applied
|
87
|
-
#
|
88
|
+
# @param options [::Gapic::CallOptions,Hash] gapic options to be applied
|
89
|
+
# to the REST call. Currently only timeout and headers are supported.
|
88
90
|
# @return [Faraday::Response]
|
89
91
|
def make_patch_request uri:, body:, params: {}, options: {}
|
90
92
|
make_http_request :patch, uri: uri, body: body, params: params, options: options
|
@@ -96,8 +98,8 @@ module Gapic
|
|
96
98
|
# @param uri [String] uri to send this request to
|
97
99
|
# @param body [String] a body to send with the request, nil for requests without a body
|
98
100
|
# @param params [Hash] query string parameters for the request
|
99
|
-
# @param options [::Gapic::CallOptions] gapic options to be applied
|
100
|
-
#
|
101
|
+
# @param options [::Gapic::CallOptions,Hash] gapic options to be applied
|
102
|
+
# to the REST call. Currently only timeout and headers are supported.
|
101
103
|
# @return [Faraday::Response]
|
102
104
|
def make_post_request uri:, body: nil, params: {}, options: {}
|
103
105
|
make_http_request :post, uri: uri, body: body, params: params, options: options
|
@@ -109,8 +111,8 @@ module Gapic
|
|
109
111
|
# @param uri [String] uri to send this request to
|
110
112
|
# @param body [String] a body to send with the request, nil for requests without a body
|
111
113
|
# @param params [Hash] query string parameters for the request
|
112
|
-
# @param options [::Gapic::CallOptions] gapic options to be applied
|
113
|
-
#
|
114
|
+
# @param options [::Gapic::CallOptions,Hash] gapic options to be applied
|
115
|
+
# to the REST call. Currently only timeout and headers are supported.
|
114
116
|
# @return [Faraday::Response]
|
115
117
|
def make_put_request uri:, body: nil, params: {}, options: {}
|
116
118
|
make_http_request :put, uri: uri, body: body, params: params, options: options
|
@@ -123,17 +125,84 @@ module Gapic
|
|
123
125
|
# @param uri [String] uri to send this request to
|
124
126
|
# @param body [String, nil] a body to send with the request, nil for requests without a body
|
125
127
|
# @param params [Hash] query string parameters for the request
|
126
|
-
# @param options [::Gapic::CallOptions] gapic options to be applied to the REST call.
|
127
|
-
#
|
128
|
+
# @param options [::Gapic::CallOptions,Hash] gapic options to be applied to the REST call.
|
129
|
+
# @param is_server_streaming [Boolean] flag if method is streaming
|
130
|
+
# @yieldparam chunk [String] The chunk of data received during server streaming.
|
128
131
|
# @return [Faraday::Response]
|
129
|
-
def make_http_request verb, uri:, body:, params:, options:
|
132
|
+
def make_http_request verb, uri:, body:, params:, options:, is_server_streaming: false, &block
|
133
|
+
# Converts hash and nil to an options object
|
134
|
+
options = ::Gapic::CallOptions.new(**options.to_h) unless options.is_a? ::Gapic::CallOptions
|
135
|
+
deadline = calculate_deadline options
|
136
|
+
retried_exception = nil
|
137
|
+
next_timeout = get_timeout deadline
|
138
|
+
|
139
|
+
begin
|
140
|
+
base_make_http_request(verb,
|
141
|
+
uri: uri,
|
142
|
+
body: body,
|
143
|
+
params: params,
|
144
|
+
metadata: options.metadata,
|
145
|
+
timeout: next_timeout,
|
146
|
+
is_server_streaming: is_server_streaming,
|
147
|
+
&block)
|
148
|
+
rescue ::Faraday::Error => e
|
149
|
+
next_timeout = get_timeout deadline
|
150
|
+
|
151
|
+
if next_timeout&.positive? && options.retry_policy.call(e)
|
152
|
+
retried_exception = e
|
153
|
+
retry
|
154
|
+
end
|
155
|
+
|
156
|
+
unless next_timeout&.positive?
|
157
|
+
raise Gapic::GRPC::DeadlineExceededError.new e.message, root_cause: retried_exception
|
158
|
+
end
|
159
|
+
|
160
|
+
raise e
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
private
|
165
|
+
|
166
|
+
##
|
167
|
+
# @private
|
168
|
+
# Sends a http request via Faraday
|
169
|
+
# @param verb [Symbol] http verb
|
170
|
+
# @param uri [String] uri to send this request to
|
171
|
+
# @param body [String, nil] a body to send with the request, nil for requests without a body
|
172
|
+
# @param params [Hash] query string parameters for the request
|
173
|
+
# @param metadata [Hash] additional headers for the request
|
174
|
+
# @param is_server_streaming [Boolean] flag if method is streaming
|
175
|
+
# @yieldparam chunk [String] The chunk of data received during server streaming.
|
176
|
+
# @return [Faraday::Response]
|
177
|
+
def base_make_http_request verb, uri:, body:, params:, metadata:, timeout:, is_server_streaming: false
|
178
|
+
if @numeric_enums && (!params.key?("$alt") || params["$alt"] == "json")
|
179
|
+
params = params.merge({ "$alt" => "json;enum-encoding=int" })
|
180
|
+
end
|
181
|
+
|
130
182
|
@connection.send verb, uri do |req|
|
131
183
|
req.params = params if params.any?
|
132
184
|
req.body = body unless body.nil?
|
133
|
-
req.headers = req.headers.merge
|
134
|
-
req.options.timeout =
|
185
|
+
req.headers = req.headers.merge metadata
|
186
|
+
req.options.timeout = timeout if timeout&.positive?
|
187
|
+
if is_server_streaming
|
188
|
+
req.options.on_data = proc do |chunk, _overall_received_bytes|
|
189
|
+
yield chunk
|
190
|
+
end
|
191
|
+
end
|
135
192
|
end
|
136
193
|
end
|
194
|
+
|
195
|
+
def calculate_deadline options
|
196
|
+
return if options.timeout.nil?
|
197
|
+
return if options.timeout.negative?
|
198
|
+
|
199
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC) + options.timeout
|
200
|
+
end
|
201
|
+
|
202
|
+
def get_timeout deadline
|
203
|
+
return if deadline.nil?
|
204
|
+
deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
205
|
+
end
|
137
206
|
end
|
138
207
|
end
|
139
208
|
end
|
data/lib/gapic/rest/error.rb
CHANGED
@@ -19,16 +19,30 @@ module Gapic
|
|
19
19
|
module Rest
|
20
20
|
# Gapic REST exception class
|
21
21
|
class Error < ::Gapic::Common::Error
|
22
|
-
# @return [Integer] the http status code for the error
|
22
|
+
# @return [Integer, nil] the http status code for the error
|
23
23
|
attr_reader :status_code
|
24
|
+
# @return [Object, nil] the text representation of status as parsed from the response body
|
25
|
+
attr_reader :status
|
26
|
+
# @return [Object, nil] the details as parsed from the response body
|
27
|
+
attr_reader :details
|
28
|
+
# @return [Object, nil] the headers of the REST error
|
29
|
+
attr_reader :headers
|
30
|
+
# The Cloud error wrapper expect to see a `header` property
|
31
|
+
alias header headers
|
24
32
|
|
25
33
|
##
|
26
34
|
# @param message [String, nil] error message
|
27
35
|
# @param status_code [Integer, nil] HTTP status code of this error
|
36
|
+
# @param status [String, nil] The text representation of status as parsed from the response body
|
37
|
+
# @param details [Object, nil] Details data of this error
|
38
|
+
# @param headers [Object, nil] Http headers data of this error
|
28
39
|
#
|
29
|
-
def initialize message, status_code
|
30
|
-
@status_code = status_code
|
40
|
+
def initialize message, status_code, status: nil, details: nil, headers: nil
|
31
41
|
super message
|
42
|
+
@status_code = status_code
|
43
|
+
@status = status
|
44
|
+
@details = details
|
45
|
+
@headers = headers
|
32
46
|
end
|
33
47
|
|
34
48
|
class << self
|
@@ -37,36 +51,85 @@ module Gapic
|
|
37
51
|
# it tries to parse and set a detailed message and an error code from
|
38
52
|
# from the Google Cloud's response body
|
39
53
|
#
|
54
|
+
# @param err [Faraday::Error] the Faraday error to wrap
|
55
|
+
#
|
56
|
+
# @return [ Gapic::Rest::Error]
|
40
57
|
def wrap_faraday_error err
|
41
58
|
message = err.message
|
42
59
|
status_code = err.response_status
|
60
|
+
status = nil
|
61
|
+
details = nil
|
62
|
+
headers = err.response_headers
|
43
63
|
|
44
64
|
if err.response_body
|
45
|
-
msg, code = try_parse_from_body err.response_body
|
65
|
+
msg, code, status, details = try_parse_from_body err.response_body
|
46
66
|
message = "An error has occurred when making a REST request: #{msg}" unless msg.nil?
|
47
67
|
status_code = code unless code.nil?
|
48
68
|
end
|
49
69
|
|
50
|
-
Gapic::Rest::Error.new message, status_code
|
70
|
+
Gapic::Rest::Error.new message, status_code, status: status, details: details, headers: headers
|
51
71
|
end
|
52
72
|
|
53
73
|
private
|
54
74
|
|
55
75
|
##
|
76
|
+
# @private
|
56
77
|
# Tries to get the error information from the JSON bodies
|
57
78
|
#
|
58
79
|
# @param body_str [String]
|
59
|
-
# @return [Array(String, String)]
|
80
|
+
# @return [Array(String, String, String, String)]
|
60
81
|
def try_parse_from_body body_str
|
61
82
|
body = JSON.parse body_str
|
62
|
-
return [nil, nil] unless body && body["error"].is_a?(Hash)
|
63
83
|
|
64
|
-
|
65
|
-
|
84
|
+
unless body.is_a?(::Hash) && body&.key?("error") && body["error"].is_a?(::Hash)
|
85
|
+
return [nil, nil, nil, nil]
|
86
|
+
end
|
87
|
+
error = body["error"]
|
88
|
+
|
89
|
+
message = error["message"] if error.key? "message"
|
90
|
+
code = error["code"] if error.key? "code"
|
91
|
+
status = error["status"] if error.key? "status"
|
92
|
+
|
93
|
+
details = parse_details error["details"] if error.key? "details"
|
66
94
|
|
67
|
-
[message, code]
|
95
|
+
[message, code, status, details]
|
68
96
|
rescue JSON::ParserError
|
69
|
-
[nil, nil]
|
97
|
+
[nil, nil, nil, nil]
|
98
|
+
end
|
99
|
+
|
100
|
+
##
|
101
|
+
# @private
|
102
|
+
# Parses the details data, trying to extract the Protobuf.Any objects
|
103
|
+
# from it, if it's an array of hashes. Otherwise returns it as is.
|
104
|
+
#
|
105
|
+
# @param details [Object, nil] the details object
|
106
|
+
#
|
107
|
+
# @return [Object, nil]
|
108
|
+
def parse_details details
|
109
|
+
# For rest errors details will contain json representations of `Protobuf.Any`
|
110
|
+
# decoded into hashes. If it's not an array, of its elements are not hashes,
|
111
|
+
# it's some other case
|
112
|
+
return details unless details.is_a? ::Array
|
113
|
+
|
114
|
+
details.map do |detail_instance|
|
115
|
+
next detail_instance unless detail_instance.is_a? ::Hash
|
116
|
+
# Next, parse detail_instance into a Proto message.
|
117
|
+
# There are three possible issues for the JSON->Any->message parsing
|
118
|
+
# - json decoding fails
|
119
|
+
# - the json belongs to a proto message type we don't know about
|
120
|
+
# - any unpacking fails
|
121
|
+
# If we hit any of these three issues we'll just return the original hash
|
122
|
+
begin
|
123
|
+
any = ::Google::Protobuf::Any.decode_json detail_instance.to_json
|
124
|
+
klass = ::Google::Protobuf::DescriptorPool.generated_pool.lookup(any.type_name)&.msgclass
|
125
|
+
next detail_instance if klass.nil?
|
126
|
+
unpack = any.unpack klass
|
127
|
+
next detail_instance if unpack.nil?
|
128
|
+
unpack
|
129
|
+
rescue ::Google::Protobuf::ParseError
|
130
|
+
detail_instance
|
131
|
+
end
|
132
|
+
end.compact
|
70
133
|
end
|
71
134
|
end
|
72
135
|
end
|
@@ -18,22 +18,43 @@ module Gapic
|
|
18
18
|
# Registers the middleware with Faraday
|
19
19
|
module FaradayMiddleware
|
20
20
|
##
|
21
|
+
# @private
|
21
22
|
# Request middleware that constructs the Authorization HTTP header
|
22
23
|
# using ::Google::Auth::Credentials
|
23
24
|
#
|
24
25
|
class GoogleAuthorization < Faraday::Middleware
|
25
26
|
##
|
27
|
+
# @private
|
26
28
|
# @param app [#call]
|
27
|
-
# @param credentials [
|
29
|
+
# @param credentials [Google::Auth::Credentials, Signet::OAuth2::Client, Symbol, Proc]
|
30
|
+
# Provides the means for authenticating requests made by
|
31
|
+
# the client. This parameter can be many types:
|
32
|
+
# * A `Google::Auth::Credentials` uses a the properties of its represented keyfile for authenticating requests
|
33
|
+
# made by this client.
|
34
|
+
# * A `Signet::OAuth2::Client` object used to apply the OAuth credentials.
|
35
|
+
# * A `Proc` will be used as an updater_proc for the auth token.
|
36
|
+
# * A `Symbol` is treated as a signal that authentication is not required.
|
37
|
+
#
|
28
38
|
def initialize app, credentials
|
29
|
-
@
|
39
|
+
@updater_proc = case credentials
|
40
|
+
when Symbol
|
41
|
+
credentials
|
42
|
+
else
|
43
|
+
updater_proc = credentials.updater_proc if credentials.respond_to? :updater_proc
|
44
|
+
updater_proc ||= credentials if credentials.is_a? Proc
|
45
|
+
raise ArgumentError, "invalid credentials (#{credentials.class})" if updater_proc.nil?
|
46
|
+
updater_proc
|
47
|
+
end
|
30
48
|
super app
|
31
49
|
end
|
32
50
|
|
51
|
+
# @private
|
33
52
|
# @param env [Faraday::Env]
|
34
53
|
def call env
|
35
|
-
|
36
|
-
|
54
|
+
unless @updater_proc.is_a? Symbol
|
55
|
+
auth_hash = @updater_proc.call({})
|
56
|
+
env.request_headers["Authorization"] = auth_hash[:authorization]
|
57
|
+
end
|
37
58
|
|
38
59
|
@app.call env
|
39
60
|
end
|
@@ -15,6 +15,7 @@
|
|
15
15
|
module Gapic
|
16
16
|
module Rest
|
17
17
|
class GrpcTranscoder
|
18
|
+
##
|
18
19
|
# @private
|
19
20
|
# A single binding for GRPC-REST transcoding of a request
|
20
21
|
# It includes a uri template with bound field parameters, a HTTP method type,
|
@@ -25,7 +26,8 @@ module Gapic
|
|
25
26
|
# @attribute [r] template
|
26
27
|
# @return [String] The URI template for the request.
|
27
28
|
# @attribute [r] field_bindings
|
28
|
-
# @return [Array<FieldBinding>]
|
29
|
+
# @return [Array<Gapic::Rest::GrpcTranscoder::HttpBinding::FieldBinding>]
|
30
|
+
# The field bindings for the URI template variables.
|
29
31
|
# @attribute [r] body
|
30
32
|
# @return [String] The body template for the request.
|
31
33
|
class HttpBinding
|
@@ -41,6 +43,57 @@ module Gapic
|
|
41
43
|
@body = body
|
42
44
|
end
|
43
45
|
|
46
|
+
##
|
47
|
+
# @private
|
48
|
+
# Creates a new HttpBinding.
|
49
|
+
#
|
50
|
+
# @param uri_method [Symbol] The rest verb for the binding.
|
51
|
+
# @param uri_template [String] The string with uri template for the binding.
|
52
|
+
# This string will be expanded with the parameters from variable bindings.
|
53
|
+
# @param matches [Array<Array>] Variable bindings in an array. Every element
|
54
|
+
# of the array is an [Array] triplet, where:
|
55
|
+
# - the first element is a [String] field path (e.g. `foo.bar`) in the request
|
56
|
+
# to bind to
|
57
|
+
# - the second element is a [Regexp] to match the field value
|
58
|
+
# - the third element is a [Boolean] whether the slashes in the field value
|
59
|
+
# should be preserved (as opposed to escaped) when expanding the uri template.
|
60
|
+
# @param body [String, Nil] The body template, e.g. `*` or a field path.
|
61
|
+
#
|
62
|
+
# @return [Gapic::Rest::GrpcTranscoder::HttpBinding] The new binding.
|
63
|
+
def self.create_with_validation uri_method:, uri_template:, matches: [], body: nil
|
64
|
+
template = uri_template
|
65
|
+
|
66
|
+
matches.each do |name, _regex, _preserve_slashes|
|
67
|
+
unless uri_template =~ /({#{Regexp.quote name}})/
|
68
|
+
err_msg = "Binding configuration is incorrect: missing parameter in the URI template.\n" \
|
69
|
+
"Parameter `#{name}` is specified for matching but there is no corresponding parameter " \
|
70
|
+
"`{#{name}}` in the URI template."
|
71
|
+
raise ::Gapic::Common::Error, err_msg
|
72
|
+
end
|
73
|
+
|
74
|
+
template = template.gsub "{#{name}}", ""
|
75
|
+
end
|
76
|
+
|
77
|
+
if template =~ /{([a-zA-Z_.]+)}/
|
78
|
+
err_name = Regexp.last_match[1]
|
79
|
+
err_msg = "Binding configuration is incorrect: missing match configuration.\n" \
|
80
|
+
"Parameter `{#{err_name}}` is specified in the URI template but there is no " \
|
81
|
+
"corresponding match configuration for `#{err_name}`."
|
82
|
+
raise ::Gapic::Common::Error, err_msg
|
83
|
+
end
|
84
|
+
|
85
|
+
if body&.include? "."
|
86
|
+
raise ::Gapic::Common::Error,
|
87
|
+
"Provided body template `#{body}` points to a field in a sub-message. This is not supported."
|
88
|
+
end
|
89
|
+
|
90
|
+
field_bindings = matches.map do |name, regex, preserve_slashes|
|
91
|
+
HttpBinding::FieldBinding.new name, regex, preserve_slashes
|
92
|
+
end
|
93
|
+
|
94
|
+
HttpBinding.new uri_method, uri_template, field_bindings, body
|
95
|
+
end
|
96
|
+
|
44
97
|
# A single binding for a field of a request message.
|
45
98
|
# @attribute [r] field_path
|
46
99
|
# @return [String] The path of the bound field, e.g. `foo.bar`.
|
@@ -44,36 +44,11 @@ module Gapic
|
|
44
44
|
#
|
45
45
|
# @return [Gapic::Rest::GrpcTranscoder] The updated transcoder.
|
46
46
|
def with_bindings uri_method:, uri_template:, matches: [], body: nil
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
"Parameter `#{name}` is specified for matching but there is no corresponding parameter" \
|
53
|
-
" `{#{name}}` in the URI template."
|
54
|
-
raise ::Gapic::Common::Error, err_msg
|
55
|
-
end
|
56
|
-
|
57
|
-
template = template.gsub "{#{name}}", ""
|
58
|
-
end
|
59
|
-
|
60
|
-
if template =~ /{([a-zA-Z_.]+)}/
|
61
|
-
err_name = Regexp.last_match[1]
|
62
|
-
err_msg = "Binding configuration is incorrect: missing match configuration.\n" \
|
63
|
-
"Parameter `{#{err_name}}` is specified in the URI template but there is no" \
|
64
|
-
" corresponding match configuration for `#{err_name}`."
|
65
|
-
raise ::Gapic::Common::Error, err_msg
|
66
|
-
end
|
67
|
-
|
68
|
-
if body&.include? "."
|
69
|
-
raise ::Gapic::Common::Error,
|
70
|
-
"Provided body template `#{body}` points to a field in a sub-message. This is not supported."
|
71
|
-
end
|
72
|
-
|
73
|
-
field_bindings = matches.map do |name, regex, preserve_slashes|
|
74
|
-
HttpBinding::FieldBinding.new name, regex, preserve_slashes
|
75
|
-
end
|
76
|
-
GrpcTranscoder.new @bindings + [HttpBinding.new(uri_method, uri_template, field_bindings, body)]
|
47
|
+
binding = HttpBinding.create_with_validation(uri_method: uri_method,
|
48
|
+
uri_template: uri_template,
|
49
|
+
matches: matches,
|
50
|
+
body: body)
|
51
|
+
GrpcTranscoder.new @bindings + [binding]
|
77
52
|
end
|
78
53
|
|
79
54
|
##
|
@@ -108,12 +83,9 @@ module Gapic
|
|
108
83
|
uri_values = bind_uri_values! http_binding, request_hash
|
109
84
|
next if uri_values.any? { |_, value| value.nil? }
|
110
85
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
body_binding_camel = camel_name_for http_binding.body
|
115
|
-
next unless request_hash.key? body_binding_camel
|
116
|
-
end
|
86
|
+
# Note that the body template can only point to a top-level field,
|
87
|
+
# so there is no need to split the path.
|
88
|
+
next if http_binding.body && http_binding.body != "*" && !(request.respond_to? http_binding.body.to_sym)
|
117
89
|
|
118
90
|
method = http_binding.method
|
119
91
|
uri = expand_template http_binding.template, uri_values
|
@@ -145,11 +117,7 @@ module Gapic
|
|
145
117
|
field_value = extract_scalar_value! request_hash, field_path_camel, field_binding.regex
|
146
118
|
|
147
119
|
if field_value
|
148
|
-
field_value =
|
149
|
-
field_value.split("/").map { |segment| percent_escape(segment) }.join("/")
|
150
|
-
else
|
151
|
-
percent_escape field_value
|
152
|
-
end
|
120
|
+
field_value = field_value.split("/").map { |segment| percent_escape(segment) }.join("/")
|
153
121
|
end
|
154
122
|
|
155
123
|
[field_binding.field_path, field_value]
|
@@ -186,9 +154,15 @@ module Gapic
|
|
186
154
|
#
|
187
155
|
# The `request_hash_without_uri` at this point was mutated to delete these fields.
|
188
156
|
#
|
189
|
-
# Note
|
190
|
-
|
191
|
-
|
157
|
+
# Note 1: body template can only point to a top-level field.
|
158
|
+
# Note 2: The field that body template points to can be null, in which case
|
159
|
+
# an empty string should be sent. E.g. `Compute.Projects.SetUsageExportBucket`.
|
160
|
+
request_body_field = request.send body_template.to_sym if request.respond_to? body_template.to_sym
|
161
|
+
if request_body_field
|
162
|
+
request_hash_without_uri.delete camel_name_for body_template
|
163
|
+
body = request_body_field.to_json emit_defaults: true
|
164
|
+
end
|
165
|
+
|
192
166
|
query_params = build_query_params request_hash_without_uri
|
193
167
|
else
|
194
168
|
query_params = build_query_params request_hash_without_uri
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# Copyright 2022 Google LLC
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require "json"
|
16
|
+
|
17
|
+
module Gapic
|
18
|
+
module Rest
|
19
|
+
##
|
20
|
+
# A class to provide the Enumerable interface to the response of a REST server-streaming dmethod.
|
21
|
+
#
|
22
|
+
# ServerStream provides the enumerations over the individual response messages within the stream.
|
23
|
+
#
|
24
|
+
# @example normal iteration over resources.
|
25
|
+
# server_stream.each { |response| puts response }
|
26
|
+
#
|
27
|
+
class ServerStream
|
28
|
+
include Enumerable
|
29
|
+
|
30
|
+
##
|
31
|
+
# Initializes ServerStream object.
|
32
|
+
#
|
33
|
+
# @param message_klass [Class]
|
34
|
+
# @param json_enumerator [Enumerator<String>]
|
35
|
+
def initialize message_klass, json_enumerator
|
36
|
+
@json_enumerator = json_enumerator
|
37
|
+
@obj = ""
|
38
|
+
@message_klass = message_klass
|
39
|
+
@ready_objs = [] # List of strings
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# Iterate over JSON objects in the streamed response.
|
44
|
+
#
|
45
|
+
# @yield [Object] Gives one complete Message object.
|
46
|
+
#
|
47
|
+
# @return [Enumerator] if no block is provided
|
48
|
+
#
|
49
|
+
def each
|
50
|
+
return enum_for :each unless block_given?
|
51
|
+
|
52
|
+
loop do
|
53
|
+
while @ready_objs.length.zero?
|
54
|
+
begin
|
55
|
+
chunk = @json_enumerator.next
|
56
|
+
next unless chunk
|
57
|
+
next_json! chunk
|
58
|
+
rescue StopIteration
|
59
|
+
dangling_content = @obj.strip
|
60
|
+
error_expl = "Dangling content left after iterating through the stream. " \
|
61
|
+
"This means that not all content was received or parsed correctly. " \
|
62
|
+
"It is likely a result of server or network error."
|
63
|
+
error_text = "#{error_expl}\n Content left unparsed: #{dangling_content}"
|
64
|
+
|
65
|
+
raise Gapic::Common::Error, error_text unless dangling_content.empty?
|
66
|
+
return
|
67
|
+
end
|
68
|
+
end
|
69
|
+
yield @message_klass.decode_json @ready_objs.shift, ignore_unknown_fields: true
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
##
|
76
|
+
# Builds the next JSON object of the server stream from chunk.
|
77
|
+
#
|
78
|
+
# @param chunk [String] Contains (partial) JSON object
|
79
|
+
#
|
80
|
+
def next_json! chunk
|
81
|
+
chunk.chars.each do |char|
|
82
|
+
# Invariant: @obj is always either a part of a single JSON object or the entire JSON object.
|
83
|
+
# Hence, it's safe to strip whitespace, commans and array brackets. These characters
|
84
|
+
# are only added before @obj is a complete JSON object and essentially can be flushed.
|
85
|
+
next if @obj.empty? && char != "{"
|
86
|
+
@obj += char
|
87
|
+
next unless char == "}"
|
88
|
+
begin
|
89
|
+
# Two choices here: append a Ruby object into
|
90
|
+
# ready_objs or a string. Going with the latter here.
|
91
|
+
JSON.parse @obj
|
92
|
+
@ready_objs.append @obj
|
93
|
+
@obj = ""
|
94
|
+
rescue JSON::ParserError
|
95
|
+
next
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# Copyright 2022 Google LLC
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
|
16
|
+
module Gapic
|
17
|
+
module Rest
|
18
|
+
##
|
19
|
+
# @private
|
20
|
+
# A class to provide the Enumerable interface to an incoming stream of data.
|
21
|
+
#
|
22
|
+
# ThreadedEnumerator provides the enumerations over the individual chunks of data received from the server.
|
23
|
+
#
|
24
|
+
# @example normal iteration over resources.
|
25
|
+
# chunk = threaded_enumerator.next
|
26
|
+
#
|
27
|
+
# @attribute [r] in_q
|
28
|
+
# @return [Queue] Input queue.
|
29
|
+
# @attribute [r] out_q
|
30
|
+
# @return [Queue] Output queue.
|
31
|
+
class ThreadedEnumerator
|
32
|
+
attr_reader :in_q
|
33
|
+
attr_reader :out_q
|
34
|
+
|
35
|
+
# Spawns a new thread and does appropriate clean-up
|
36
|
+
# in case thread fails. Propagates exception back
|
37
|
+
# to main thread.
|
38
|
+
#
|
39
|
+
# @yieldparam in_q[Queue] input queue
|
40
|
+
# @yieldparam out_q[Queue] output queue
|
41
|
+
def initialize
|
42
|
+
@in_q = Queue.new
|
43
|
+
@out_q = Queue.new
|
44
|
+
|
45
|
+
Thread.new do
|
46
|
+
yield @in_q, @out_q
|
47
|
+
@out_q.enq nil
|
48
|
+
rescue StandardError => e
|
49
|
+
@out_q.push e
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def next
|
54
|
+
@in_q.enq :next
|
55
|
+
chunk = @out_q.deq
|
56
|
+
|
57
|
+
if chunk.is_a? StandardError
|
58
|
+
@out_q.close
|
59
|
+
@in_q.close
|
60
|
+
raise chunk
|
61
|
+
end
|
62
|
+
|
63
|
+
if chunk.nil?
|
64
|
+
@out_q.close
|
65
|
+
@in_q.close
|
66
|
+
raise StopIteration
|
67
|
+
end
|
68
|
+
chunk
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|