absinthe-graphql / absinthe

The GraphQL toolkit for Elixir
http://absinthe-graphql.org
Other
4.28k stars 524 forks source link

absinthe_types/1 gets called with two args #614

Closed olivermt closed 6 years ago

olivermt commented 6 years ago

If submitting a bug, please provide the following:

Environment

Expected behavior

fields with data for query:

query GetVirtualOutcropModelsByOutcrop{
  virtualOutcropModelList(outcropId: 58008){
    outcrop {
      name
      georeference {
        ... on GeoreferencePoint {
          name
        }

      }
    } 
  }
}

Actual behavior

** (exit) an exception was raised:
    ** (BadArityError) #Function<0.4445879/1 in SafariWeb.Schema.LocationTypes.__absinthe_type__/1> with arity 1 called with 2 arguments (%{data: %{coordinates: %{langitude: 29.769673597523774, longitude: -115.62357985340464}}, data_type: "centre", description: "Playa Escolete section", id: 1078, inserted_at: ~N[2018-03-19 10:26:40.169642], name: "Playa Escolete section", outcrop_id: 58008, updated_at: ~N[2018-03-19 10:26:40.169668]}, %Absinthe.Blueprint.Execution{acc: %{Absinthe.Middleware.Async => false, Absinthe.Middleware.Batch => %{input: [], output: %{{Safari.Location.GeoreferenceService, :batch_many, {:outcrop_id, %RestAuth.Authority{anonymous: false, metadata: %{company: %{id: 1, name: "Redacted"}, name: "Oliver Tynes", username: "oliver@redacted"}, roles: ["ROLE_USER", "ROLE_SAFARI2", "ROLE_SAFARI3", "ROLE_COMPANY_ADMIN", "ROLE_ADMIN"], token:

I truncated the output, the rest are some security tokens + the entire blueprint.

I am transforming the binary map keys to atom keys, because I intially thought that was why it went boom before I read error more closely.

Relevant Schema/Middleware Code

Relevant schema:

defmodule SafariWeb.Schema.LocationTypes do
  use Absinthe.Schema.Notation

  union :georeference do
    types([:georeference_point, :georeference_polygon])

    resolve_type(fn
      %{data_type: dt} when dt in ["centre", "point"] ->
        :georeference_point

      _ ->
        :georeference_polygon
    end)
  end

  enum :georeference_type_point do
    value(:centre, as: "centre")
    value(:point, as: "point")
  end

  enum :georeference_type_polygon do
    value(:outline, as: "outline")
    value(:polygon, as: "polygon")
    value(:polyline, as: "polyline")
  end

  object :georeference_point do
    field(:name, :string)
    field(:description, :string)
    field(:data_type, :georeference_type_point)
    field(:data, :coordinates_point)
  end

  object :georeference_polygon do
    field(:name, :string)
    field(:description, :string)
    field(:data_type, :georeference_type_polygon)
    field(:data, :coordinates_polygon)
  end

  object :coordinates_point do
    field(:coordinates, :georeference_lon_lat)
  end

  object :coordinates_polygon do
    field(:coordinates, list_of(:georeference_lon_lat))
  end

  object :georeference_lon_lat do
    field(:longitude, :float)
    field(:latitude, :float)
  end

end

Relevant middleware / resolver:

defmodule SafariWeb.Resolvers.LocationResolver do
  import Absinthe.Resolution.Helpers, only: [batch: 3]
  alias Safari.Location.GeoreferenceService
  require Logger

  def georeference_derived(parent, _args, %{context: %{auth: auth}}) do
    case parent do
      %{__struct__: Safari.Depositional.Outcrop} ->
        batch({GeoreferenceService, :batch_many, {:outcrop_id, auth}}, parent.id, fn res ->
          {
            :ok,
            Map.get(res, parent.id)
          }
        end)
      %{__struct__: Safari.Depositional.Study} ->
        batch({GeoreferenceService, :batch_many, {:study_id, auth}}, parent.id, fn res ->
          {
            :ok,
            Map.get(res, parent.id)
          }
        end)
    end
  end
end

Service module that provides the data:

defmodule Safari.Location.GeoreferenceService do
  use Safari.Service.ServiceHelper

  alias Safari.Location.{
    Georeference,
    OutcropGeoreference,
    StudyGeoreference
  }

  def batch_query(auth, field, ids) do
    ids = Enum.uniq(ids)

    join_table = case field do
      :outcrop_id ->
        OutcropGeoreference
      :study_id ->
        StudyGeoreference
    end

    main_fields = Georeference.__schema__(:fields)
    parent_pk_fields = [field]
    (
      from jt in join_table,
      inner_join: g in assoc(jt, :georeference),
      where: field(jt, ^field) in ^ids,
      select: merge(
        map(g, ^main_fields),
        map(jt, ^parent_pk_fields)
      )
    )
  end

  @doc """
  We need to transform inner jsonb to atom keys
  """
  def transform_batch_row(%{data: %{"coordinates" => nil}} = row), do: row

  def transform_batch_row(%{data: %{"coordinates" => coords}} = row) when is_list(coords) do
    coords = Enum.map(coords, fn %{"longitude" => lon, "latitude" => lat} ->
      %{langitude: lat, longitude: lon}
    end)
    Map.put(row, :data, %{coordinates: coords})
  end

  def transform_batch_row(%{data: %{"coordinates" => %{"longitude" => lon, "latitude" => lat}}} = row)  do
    Map.put(row, :data, %{coordinates: %{langitude: lat, longitude: lon}})
  end

