class FunctionsFramework::LegacyEventConverter

Converter from legacy GCF event formats to CloudEvents.

Constants

CE_SERVICE_TO_RESOURCE_RE
FIREBASE_AUTH_METADATA_LEGACY_TO_CE

Map Firebase Auth legacy event metadata field names to their equivalent CloudEvent field names.

LEGACY_TYPE_TO_CE_TYPE
LEGACY_TYPE_TO_SERVICE

Public Instance Methods

decode_rack_env(env) click to toggle source

Decode an event from the given Rack environment hash.

@param env [Hash] The Rack environment @return [::CloudEvents::Event] if the request could be converted @return [nil] if the event format was not recognized.

# File lib/functions_framework/legacy_event_converter.rb, line 29
def decode_rack_env env
  content_type = ::CloudEvents::ContentType.new env["CONTENT_TYPE"], default_charset: "utf-8"
  return nil unless content_type.media_type == "application" && content_type.subtype_base == "json"
  input = read_input_json env["rack.input"], content_type.charset
  return nil unless input
  input = convert_raw_pubsub_event input, env if raw_pubsub_payload? input
  context = normalized_context input
  return nil unless context
  construct_cloud_event context, input["data"]
end

Private Instance Methods

analyze_resource(raw_resource) click to toggle source
# File lib/functions_framework/legacy_event_converter.rb, line 98
def analyze_resource raw_resource
  service = resource = nil
  case raw_resource
  when ::Hash
    service = raw_resource["service"]
    resource = raw_resource["name"]
  when ::String
    resource = raw_resource
  end
  [service, resource]
end
construct_cloud_event(context, data) click to toggle source
# File lib/functions_framework/legacy_event_converter.rb, line 117
def construct_cloud_event context, data
  source, subject = convert_source context[:service], context[:resource], context[:domain]
  type = LEGACY_TYPE_TO_CE_TYPE[context[:type]]
  return nil unless type && source
  ce_data, data_subject = convert_data context, data
  content_type = "application/json"
  ::CloudEvents::Event.new id:                context[:id],
                           source:            source,
                           type:              type,
                           spec_version:      "1.0",
                           data_content_type: content_type,
                           data:              ce_data,
                           subject:           subject || data_subject,
                           time:              context[:timestamp]
end
convert_data(context, data) click to toggle source
# File lib/functions_framework/legacy_event_converter.rb, line 157
def convert_data context, data
  service = context[:service]
  case service
  when "pubsub.googleapis.com"
    data["messageId"] = context[:id]
    data["publishTime"] = context[:timestamp]
    [{ "message" => data }, nil]
  when "firebaseauth.googleapis.com"
    if data.key? "metadata"
      FIREBASE_AUTH_METADATA_LEGACY_TO_CE.each do |old_key, new_key|
        if data["metadata"].key? old_key
          data["metadata"][new_key] = data["metadata"][old_key]
          data["metadata"].delete old_key
        end
      end
    end
    subject = "users/#{data['uid']}" if data.key? "uid"
    [data, subject]
  else
    [data, nil]
  end
end
convert_raw_pubsub_event(input, env) click to toggle source
# File lib/functions_framework/legacy_event_converter.rb, line 58
def convert_raw_pubsub_event input, env
  message = input["message"]
  path = "#{env['SCRIPT_NAME']}#{env['PATH_INFO']}"
  path_match = %r{projects/[^/?]+/topics/[^/?]+}.match path
  topic = path_match ? path_match[0] : "UNKNOWN_PUBSUB_TOPIC"
  timestamp = message["publishTime"] || ::Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.%6NZ")
  {
    "context" => {
      "eventId" => message["messageId"],
      "timestamp" => timestamp,
      "eventType" => "google.pubsub.topic.publish",
      "resource" => {
        "service" => "pubsub.googleapis.com",
        "type" => "type.googleapis.com/google.pubsub.v1.PubsubMessage",
        "name" => topic
      }
    },
    "data" => {
      "@type" => "type.googleapis.com/google.pubsub.v1.PubsubMessage",
      "data" => message["data"],
      "attributes" => message["attributes"]
    }
  }
end
convert_source(service, resource, domain) click to toggle source
# File lib/functions_framework/legacy_event_converter.rb, line 133
def convert_source service, resource, domain
  return ["//#{service}/#{resource}", nil] unless CE_SERVICE_TO_RESOURCE_RE.key? service

  match = CE_SERVICE_TO_RESOURCE_RE[service].match resource
  return [nil, nil] unless match
  resource_fragment = match[1]
  subject = match[2]

  if service == "firebasedatabase.googleapis.com"
    location =
      case domain
      when "firebaseio.com"
        "us-central1"
      when /^([\w-]+)\./
        Regexp.last_match[1]
      else
        return [nil, nil]
      end
    ["//#{service}/projects/_/locations/#{location}/#{resource_fragment}", subject]
  else
    ["//#{service}/#{resource_fragment}", subject]
  end
end
normalized_context(input) click to toggle source
# File lib/functions_framework/legacy_event_converter.rb, line 83
def normalized_context input
  id = normalized_context_field input, "eventId"
  timestamp = normalized_context_field input, "timestamp"
  type = normalized_context_field input, "eventType"
  domain = normalized_context_field input, "domain"
  service, resource = analyze_resource normalized_context_field input, "resource"
  service ||= service_from_type type
  return nil unless id && timestamp && type && service && resource
  { id: id, timestamp: timestamp, type: type, service: service, resource: resource, domain: domain }
end
normalized_context_field(input, field) click to toggle source
# File lib/functions_framework/legacy_event_converter.rb, line 94
def normalized_context_field input, field
  input["context"]&.[](field) || input[field]
end
raw_pubsub_payload?(input) click to toggle source
# File lib/functions_framework/legacy_event_converter.rb, line 52
def raw_pubsub_payload? input
  return false if input.include?("context") || !input.include?("subscription")
  message = input["message"]
  message.is_a?(::Hash) && message.include?("data") && message.include?("messageId")
end
read_input_json(input, charset) click to toggle source
# File lib/functions_framework/legacy_event_converter.rb, line 42
def read_input_json input, charset
  input = input.read if input.respond_to? :read
  input.force_encoding charset if charset
  content = ::JSON.parse input
  content = nil unless content.is_a? ::Hash
  content
rescue ::JSON::ParserError
  nil
end
service_from_type(type) click to toggle source
# File lib/functions_framework/legacy_event_converter.rb, line 110
def service_from_type type
  LEGACY_TYPE_TO_SERVICE.each do |pattern, service|
    return service if pattern =~ type
  end
  nil
end