gapic-common 0.10.0 → 0.15.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|