end

 def batch_many({field, auth}, ids) do
        # Logger.debug("Fetching ids: #{inspect(ids, limit: :infinity)}")
        transform_batch_row = function_exported?(__MODULE__, :transform_batch_row, 1)
        apply(__MODULE__, :batch_query, [auth, field, ids])
        # batch_query(auth, field, ids)
        |> Repo.all()
        # |> case do
        #   res ->
        #     Logger.debug("res: #{inspect(res, pretty: true, limit: :infinity)}")
        #     res
        # end
        |> Enum.reduce(%{}, fn i, acc ->
          # If module exposes transform function, each row needs to be transformed
          i = if transform_batch_row do
            IO.puts "Transforming row"
            apply(__MODULE__, :transform_batch_row, [i])
          else
            i
          end
          Map.update(acc, Map.get(i, field), [i], fn inner -> [i | inner] end)
        end)

        |> case do
          res ->
            Logger.debug("res: #{inspect(res, pretty: true, limit: :infinity)}")
            res
        end
      end

The batch_many etc comes from a macro, I just put it into the same module for documentation purposes

And at last, the data returned to the batcher for georef on outcrop, that the Map.get acts on (this is valid data):

[debug] module=Safari.Location.GeoreferenceService function=batch_many/2  res: %{
  58008 => [                                                                     
    %{                                                                           
      data: %{                                                                   
        coordinates: %{                                                          
          langitude: 29.769673597523774,                                         
          longitude: -115.62357985340464                                         
        }                                                                        
      },                                                                         
      data_type: "centre",                                                       
      description: "Playa Escolete section",                                     
      id: 1078,                                                                  
      inserted_at: ~N[2018-03-19 10:26:40.169642],                               
      name: "Playa Escolete section",                                            
      outcrop_id: 58008,                                                         
      updated_at: ~N[2018-03-19 10:26:40.169668]                                 
    },                                                                           
    %{                                                                           
      data: %{                                                                   
        coordinates: [                                                           
          %{langitude: 29.769207949733374, longitude: -115.62651955448496},      
          %{langitude: 29.77006473999488, longitude: -115.62450253330576},       
          %{langitude: 29.770381378019444, longitude: -115.6224211391102},       
          %{langitude: 29.769990236784807, longitude: -115.62076889835703},      
          %{langitude: 29.769543216360645, longitude: -115.62018954120981},      
          %{langitude: 29.769003064019813, longitude: -115.62033974491465},      
          %{langitude: 29.76915207185304, longitude: -115.62212073170053},       
          %{langitude: 29.769077567964164, longitude: -115.62349402271616},      
          %{langitude: 29.768164890827844, longitude: -115.62639080845224},      
          %{langitude: 29.768872681983922, longitude: -115.62688433491098}       
        ]                                                                        
      },                                                                         
      data_type: "outline",                                                      
      description: "Playa Escolete section outline",                             
      id: 1077,                                                                  
      inserted_at: ~N[2018-03-19 10:26:33.441609],                               
      name: "Playa Escolete section",                                            
      outcrop_id: 58008,                                                         
      updated_at: ~N[2018-03-19 10:26:33.441623]                                 
    }                                                                            
  ]                                                                              
}                                                                                
olivermt commented 6 years ago

And yes I have fixed the spelling error langitude -> latitude, still same error :)

olivermt commented 6 years ago

Also, to save you the hassle of reading the fine print, Arg 1: %{data: %{coordinates: %{latitude: 29.769673597523774, longitude: -115.62357985340464}}, data_type: "centre", description: "Playa Escolete section", id: 1078, inserted_at: ~N[2018-03-19 10:26:40.169642], name: "Playa Escolete section", outcrop_id: 58008, updated_at: ~N[2018-03-19 10:26:40.169668]}

Arg 2: The blueprint in its entirety, cant see anything useful there.. a whole lot of references to various lines in the file, seems to be one per object and enum

olivermt commented 6 years ago

And lastly, it turns out the graphiql interface has the non-truncated version as well.

So here is that too:

https://gist.githubusercontent.com/olivermt/ba1a5b9dc3f4dadf5069b5b48e812865/raw/f66ced5369973df25770482c46c079587a17f124/trace.ex

olivermt commented 6 years ago

Lastly, I'm uninstalling elixir and getting a job flipping burgers at mcdonalds...

I was calling resolve_type with just one arg, it more or less said so in clear text ffs 😿

bruce commented 6 years ago

@olivermt Take it easy on yourself! :-) Best of luck; we're here for you if you run into another issue.

madsbuch commented 1 year ago

Just adding a clarifying comment here (as I fell in the same hole):

resolve_type fn
    %Person{}, _ -> :person
    %Business{}, _ -> :business
end

is different from

resolve_type fn
    %Person{} -> :person
    %Business{} -> :business
end

For me, it has been non-standard to use anonymous functions with multiple bodies. But I learned something new!