Skip to content

Commit

Permalink
improvement: add hide_inputs to queries and mutations
Browse files Browse the repository at this point in the history
  • Loading branch information
zachdaniel committed Mar 11, 2024
1 parent 4c1f8a2 commit 90dc208
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 23 deletions.
16 changes: 15 additions & 1 deletion lib/resource/mutation.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ defmodule AshGraphql.Resource.Mutation do
:upsert?,
:upsert_identity,
:modify_resolution,
:relay_id_translations
:relay_id_translations,
hide_inputs: []
]

@create_schema [
Expand Down Expand Up @@ -39,6 +40,11 @@ defmodule AshGraphql.Resource.Mutation do
An MFA that will be called with the resolution, the query, and the result of the action as the first three arguments. See the [the guide](/documentation/topics/modifying-the-resolution.html) for more.
"""
],
hide_inputs: [
type: {:list, :atom},
default: [],
doc: "A list of inputs to hide from the mutation."
],
relay_id_translations: [
type: :keyword_list,
doc: """
Expand Down Expand Up @@ -70,6 +76,10 @@ defmodule AshGraphql.Resource.Mutation do
doc:
"The read action to use to fetch the record to be updated. Defaults to the primary read action."
],
hide_inputs: [
type: {:list, :atom},
doc: "A list of inputs to hide from the mutation."
],
relay_id_translations: [
type: :keyword_list,
doc: """
Expand Down Expand Up @@ -101,6 +111,10 @@ defmodule AshGraphql.Resource.Mutation do
The identity to use to fetch the record to be destroyed. Use `false` if no identity is required.
"""
],
hide_inputs: [
type: {:list, :atom},
doc: "A list of inputs to hide from the mutation."
],
relay_id_translations: [
type: :keyword_list,
doc: """
Expand Down
6 changes: 6 additions & 0 deletions lib/resource/query.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ defmodule AshGraphql.Resource.Query do
:modify_resolution,
:relay_id_translations,
as_mutation?: false,
hide_inputs: [],
metadata_names: [],
metadata_types: [],
show_metadata: nil,
Expand Down Expand Up @@ -60,6 +61,11 @@ defmodule AshGraphql.Resource.Query do
A keyword list indicating arguments or attributes that have to be translated from global Relay IDs to internal IDs. See the [Relay guide](/documentation/topics/relay.md#translating-relay-global-ids-passed-as-arguments) for more.
""",
default: []
],
hide_inputs: [
type: {:list, :atom},
doc: "A list of inputs to hide from the mutation.",
default: []
]
]

