class Gapic::Rest::GrpcTranscoder

Internal doc go/actools-regapic-grpc-transcoding.
using a configuration of bindings.
Transcodes a proto request message into HTTP Rest call components
@private

def bind_uri_values! http_binding, request_hash

Returns:
  • (Hash{String, String}) -

Parameters:
  • request_hash (Hash) --
  • http_binding (Gapic::Rest::GrpcTranscoder::HttpBinding) --
def bind_uri_values! http_binding, request_hash
  http_binding.field_bindings.to_h do |field_binding|
    field_path_camel = field_binding.field_path.split(".").map { |part| camel_name_for part }.join(".")
    field_value = extract_scalar_value! request_hash, field_path_camel, field_binding.regex
    if field_value
      field_value = field_value.split("/").map { |segment| percent_escape segment }.join("/")
    end
    [field_binding.field_path, field_value]
  end
end

def build_query_params request_hash, prefix = ""

Returns:
  • (Array{String, String}) - Query string params as key-value pairs.

Parameters:
  • prefix (String) -- A prefix to form the correct query parameter key.
  • request_hash (Hash) --
def build_query_params request_hash, prefix = ""
  result = []
  request_hash.each do |key, value|
    full_key_name = "#{prefix}#{key}"
    case value
    when ::Array
      value.each do |_val|
        result.push "#{full_key_name}=#{value}"
      end
    when ::Hash
      result += build_query_params value, "#{full_key_name}."
    else
      result.push "#{full_key_name}=#{value}" unless value.nil?
    end
  end
  result
end

def camel_name_for attr_name

Returns:
  • (String) - Camel-cased parameter name.

Parameters:
  • attr_name (String) -- Parameter name.
def camel_name_for attr_name
  parts = attr_name.split "_"
  first_part = parts[0]
  other_parts = parts[1..]
  other_parts_pascal = other_parts.map(&:capitalize).join
  "#{first_part}#{other_parts_pascal}"
end

def construct_body_query_params body_template, request_hash_without_uri, request

Returns:
  • (Array{String, Array}) - A pair of body and query parameters.

Parameters:
  • request (Object) -- The GRPC request.
  • request_hash_without_uri (Hash) --
  • body_template (String, Nil) -- The template for the body, e.g. `*`.
def construct_body_query_params body_template, request_hash_without_uri, request
  body = ""
  query_params = []
  if body_template == "*"
    body = request_hash_without_uri.to_json
  elsif body_template && body_template != ""
    # Using a `request` here instead of `request_hash_without_uri`
    # because if `body` is bound to a message field,
    # the fields of the corresponding sub-message,
    # which were used when constructing the URI, should not be deleted
    # (as opposed to the case when `body` is `*`).
    #
    # The `request_hash_without_uri` at this point was mutated to delete these fields.
    #
    # Note 1: body template can only point to a top-level field.
    # Note 2: The field that body template points to can be null, in which case
    # an empty string should be sent. E.g. `Compute.Projects.SetUsageExportBucket`.
    request_body_field = request.send body_template.to_sym if request.respond_to? body_template.to_sym
    if request_body_field
      request_hash_without_uri.delete camel_name_for body_template
      body = request_body_field.to_json emit_defaults: true
    end
    query_params = build_query_params request_hash_without_uri
  else
    query_params = build_query_params request_hash_without_uri
  end
  [body, query_params]
end

def expand_template template, bindings

Returns:
  • (String) - The expanded template.

Parameters:
  • bindings (Hash{String, String}) --
  • template (String) -- The Uri template.
def expand_template template, bindings
  result = template
  bindings.each do |name, value|
    result = result.gsub "{#{name}}", value
  end
  result
end

def extract_scalar_value! request_hash, field_path, regex

Returns:
  • (String, Nil) - the field's string representation or nil.

Parameters:
  • regex (Regexp) -- A regex to match on the field's string representation.
  • field_path (String) -- A path to the field, e.g. `foo.bar`.
  • request_hash (Hash) --
def extract_scalar_value! request_hash, field_path, regex
  parent, name = find_value request_hash, field_path
  value = parent.delete name
  # Covers the case where in `foo.bar.baz`, `baz` is still a submessage or an array.
  return nil if value.is_a?(::Hash) || value.is_a?(::Array)
  value.to_s if value.to_s =~ regex
end

def find_value request_hash, field_path

Parameters:
  • field_path (String) -- A path of the field, e.g. `foo.bar`.
  • request_hash (Hash) -- A hash of the GRPC request or the sub-request.
def find_value request_hash, field_path
  path_split = field_path.split "."
  value_parent = nil
  value = request_hash
  last_field_name = nil
  path_split.each do |curr_field|
    # Covers the case when in `foo.bar.baz`, `bar` is not a submessage field
    # or is a submessage field initialized with nil.
    return {}, nil unless value.is_a? ::Hash
    value_parent = value
    last_field_name = curr_field
    value = value[curr_field]
  end
  [value_parent, last_field_name]
end

def initialize bindings = nil

def initialize bindings = nil
  @bindings = bindings || []
end

def percent_escape str

Returns:
  • (String) - Escaped string.

Parameters:
  • str (String) -- String to escape.
def percent_escape str
  # `+` to represent spaces is not currently supported in Showcase server.
  CGI.escape(str).gsub("+", "%20")
end

def transcode request

Returns:
  • (Array) - The components of the transcoded request.

Parameters:
  • request (Object) -- The GRPC request object

Other tags:
    Private: -
def transcode request
  # Using bindings in reverse here because of the "last one wins" rule
  @bindings.reverse.each do |http_binding|
    # The main reason we are using request.to_json here
    # is that the unset proto3_optional fields will not be
    # in that JSON, letting us skip the checks that would look like
    #   `request.respond_to?("has_#{key}?".to_sym) && !request.send("has_#{key}?".to_sym)`
    # The reason we set emit_defaults: true is to avoid
    # having to figure out default values for the required
    # fields at a runtime.
    #
    # Make a new one for each binding because extract_scalar_value! is destructive
    request_hash = JSON.parse request.to_json emit_defaults: true
    uri_values = bind_uri_values! http_binding, request_hash
    next if uri_values.any? { |_, value| value.nil? }
    # Note that the body template can only point to a top-level field,
    # so there is no need to split the path.
    next if http_binding.body && http_binding.body != "*" && !(request.respond_to? http_binding.body.to_sym)
    method = http_binding.method
    uri = expand_template http_binding.template, uri_values
    body, query_params = construct_body_query_params http_binding.body, request_hash, request
    return method, uri, query_params, body
  end
  raise ::Gapic::Common::Error,
        "Request object does not match any transcoding template. Cannot form a correct REST call."
end

def with_bindings uri_method:, uri_template:, matches: [], body: nil

Returns:
  • (Gapic::Rest::GrpcTranscoder) - The updated transcoder.

Parameters:
  • body (String, Nil) -- The body template, e.g. `*` or a field path.
  • matches (Array) -- Variable bindings in an array. Every element
  • uri_template (String) -- The string with uri template for the binding.
  • uri_method (Symbol) -- The rest verb for the binding.

Other tags:
    Private: -
def with_bindings uri_method:, uri_template:, matches: [], body: nil
  binding = HttpBinding.create_with_validation(uri_method: uri_method,
                                               uri_template: uri_template,
                                               matches: matches,
                                               body: body)
  GrpcTranscoder.new @bindings + [binding]
end