get selected fields in create/update action

barnabasj
2023-06-05

barnabasj:

Hi, I have this policy to check if a user is only selecting fields that they are allowed to see. This worked well for reads because it was possible to get the selected/loaded fields from the query. Is it possible to do something similar for mutations

defmodule Demo.Policies.SelectsAllowedFields do
  @moduledoc """
  Checks if an action selects only fields that are allowed

  Takes a mapping of roles to allowed fields as well as a bypass option
  to circumvent the check for a given role

  e.g:

  user: [:id, :name, :last_name],
  super_user: [:id, :address],
  bypass: admin
  """
  use Ash.Policy.SimpleCheck

  require Logger

  @impl true
  def describe(_) do
    "Checks if only allowed fields are selected"
  end

  @impl true
  def match?(actor, context, options) do
    bypass_role = options[:bypass]

    matched =
      case get_role(actor) do
        role when is_atom(bypass_role) and role == bypass_role ->
          true

        role ->
          match(
            options[role],
            []
            |> Enum.concat(get_selects(context))
            |> Enum.concat(get_loads(context))
            |> Enum.concat(get_calculations(context))
            |> Enum.concat(get_aggregates(context))
          )
      end

    if not matched do
      dbg()
      allowed_fields = options[get_role(actor)] || []

      selected_fields =
        []
        |> Enum.concat(get_selects(context))
        |> Enum.concat(get_loads(context))
        |> Enum.concat(get_calculations(context))
        |> Enum.concat(get_aggregates(context))

      Logger.debug("""
      Actor (#{Map.get(actor, :id, "unknown")}) with role #{get_role(actor)} tried to access resource #{context.resource}"

      selecting: #{inspect(selected_fields)}
      allowed: #{inspect(allowed_fields)}
      diff: #{inspect(selected_fields -- allowed_fields)}
      """)
    end

    matched
  end

  def get_role(%{roles: roles}) when is_list(roles), do: List.first(roles)
  def get_role(_), do: nil

  # Get all attributes from the resource struct if no fields are selected
  #
  # @see https://www.ash-hq.org/docs/module/ash/2.4.10/ash-query#function-select-3
  #
  # ignore meta fields starting with `__`
  # and field with structs as values as those point to
  # calculations/aggregates/relationships
  defp get_selects(%{query: %{select: nil}, resource: resource}),
    do: get_resource_fields(resource)

  defp get_selects(%{query: nil, resource: resource}),
    do: get_resource_fields(resource)

  defp get_selects(%{query: %{select: select}}), do: select
  defp get_selects(e), do: raise(e)

  defp get_resource_fields(resource) do
    struct = resource.__struct__

    struct
    |> Map.keys()
    |> Enum.filter(fn key ->
      !String.starts_with?(to_string(key), "__") and
        !Enum.any?([:aggregates, :calculations], fn special_field -> special_field == key end) and
        !is_struct(
          Map.get(
            struct,
            key
          )
        )
    end)
  end

  defp get_loads(%{query: %{load: load}}), do: Keyword.keys(load)
  defp get_loads(_), do: []
  defp get_calculations(%{query: %{calculations: calculations}}), do: Map.keys(calculations)
  defp get_calculations(_), do: []
  defp get_aggregates(%{query: %{aggregates: aggregates}}), do: Map.keys(aggregates)
  defp get_aggregates(_), do: []

  defp match(allowed_fields, selected_fields)
       when is_list(allowed_fields) and is_list(selected_fields) do
    case selected_fields -- allowed_fields do
      [] ->
        true

      _ ->
        false
    end
  end

  defp match(_, _), do: false
end

zachdaniel:

šŸ¤” currently, not really. You can get the selected fields

zachdaniel:

because there is a select option on changeset

zachdaniel:

but we would need to add load to changeset as well

zachdaniel:

which would make sense to do šŸ™‚

zachdaniel:

Let me take a look

barnabasj:

I thought I read somewhere in the docs that it’s not possible to load stuff with mutations . But its been a while so not sure

zachdaniel:

Yeah, there is a way to load them but its done with api.load after the initial create

zachdaniel:

I’ll give you the first sneak preview of what I’m working on this week to solve for this need

zachdaniel:

  field_policy :field do
    authorize_if expr(id == ^actor(:id))
    authorize_if SomeRuntimeCheck # forbidden if you're filtering on this
  end