Expand Down
57 changes: 35 additions & 22 deletions lib/resource/resource.ex
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ defmodule AshGraphql.Resource do
doc: "The action to use for the query.",
required: true
],
hide_inputs: [
type: {:list, :atom},
doc: "Inputs to hide in the mutation/query",
default: []
],
relay_id_translations: [
type: :keyword_list,
doc: """
Expand All @@ -69,7 +74,7 @@ defmodule AshGraphql.Resource do

defmodule Action do
@moduledoc "Represents a configured generic action"
defstruct [:type, :name, :action, :relay_id_translations]
defstruct [:type, :name, :action, :relay_id_translations, hide_inputs: []]
end

@action %Spark.Dsl.Entity{
Expand Down Expand Up @@ -525,7 +530,8 @@ defmodule AshGraphql.Resource do
raise "No such action #{query.action} on #{resource}"

%Absinthe.Blueprint.Schema.FieldDefinition{
arguments: args(query.type, resource, query_action, schema, query.identity),
arguments:
args(query.type, resource, query_action, schema, query.identity, query.hide_inputs),
identifier: query.name,
middleware:
action_middleware ++
Expand Down Expand Up @@ -629,7 +635,8 @@ defmodule AshGraphql.Resource do
resource,
schema,
action,
mutation.type
mutation.type,
mutation.hide_inputs
) do
[] ->
[]
Expand Down Expand Up @@ -680,7 +687,8 @@ defmodule AshGraphql.Resource do
resource,
schema,
action,
mutation.type
mutation.type,
mutation.hide_inputs
) do
[] ->
mutation_args(mutation, resource, schema)
Expand Down Expand Up @@ -896,7 +904,8 @@ defmodule AshGraphql.Resource do
resource,
schema,
mutation.action,
mutation.type
mutation.type,
mutation.hide_inputs
) do
[] ->
[result] ++ List.wrap(metadata_object_type)
Expand Down Expand Up @@ -930,8 +939,11 @@ defmodule AshGraphql.Resource do
# sobelow_skip ["DOS.StringToAtom"]

defp metadata_field(resource, mutation, schema) do
IO.inspect(mutation.action)

Check warning on line 942 in lib/resource/resource.ex

View workflow job for this annotation

GitHub Actions / ash-ci / mix credo --strict

There should be no calls to IO.inspect/1.

metadata_fields =
Map.get(mutation.action, :metadata, [])
|> IO.inspect()

Check warning on line 946 in lib/resource/resource.ex

View workflow job for this annotation

GitHub Actions / ash-ci / mix credo --strict

There should be no calls to IO.inspect/1.
|> Enum.map(fn metadata ->
field_type =
metadata.type
Expand Down Expand Up @@ -1029,7 +1041,7 @@ defmodule AshGraphql.Resource do
}
end

defp mutation_fields(resource, schema, action, type) do
defp mutation_fields(resource, schema, action, type, hide_inputs \\ []) do
field_names = AshGraphql.Resource.Info.field_names(resource)
argument_names = AshGraphql.Resource.Info.argument_names(resource)

Expand All @@ -1046,7 +1058,8 @@ defmodule AshGraphql.Resource do
|> Ash.Resource.Info.public_attributes()
|> Enum.filter(fn attribute ->
AshGraphql.Resource.Info.show_field?(resource, attribute.name) &&
(is_nil(action.accept) || attribute.name in action.accept) && attribute.writable?
(is_nil(action.accept) || attribute.name in action.accept) && attribute.writable? &&
attribute.name not in hide_inputs
end)
|> Enum.map(fn attribute ->
allow_nil? =
Expand Down Expand Up @@ -1332,14 +1345,14 @@ defmodule AshGraphql.Resource do
end)
end

defp args(action_type, resource, action, schema, identity \\ nil)
defp args(action_type, resource, action, schema, identity \\ nil, hide_inputs \\ [])

defp args(:get, resource, action, schema, nil) do
defp args(:get, resource, action, schema, nil, hide_inputs) do
get_fields(resource) ++
read_args(resource, action, schema)
read_args(resource, action, schema, hide_inputs)
end

defp args(:get, resource, action, schema, identity) do
defp args(:get, resource, action, schema, identity, hide_inputs) do
if identity do
resource
|> Ash.Resource.Info.identities()
Expand All @@ -1361,10 +1374,10 @@ defmodule AshGraphql.Resource do
else
[]
end
|> Enum.concat(read_args(resource, action, schema))
|> Enum.concat(read_args(resource, action, schema, hide_inputs))
end

defp args(:read_one, resource, action, schema, _) do
defp args(:read_one, resource, action, schema, _, hide_inputs) do
args =
if AshGraphql.Resource.Info.derive_filter?(resource) do
case resource_filter_fields(resource, schema) do
Expand All @@ -1386,10 +1399,10 @@ defmodule AshGraphql.Resource do
[]
end

args ++ read_args(resource, action, schema)
args ++ read_args(resource, action, schema, hide_inputs)
end

defp args(:list, resource, action, schema, _) do
defp args(:list, resource, action, schema, _, hide_inputs) do
args =
if AshGraphql.Resource.Info.derive_filter?(resource) do
case resource_filter_fields(resource, schema) do
Expand Down Expand Up @@ -1435,11 +1448,11 @@ defmodule AshGraphql.Resource do
args
end

args ++ pagination_args(action) ++ read_args(resource, action, schema)
args ++ pagination_args(action) ++ read_args(resource, action, schema, hide_inputs)
end

defp args(:list_related, resource, action, schema, identity) do
args(:list, resource, action, schema, identity) ++
defp args(:list_related, resource, action, schema, identity, hide_inputs) do
args(:list, resource, action, schema, identity, hide_inputs) ++
[
%Absinthe.Blueprint.Schema.InputValueDefinition{
name: "limit",
Expand All @@ -1458,13 +1471,13 @@ defmodule AshGraphql.Resource do
]
end

defp args(:one_related, resource, action, schema, _identity) do
read_args(resource, action, schema)
defp args(:one_related, resource, action, schema, _identity, hide_inputs) do
read_args(resource, action, schema, hide_inputs)
end

defp read_args(resource, action, schema) do
defp read_args(resource, action, schema, hide_inputs) do
action.arguments
|> Enum.reject(& &1.private?)
|> Enum.reject(&(&1.private? || &1.name in hide_inputs))
|> Enum.map(fn argument ->
type =
argument.type
Expand Down
4 changes: 4 additions & 0 deletions test/support/resources/post.ex
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,10 @@ defmodule AshGraphql.Test.Post do
update :update_best_post, :update, read_action: :best_post, identity: false
update :update_best_post_arg, :update, read_action: :best_post_arg, identity: false

update :update_post_with_hidden_input, :update do
hide_inputs([:score])
end

destroy :archive_post, :archive
destroy :delete_post, :destroy
destroy :delete_best_post, :destroy, read_action: :best_post, identity: false
Expand Down
39 changes: 39 additions & 0 deletions test/update_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -280,4 +280,43 @@ defmodule AshGraphql.UpdateTest do

assert message =~ "confirmation did not match value"
end

test "referencing a hidden input is not allowed" do
post = AshGraphql.Test.Api.create!(Ash.Changeset.new(AshGraphql.Test.Post, text: "foobar"))

resp =
"""
mutation UpdatePostWithHiddenInput($id: ID!, $input: UpdatePostWithHiddenInputInput) {
updatePostWithHiddenInput(id: $id, input: $input) {
result{
text
}
errors{
message
}
}
}
"""
|> Absinthe.run(AshGraphql.Test.Schema,
variables: %{
"id" => post.id,
"input" => %{
"score" => 10
}
}
)

assert {
:ok,
%{
errors: [
%{
message:
"Argument \"input\" has invalid value $input.\nIn field \"score\": Unknown field."
}
]
}
} =
resp
end
end

0 comments on commit 90dc208

Please sign in to comment.