# File lib/puppet/indirector/catalog/puppetdb.rb, line 229
  def synthesize_edges(hash, catalog)
    profile("Synthesize edges",
            [:puppetdb, :edges, :synthesize]) do
      aliases = map_aliases_to_title(hash)

      resource_table = {}
      profile("Build up resource_table",
              [:puppetdb, :edges, :synthesize, :resource_table, :build]) do
        hash['resources'].each do |resource|
          resource_table[ [resource['type'], resource['title']] ] = resource
        end
      end

      profile("Primary synthesis",
              [:puppetdb, :edges, :synthesize, :primary_synthesis])do
        hash['resources'].each do |resource|
          # Standard virtual resources don't appear in the catalog. However,
          # exported resources which haven't been also collected will appears as
          # exported and virtual (collected ones will only be exported). They will
          # eventually be removed from the catalog, so we can't add edges involving
          # them. Puppet::Resource#to_data_hash omits 'virtual', so we have to
          # look it up in the catalog to find that information. This isn't done in
          # a separate step because we don't actually want to send the field (it
          # will always be false). See ticket #16472.
          #
          # The outer conditional is here because Class[main] can't properly be
          # looked up using catalog.resource and will return nil. See ticket
          # #16473. Yay.
          if real_resource = catalog.resource(resource['type'], resource['title'])
            next if real_resource.virtual?
          end

          Relationships.each do |param,relation|
            if value = resource['parameters'][param]
              [value].flatten.each do |other_ref|
                edge = {'relationship' => relation[:relationship]}

                resource_hash = {'type' => resource['type'], 'title' => resource['title']}
                other_hash = resource_ref_to_hash(other_ref)

                # Puppet doesn't always seem to check this correctly. If we don't
                # users will later get an invalid relationship error instead.
                #
                # Primarily we are trying to catch the non-capitalized resourceref
                # case problem here: http://projects.puppetlabs.com/issues/19474
                # Once that problem is solved and older versions of Puppet that have
                # the bug are no longer supported we can probably remove this code.
                unless other_ref =~ /^[A-Z][a-z0-9_-]*(::[A-Z][a-z0-9_-]*)*\[.*\]/m
                  rel = edge_to_s(resource_hash_to_ref(resource_hash), other_ref, param)
                  raise Puppet::Error, "Invalid relationship: #{rel}, because " +
                    "#{other_ref} doesn't seem to be in the correct format. " +
                    "Resource references should be formatted as: " +
                    "Classname['title'] or Modulename::Classname['title'] (take " +
                    "careful note of the capitalization)."
                end

                # This is an unfortunate hack.  Puppet does some weird things w/rt
                # munging off trailing slashes from file resources, and users may
                # legally specify relationships using a different number of trailing
                # slashes than the resource was originally declared with.
                # We do know that for any file resource in the catalog, there should
                # be a canonical entry for it that contains no trailing slashes.  So,
                # here, in case someone has specified a relationship to a file resource
                # and has used one or more trailing slashes when specifying the
                # relationship, we will munge off the trailing slashes before
                # we look up the resource in the catalog to create the edge.
                if other_hash['type'] == 'File' and other_hash['title'] =~ /\/$/
                  other_hash['title'] = other_hash['title'].sub(/\/+$/, '')
                end

                other_array = [other_hash['type'], other_hash['title']]

                # Try to find the resource by type/title or look it up as an alias
                # and try that
                other_resource = resource_table[other_array]
                if other_resource.nil? and alias_hash = aliases[other_array]
                  other_resource = resource_table[ alias_hash.values_at('type', 'title') ]
                end

                raise Puppet::Error, "Invalid relationship: #{edge_to_s(resource_hash_to_ref(resource_hash), other_ref, param)}, because #{other_ref} doesn't seem to be in the catalog" unless other_resource

                # As above, virtual exported resources will eventually be removed,
                # so if a real resource refers to one, it's wrong. Non-virtual
                # exported resources are exported resources that were also
                # collected in this catalog, so they're okay. Virtual non-exported
                # resources can't appear in the catalog in the first place, so it
                # suffices to check for virtual.
                if other_real_resource = catalog.resource(other_resource['type'], other_resource['title'])
                  if other_real_resource.virtual?
                    raise Puppet::Error, "Invalid relationship: #{edge_to_s(resource_hash_to_ref(resource_hash), other_ref, param)}, because #{other_ref} is exported but not collected"
                  end
                end

                # If the ref was an alias, it will have a different title, so use
                # that
                other_hash['title'] = other_resource['title']

                if relation[:direction] == :forward
                  edge.merge!('source' => resource_hash, 'target' => other_hash)
                else
                  edge.merge!('source' => other_hash, 'target' => resource_hash)
                end
                hash['edges'] << edge
              end
            end
          end
        end
      end

      profile("Make edges unique",
              [:puppetdb, :edges, :synthesize, :make_unique]) do
        hash['edges'].uniq!
      end

      hash
    end
  end