zachdaniel:

It will come w/ a similar thing to %Ash.NotLoaded{} called %Ash.ForbiddenField{}

zachdaniel:

and it will resolve that field to a forbidden error in gql (the entire request won’t be forbidden, but the fields you can’t see will have a forbidden error in their resolution

zachdaniel:

The other aspect of this though is that we will actually deprecate loading? checks in policies

zachdaniel:

You can still use selecting , but everything related to can you load X should be done as a field policy

barnabasj:

Fortunately I never used loading?, I did it all with this extension so far. But until now it was almost exlcusivly reads.

zachdaniel:

Yeah, there is an issue w/ that though

zachdaniel:

specifically we don’t rerun a read action on api.load

zachdaniel:

all of our authorization is done as ā€œrow levelā€

zachdaniel:

so if you can see a row, it lets you api.load anything on it.

zachdaniel:

AshGraphql first gets the record and uses api.load

zachdaniel:

meaning your policies might not be doing what you think they are doing 😢

barnabasj:

But the query for the first resource would still be getting the load list in the query right? If so I would already check for this on the first resource, if not I do have policies on all the related resources as well so I should be good for now. But definitly something to consider

zachdaniel:

the current implementation it will not get the load, no

zachdaniel:

the load happens later

barnabasj:

this will trigger the policy on the loaded resource though right?

zachdaniel:

yeah, all resource policies are still run

zachdaniel:

but aggregates/calculations will always appear to not be being loaded

barnabasj:

Great, in that case I will be able to sleep tonight 🤣

zachdaniel:

So like if you have a calculation you’re locking down using that check you showed me to see if its being loaded

zachdaniel:

it will always appear that its not being loaded

zachdaniel:

selected attributes will always be correct though

barnabasj:

ok, that probably means all my calculations are not secured, because there are no policies attached to calculation directly, same with aggregates. But relationships should be ok

zachdaniel:

zachdaniel:

Well, if you’re locking down calcs/aggs that way, I’m sorry

zachdaniel:

Its definitely not clear that this is how it works in the docs, hopefully will have field policies done this week

barnabasj:

Just to make sure, policies for calculation would only work correctly with the loading? policy right now?

zachdaniel:

policies for aggregates and calculations won’t work with ash_graphql essentially no matter how it works

zachdaniel:

Actually, just calculations, yeah

zachdaniel:

because it does use Ash.Query.load for aggregates

zachdaniel:

actually…I can make this better sooner

zachdaniel:

one sec

zachdaniel:

I added something to core recently that should let me make AshGraphql use Ash.Query.load for calculations

zachdaniel:

So then the only problem would be if you tried to use loading? with a relationship

zachdaniel:

i.e in your case you have get_loads that will always appear empty

frankdugan3:

Sounds like these kinds of policy expectations would a good thing to add to my app’s integration tests for the GraphQL API. šŸ˜…

zachdaniel:

ah, okay but there is an implementation detail you need to know about for your get_calculations using this new stuff:

  defp get_calculations(%{query: %{calculations: calculations}}), do: calculations |> Map.values() |> Enum.map(&(&1.calc_name)) |> Enum.reject(&is_nil/1)
  defp get_calculations(_), do: []
 

zachdaniel:

So there may actually be a way to solve this without the need for field_policies (although field policies would be nice to have). The primary reason that ash_graphql can’t use Ash.Query.load is because the caller might do this:

things{
  relationship(limit: 10) {
  }
  relationship(limit: 2) {

  }
}

zachdaniel:

Right now, there is not a way to load the same relationship two different ways

zachdaniel:

so AshGraphql uses api.load after the fact to account for that. But if I add Ash.Query.load_relationship_as in the same way we have Ash.Query.load_calculation_as , and add a relationships key to the resource similar to calculations and aggregates for ā€œanonymousā€ calcs/aggs, then we can create a single Ash.Query.load statement, and then your policies will work 100% as you have them

zachdaniel:

(as long as you add that get_calculations/1 change I mentioned above)

zachdaniel:

I could potentially do that to start, and then add field policies after. That would prevent any unexpected behavior for current implementations. But once field_policy is added, I’d still want people to use that because it will play nice with api.load if someone uses it elsewhere

zachdaniel:

Okay <@360450161845075968> if you try main of ash_graphql with the latest release of ash (2.9.19), and make that get_calculations/1 change, that check should now honor loaded calcs in ash_graphql

barnabasj:

thanks i will have a look at it shortly

zachdaniel:

As for the original question: I’m going to be adding Ash.Changeset.load and your policy will then be usable on changesets as well in almost exactly the same way.

zachdaniel:

You should do it with aggregates too, actually:

 defp get_aggregates(%{query: %{aggregates: aggregates}}), do: aggregates |> Map.values() |> Enum.map(&(&1.agg_name)) |> Enum.reject(&is_nil/1)
  defp get_aggregates(_), do: []

barnabasj:

<@816769011533742100> might be interesting for you

barnabasj:

A quick test looked promising. The policy seems to work, but I’m getting an error down the line. Not 100% sure if that is just coincidental or related. Will take a closer look tomorrow

[error] Task #PID<0.1700.0> started from #PID<0.1695.0> terminating
** (KeyError) key :source_attribute not found in: nil. If you are using the dot syntax, such as map.field, make sure the left-hand side of the dot is a map
    (ash 2.9.19) lib/ash/actions/read.ex:1472: anonymous fn/3 in Ash.Actions.Read.calculation_dependency_requests/9
    (elixir 1.14.3) lib/enum.ex:4249: Enum.flat_map_list/2
    (ash 2.9.19) lib/ash/actions/read.ex:1461: anonymous fn/9 in Ash.Actions.Read.calculation_dependency_requests/9
    (elixir 1.14.3) lib/enum.ex:4249: Enum.flat_map_list/2
    (elixir 1.14.3) lib/enum.ex:4250: Enum.flat_map_list/2
    (ash 2.9.19) lib/ash/actions/read.ex:820: anonymous fn/4 in Ash.Actions.Read.data_field/3
    (ash 2.9.19) lib/ash/engine/engine.ex:537: anonymous fn/2 in Ash.Engine.run_iteration/1
    (ash 2.9.19) lib/ash/engine/engine.ex:558: anonymous fn/4 in Ash.Engine.async/2
    (elixir 1.14.3) lib/task/supervised.ex:89: Task.Supervised.invoke_mfa/2
    (elixir 1.14.3) lib/task/supervised.ex:34: Task.Supervised.reply/4
    (stdlib 4.2) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
    (ash 2.9.19) lib/ash/engine/engine.ex:552: Ash.Engine.async/2
    (elixir 1.14.3) lib/enum.ex:1658: Enum."-map/2-lists^map/1-0-"/2
    (ash 2.9.19) lib/ash/engine/engine.ex:702: Ash.Engine.start_pending_tasks/1
    (ash 2.9.19) lib/ash/engine/engine.ex:323: Ash.Engine.run_to_completion/1
    (ash 2.9.19) lib/ash/engine/engine.ex:252: Ash.Engine.do_run/2
    (ash 2.9.19) lib/ash/engine/engine.ex:148: Ash.Engine.run/2
    (ash 2.9.19) lib/ash/actions/read.ex:173: Ash.Actions.Read.do_run/3
    (ash 2.9.19) lib/ash/actions/read.ex:96: Ash.Actions.Read.run/3
    (ash 2.9.19) lib/ash/actions/load.ex:1544: Ash.Actions.Load.artificial_limit_and_offset/9
Function: &:erlang.apply/2
    Args: [#Function<15.63837685/1 in Dataloader.Source.AshGraphql.Dataloader.run_batches/1>, ...

zachdaniel:

😢 Hmm…yeah something is up there

zachdaniel:

I’ll have it raise a more informative (but still raise) error in that case that could potentially help track the issue down.

zachdaniel:

okay, I’ve added Ash.Changeset.load to ash core, and used it in AshGraphql

zachdaniel:

The only remaining ā€œissueā€ is that AshGraphql loads related resources after-the-fact, not in the main query

zachdaniel:

Alright, so the last thing to cover this stuff/wrap it up is the ability to load the same relationship multiple times in different ways. I’m not entirely sure how difficult this is going to end up being šŸ™‚ But once I do that, I can add utilities like Ash.Changeset.loading(changeset) => [%Attribute{}, %Calculation{}, %Aggregate{}] and Ash.Query.loading that will get you the list of things being loaded, to abstract away some of that complexity.

barnabasj:

So far everything seems to work, thank you.

barnabasj:

After some further tests, two problems emerged. I found a place where we use the aliases already. There we get this error now:

[error] Task #PID<0.1457.0> started from #PID<0.1450.0> terminating
** (Protocol.UndefinedError) protocol String.Chars not implemented for {:__ash_graphql_calculation__, "image16x9"} of type Tuple
    (elixir 1.14.3) lib/string/chars.ex:3: String.Chars.impl_for!/1
    (elixir 1.14.3) lib/string/chars.ex:22: String.Chars.to_string/1
    (ash 2.9.21) lib/ash/filter/filter.ex:2357: anonymous fn/1 in Ash.Filter.add_expression_part/3
    (elixir 1.14.3) lib/enum.ex:1251: anonymous fn/3 in Enum.flat_map/2
    (stdlib 4.2) maps.erl:411: :maps.fold_1/3
    (elixir 1.14.3) lib/enum.ex:2480: Enum.flat_map/2
    (ash 2.9.21) lib/ash/filter/filter.ex:2356: Ash.Filter.add_expression_part/3
    (ash 2.9.21) lib/ash/filter/filter.ex:2078: anonymous fn/3 in Ash.Filter.parse_expression/2
    (elixir 1.14.3) lib/enum.ex:4751: Enumerable.List.reduce/3
    (elixir 1.14.3) lib/enum.ex:2514: Enum.reduce_while/3
    (ash 2.9.21) lib/ash/filter/filter.ex:314: Ash.Filter.parse/5
    (ash 2.9.21) lib/ash/query/query.ex:2032: Ash.Query.do_filter/2
    (ash 2.9.21) lib/ash/actions/load.ex:1750: Ash.Actions.Load.get_query/4
    (ash 2.9.21) lib/ash/actions/load.ex:969: anonymous fn/10 in Ash.Actions.Load.data/10
    (ash 2.9.21) lib/ash/engine/engine.ex:537: anonymous fn/2 in Ash.Engine.run_iteration/1
    (ash 2.9.21) lib/ash/engine/engine.ex:558: anonymous fn/4 in Ash.Engine.async/2
    (elixir 1.14.3) lib/task/supervised.ex:89: Task.Supervised.invoke_mfa/2
    (elixir 1.14.3) lib/task/supervised.ex:34: Task.Supervised.reply/4
    (stdlib 4.2) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
    (ash 2.9.21) lib/ash/engine/engine.ex:552: Ash.Engine.async/2
Function: &:erlang.apply/2
    Args: [#Function<15.63837685/1 in Dataloader.Source.AshGraphql.Dataloader.run_batches/1>, 

barnabasj:

And I had a calculation that worked before now triggers this error:

[error] Task #PID<0.1708.0> started from #PID<0.1698.0> terminating
** (RuntimeError) Internal Error:

Calculation depends on relationship but we could not determine the relationship

Resource: JdlEngine.Inventory.Resources.Hotel

Relationship Dependency:
%{path: [hotel_itinerary_items: #Ash.Query<resource: Demo.Offer.Resources.ItineraryItem.HotelItineraryItem>, hotel: #Ash.Query<resource: Demo.Inventory.Resources.Hotel>], query: #Ash.Query<resource: Demo.Geo.Resources.Country>, relationship: :country, type: :relationship}

    (ash 2.9.21) lib/ash/actions/read.ex:1500: anonymous fn/15 in Ash.Actions.Read.calculation_dependency_requests/9
    (elixir 1.14.3) lib/enum.ex:4249: Enum.flat_map_list/2
    (elixir 1.14.3) lib/enum.ex:4250: Enum.flat_map_list/2
    (ash 2.9.21) lib/ash/actions/read.ex:1479: anonymous fn/10 in Ash.Actions.Read.calculation_dependency_requests/9
    (elixir 1.14.3) lib/enum.ex:4249: Enum.flat_map_list/2
    (elixir 1.14.3) lib/enum.ex:4250: Enum.flat_map_list/2
    (ash 2.9.21) lib/ash/actions/read.ex:838: anonymous fn/4 in Ash.Actions.Read.data_field/3
    (ash 2.9.21) lib/ash/engine/engine.ex:537: anonymous fn/2 in Ash.Engine.run_iteration/1
    (ash 2.9.21) lib/ash/engine/engine.ex:558: anonymous fn/4 in Ash.Engine.async/2
    (elixir 1.14.3) lib/task/supervised.ex:89: Task.Supervised.invoke_mfa/2
    (elixir 1.14.3) lib/task/supervised.ex:34: Task.Supervised.reply/4
    (stdlib 4.2) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
    (ash 2.9.21) lib/ash/engine/engine.ex:552: Ash.Engine.async/2
    (elixir 1.14.3) lib/enum.ex:1658: Enum."-map/2-lists^map/1-0-"/2
    (ash 2.9.21) lib/ash/engine/engine.ex:702: Ash.Engine.start_pending_tasks/1
    (ash 2.9.21) lib/ash/engine/engine.ex:323: Ash.Engine.run_to_completion/1
    (ash 2.9.21) lib/ash/engine/engine.ex:252: Ash.Engine.do_run/2
    (ash 2.9.21) lib/ash/engine/engine.ex:148: Ash.Engine.run/2
    (ash 2.9.21) lib/ash/actions/read.ex:173: Ash.Actions.Read.do_run/3
    (ash 2.9.21) lib/ash/actions/read.ex:96: Ash.Actions.Read.run/3
Function: &:erlang.apply/2
    Args: [#Function<15.63837685/1 in Dataloader.Source.AshGraphql.Dataloader.run_batches/1>, 

I tried to debug the second error a bit, and it seems it is looking for a hotel relationship on the hotel instead of the items, but I’m not sure why

zachdaniel:

Will look into these both today.

barnabasj:

ash: 8fdd319697ff39262fc4162f38fd6c55e2b9b189 is the last commit that I know worked for us in relation to the query/loads. Policy problems aside

barnabasj:

This is the load statement in the calc that now fails:

    @impl true
    def load(_, _, _) do
      [
        hotel_itinerary_items:
          Demo.Offer.Resources.ItineraryItem.HotelItineraryItem
          |> Ash.Query.load(
            hotel:
              Demo.Inventory.Resources.Hotel
              |> Ash.Query.select(:id)
              |> Ash.Query.load(:country)
          )
          |> Ash.Query.load(:sort_order),
        villa_itinerary_items:
          Demo.Offer.Resources.ItineraryItem.VillaItineraryItem
          |> Ash.Query.load(
            villa:
              Demo.Inventory.Resources.Villa
              |> Ash.Query.select(:id)
              |> Ash.Query.load(:country)
          )
          |> Ash.Query.load(:sort_order)
      ]
    end

zachdaniel:

Okay, so I just pushed up a fix for the first issue

zachdaniel:

the second one might be more complicated

zachdaniel:

If you’re still around, the error message I was outputting there is actually wrong a bit

barnabasj:

i can try it shortly

zachdaniel:

awesome, thanks

zachdaniel:

actually we need both

zachdaniel:

okay, just pushed a better message up to main, hopefully will help me track the error down

barnabasj:

The calc load now givs me this error:

[error] Task #PID<0.6739.0> started from #PID<0.6731.0> terminating
** (RuntimeError) Internal Error:

Calculation depends on relationship but we could not determine the relationship

Resource: Demo.Inventory.Resources.Hotel

Dependency:
%{path: [hotel_itinerary_items: #Ash.Query<resource: Demo.Offer.Resources.ItineraryItem.HotelItineraryItem>], query: #Ash.Query<resource: Demo.Inventory.Resources.Hotel, select: [:id]>, relationship: :hotel, type: :relationship}

Relationship Dependency:
%{path: [hotel_itinerary_items: #Ash.Query<resource: Demo.Offer.Resources.ItineraryItem.HotelItineraryItem>, hotel: #Ash.Query<resource: Demo.Inventory.Resources.Hotel, select: [:id]>], query: #Ash.Query<resource: Demo.Geo.Resources.Country>, relationship: :country, type: :relationship}

    (ash 2.9.21) lib/ash/actions/read.ex:1493: anonymous fn/3 in Ash.Actions.Read.calculation_dependency_requests/9
    (elixir 1.14.3) lib/enum.ex:4249: Enum.flat_map_list/2
    (ash 2.9.21) lib/ash/actions/read.ex:1479: anonymous fn/9 in Ash.Actions.Read.calculation_dependency_requests/9
    (elixir 1.14.3) lib/enum.ex:4249: Enum.flat_map_list/2
    (elixir 1.14.3) lib/enum.ex:4250: Enum.flat_map_list/2
    (ash 2.9.21) lib/ash/actions/read.ex:838: anonymous fn/4 in Ash.Actions.Read.data_field/3
    (ash 2.9.21) lib/ash/engine/engine.ex:537: anonymous fn/2 in Ash.Engine.run_iteration/1
    (ash 2.9.21) lib/ash/engine/engine.ex:558: anonymous fn/4 in Ash.Engine.async/2
    (elixir 1.14.3) lib/task/supervised.ex:89: Task.Supervised.invoke_mfa/2
    (elixir 1.14.3) lib/task/supervised.ex:34: Task.Supervised.reply/4
    (stdlib 4.2) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
    (ash 2.9.21) lib/ash/engine/engine.ex:552: Ash.Engine.async/2
    (elixir 1.14.3) lib/enum.ex:1658: Enum."-map/2-lists^map/1-0-"/2
    (ash 2.9.21) lib/ash/engine/engine.ex:702: Ash.Engine.start_pending_tasks/1
    (ash 2.9.21) lib/ash/engine/engine.ex:323: Ash.Engine.run_to_completion/1
    (ash 2.9.21) lib/ash/engine/engine.ex:252: Ash.Engine.do_run/2
    (ash 2.9.21) lib/ash/engine/engine.ex:148: Ash.Engine.run/2
    (ash 2.9.21) lib/ash/actions/read.ex:173: Ash.Actions.Read.do_run/3
    (ash 2.9.21) lib/ash/actions/read.ex:96: Ash.Actions.Read.run/3
    (ash 2.9.21) lib/ash/actions/load.ex:1549: Ash.Actions.Load.artificial_limit_and_offset/9
Function: &:erlang.apply/2

barnabasj:

what I do not understand about this error message is why is the Hotel the resource, because the calculation is on the collection which is not in the error message so the load would be collection -> hotel_itinerary_items -> hotel -> country

zachdaniel:

are you sure its the same error? could there be a different stacktrace?

barnabasj:

let me check,

barnabasj:

[error] Task #PID<0.7072.0> started from #PID<0.7061.0> terminating
** (Ash.Error.Unknown) Unknown Error

Context: resolving data on process Demo.Offer.Resources.ItineraryCollection.Image.read
* Context: resolving data on process Demo.Offer.Resources.ItineraryCollection.Image.read

** (Protocol.UndefinedError) protocol String.Chars not implemented for {:__ash_graphql_calculation__, "image16x9"} of type Tuple
  (elixir 1.14.3) lib/string/chars.ex:3: String.Chars.impl_for!/1
  (elixir 1.14.3) lib/string/chars.ex:22: String.Chars.to_string/1
  (ash 2.9.21) lib/ash/actions/read.ex:2525: Ash.Actions.Read.calculation_request/14
  (elixir 1.14.3) lib/enum.ex:1658: Enum."-map/2-lists^map/1-0-"/2
  (ash 2.9.21) lib/ash/actions/read.ex:2661: Ash.Actions.Read.add_calculation_values/14
  (ash 2.9.21) lib/ash/actions/read.ex:411: anonymous fn/8 in Ash.Actions.Read.as_requests/5
  (ash 2.9.21) lib/ash/engine/request.ex:1048: Ash.Engine.Request.do_try_resolve_local/4
  (ash 2.9.21) lib/ash/engine/request.ex:282: Ash.Engine.Request.do_next/1
  (ash 2.9.21) lib/ash/engine/request.ex:211: Ash.Engine.Request.next/1
  (ash 2.9.21) lib/ash/engine/engine.ex:712: Ash.Engine.advance_request/2
  (ash 2.9.21) lib/ash/engine/engine.ex:637: Ash.Engine.fully_advance_request/2
  (ash 2.9.21) lib/ash/engine/engine.ex:578: Ash.Engine.do_run_iteration/2
  (elixir 1.14.3) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
  (ash 2.9.21) lib/ash/engine/engine.ex:307: Ash.Engine.run_to_completion/1
  (ash 2.9.21) lib/ash/engine/engine.ex:252: Ash.Engine.do_run/2
  (ash 2.9.21) lib/ash/engine/engine.ex:148: Ash.Engine.run/2
  (ash 2.9.21) lib/ash/actions/read.ex:173: Ash.Actions.Read.do_run/3
  (ash 2.9.21) lib/ash/actions/read.ex:96: Ash.Actions.Read.run/3
  (ash 2.9.21) lib/ash/actions/load.ex:1549: Ash.Actions.Load.artificial_limit_and_offset/9
  (ash 2.9.21) lib/ash/actions/load.ex:977: anonymous fn/10 in Ash.Actions.Load.data/10
    (elixir 1.14.3) lib/string/chars.ex:3: String.Chars.impl_for!/1
    (elixir 1.14.3) lib/string/chars.ex:22: String.Chars.to_string/1
    (ash 2.9.21) lib/ash/actions/read.ex:2525: Ash.Actions.Read.calculation_request/14
    (elixir 1.14.3) lib/enum.ex:1658: Enum."-map/2-lists^map/1-0-"/2
    (ash 2.9.21) lib/ash/actions/read.ex:2661: Ash.Actions.Read.add_calculation_values/14
    (ash 2.9.21) lib/ash/actions/read.ex:411: anonymous fn/8 in Ash.Actions.Read.as_requests/5
    (ash 2.9.21) lib/ash/engine/request.ex:1048: Ash.Engine.Request.do_try_resolve_local/4
    (ash 2.9.21) lib/ash/engine/request.ex:282: Ash.Engine.Request.do_next/1
    (ash 2.9.21) lib/ash/engine/request.ex:211: Ash.Engine.Request.next/1
    (ash 2.9.21) lib/ash/engine/engine.ex:712: Ash.Engine.advance_request/2
    (ash 2.9.21) lib/ash/engine/engine.ex:637: Ash.Engine.fully_advance_request/2
    (ash 2.9.21) lib/ash/engine/engine.ex:578: Ash.Engine.do_run_iteration/2
    (elixir 1.14.3) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
    (ash 2.9.21) lib/ash/engine/engine.ex:307: Ash.Engine.run_to_completion/1
    (ash 2.9.21) lib/ash/engine/engine.ex:252: Ash.Engine.do_run/2
    (ash 2.9.21) lib/ash/engine/engine.ex:148: Ash.Engine.run/2
    (ash 2.9.21) lib/ash/actions/read.ex:173: Ash.Actions.Read.do_run/3
    (ash 2.9.21) lib/ash/actions/read.ex:96: Ash.Actions.Read.run/3
    (ash 2.9.21) lib/ash/actions/load.ex:1549: Ash.Actions.Load.artificial_limit_and_offset/9
    (ash 2.9.21) lib/ash/actions/load.ex:977: anonymous fn/10 in Ash.Actions.Load.data/10
Function: &:erlang.apply/2

barnabasj:

ok, yeah stack trace is bigger <a:facepalm:803600909254131722>

zachdaniel:

okay, pushed up another fix for that

zachdaniel:

just more places we’re thinking we can do to_string(calc.name) but we can’t always

zachdaniel:

could potentially be more, its hard to find them all the time šŸ˜† I’m surprised tests in ash_graphql didn’t find this. I guess its because it didn’t test dynamic calcs with calcs with dependencies

zachdaniel:

okay, and I think I found the issue for the dependency issue also

zachdaniel:

so main should have fixes for both hopefully

barnabasj:

%{
  {:__ash_graphql_calculation__, "image16x9"} => #Demo.Calculations.ImageUrlCaculation<[]> - %{named_transformations: ["crop_16:9_centered"]}
}
calculation: %{
  calc_name: {:__ash_graphql_calculation__, "image16x9"},
  constraints: [allow_empty?: false, trim?: true],
  context: %{

I logged the calculations in the query, I was just wondering why the calc_name was not the original calc name. Is there a way to get the original calc name from the alias?

zachdaniel:

oh

zachdaniel:

yeah that is what that is supposed to be

zachdaniel:

thanks for pointing that out

zachdaniel:

calc_name should be nil for anonymous calculations (which you can’t do through the api) and :the_resource_calc_name otherwise

zachdaniel:

okay, pushed a fix for that as well

barnabasj:

great, thanks. now i can get the correct calc name and check if the user is allowed to get it too šŸš€

zachdaniel:

🄳 🄳 🄳

barnabasj:

Just clicked through all the pages, looks like everything is working now <a:party_parrot:1085610631912226846>