Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions documentation/dsls/DSL-AshJsonApi.Resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ end
| [`paginated_includes`](#json_api-paginated_includes){: #json_api-paginated_includes } | `list(atom \| list(atom))` | `[]` | A list of relationship paths that can be paginated when included via the `included_page` query parameter. Each entry can be either an atom (for top-level relationships) or a list of atoms (for nested paths). |
| [`include_nil_values?`](#json_api-include_nil_values?){: #json_api-include_nil_values? } | `any` | | Whether or not to include properties for values that are nil in the JSON output |
| [`default_fields`](#json_api-default_fields){: #json_api-default_fields } | `list(atom)` | | The fields to include in the object if the `fields` query parameter does not specify. Defaults to all public |
| [`hide_fields`](#json_api-hide_fields){: #json_api-hide_fields } | `list(atom)` | `[]` | A list of fields to hide from generated API specifications and JSON:API responses. Applies to attributes, relationships, calculations, and aggregates. |
| [`show_fields`](#json_api-show_fields){: #json_api-show_fields } | `list(atom)` | | A list of fields to show in generated API specifications and JSON:API responses. If not specified, all public fields are shown except those in `hide_fields`. |
| [`hide_fields`](#json_api-hide_fields){: #json_api-hide_fields } | `list(atom)` | `[]` | A list of fields to hide from generated API specifications and JSON:API responses. Applies to attributes, relationships, calculations, and aggregates. Takes precedence over `show_fields`. |
| [`show_fields`](#json_api-show_fields){: #json_api-show_fields } | `list(atom)` | | A list of fields to show in generated API specifications and JSON:API responses. If not specified, all public fields are shown except those in `hide_fields`. Fields in `hide_fields` are hidden even if listed here. |
| [`derive_sort?`](#json_api-derive_sort?){: #json_api-derive_sort? } | `boolean` | `true` | Whether or not to derive a sort parameter based on the sortable fields of the resource |
| [`derive_filter?`](#json_api-derive_filter?){: #json_api-derive_filter? } | `boolean` | `true` | Whether or not to derive a filter parameter based on the sortable fields of the resource |
| [`relationship_meta_in`](#json_api-relationship_meta_in){: #json_api-relationship_meta_in } | `keyword` | `[]` | Configures how incoming JSON:API `meta` keys on relationship resource identifiers map to join resource attributes for many_to_many relationship writes. Use together with `relationship_meta_out` for reads. Each relationship you want to support must declare both mappings explicitly. |
Expand Down
2 changes: 1 addition & 1 deletion lib/ash_json_api/resource/info.ex
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ defmodule AshJsonApi.Resource.Info do
Extension.get_opt(resource, [:json_api], :hide_fields, [], true)
end

@doc "Fields to show in generated API specifications and JSON:API responses"
@doc "Fields to show in generated API specifications and JSON:API responses. `hide_fields` takes precedence."
def show_fields(resource) do
Extension.get_opt(resource, [:json_api], :show_fields, nil, true)
end
Expand Down
4 changes: 2 additions & 2 deletions lib/ash_json_api/resource/resource.ex
Original file line number Diff line number Diff line change
Expand Up @@ -561,12 +561,12 @@ defmodule AshJsonApi.Resource do
type: {:list, :atom},
default: [],
doc:
"A list of fields to hide from generated API specifications and JSON:API responses. Applies to attributes, relationships, calculations, and aggregates."
"A list of fields to hide from generated API specifications and JSON:API responses. Applies to attributes, relationships, calculations, and aggregates. Takes precedence over `show_fields`."
],
show_fields: [
type: {:list, :atom},
doc:
"A list of fields to show in generated API specifications and JSON:API responses. If not specified, all public fields are shown except those in `hide_fields`."
"A list of fields to show in generated API specifications and JSON:API responses. If not specified, all public fields are shown except those in `hide_fields`. Fields in `hide_fields` are hidden even if listed here."
],
derive_sort?: [
type: :boolean,
Expand Down
23 changes: 0 additions & 23 deletions lib/ash_json_api/resource/verifiers/verify_field_references.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ defmodule AshJsonApi.Resource.Verifiers.VerifyFieldReferences do

validate_fields!(resource, :show_fields, show_fields, public_fields)
validate_fields!(resource, :hide_fields, hide_fields, public_fields)
validate_show_hide_overlap!(resource, show_fields, hide_fields)

:ok
end
Expand All @@ -43,26 +42,4 @@ defmodule AshJsonApi.Resource.Verifiers.VerifyFieldReferences do
end
end)
end

defp validate_show_hide_overlap!(_resource, nil, _hide_fields), do: :ok

defp validate_show_hide_overlap!(resource, show_fields, hide_fields) do
overlap =
show_fields
|> MapSet.new()
|> MapSet.intersection(MapSet.new(hide_fields || []))
|> MapSet.to_list()
|> Enum.sort()

unless Enum.empty?(overlap) do
raise Spark.Error.DslError,
module: resource,
path: [:json_api],
message: """
Fields cannot appear in both `show_fields` and `hide_fields`.

Conflicting fields: #{inspect(overlap)}
"""
end
end
end
19 changes: 17 additions & 2 deletions test/acceptance/field_visibility_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ defmodule Test.Acceptance.FieldVisibilityTest do
type("visibility-show-post")
includes([:visible_author, :extra_author])
default_fields([:title, :summary, :secret_calc])
show_fields([:title, :visible_author])
hide_fields([:summary])
show_fields([:title, :summary, :visible_author])

routes do
base("/visibility_show_posts")
Expand Down Expand Up @@ -312,7 +313,9 @@ defmodule Test.Acceptance.FieldVisibilityTest do
assert response.resp_body["data"]["type"] == "visibility-author"
end

test "show_fields only exposes allowlisted fields", %{show_only_post: post} do
test "show_fields only exposes allowlisted fields and hide_fields wins", %{
show_only_post: post
} do
response =
Domain
|> get("/visibility_show_posts/#{post.id}", status: 200)
Expand All @@ -329,6 +332,18 @@ defmodule Test.Acceptance.FieldVisibilityTest do
end

test "show_fields rejects non-allowlisted sparse fieldsets", %{show_only_post: post} do
Domain
|> get("/visibility_show_posts/#{post.id}?fields[visibility-show-post]=secret_calc",
status: 400
)
|> assert_has_error(%{
"code" => "invalid_field"
})
end

test "hide_fields rejects sparse fieldsets even when the field is in show_fields", %{
show_only_post: post
} do
Domain
|> get("/visibility_show_posts/#{post.id}?fields[visibility-show-post]=summary", status: 400)
|> assert_has_error(%{
Expand Down
1 change: 1 addition & 0 deletions test/acceptance/json_schema_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ defmodule Test.Acceptance.JsonSchemaTest do
type("hidden-json-post")
default_fields([:name, :secret, :secret_calc])
hide_fields([:secret, :secret_calc, :hidden_author])
show_fields([:name, :secret, :secret_calc, :visible_author, :hidden_author])

routes do
base("/hidden_json_posts")
Expand Down
1 change: 1 addition & 0 deletions test/acceptance/open_api_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ defmodule Test.Acceptance.OpenApiTest do
paginated_includes([:visible_author, :hidden_author])
default_fields([:name, :secret, :secret_calc])
hide_fields([:secret, :secret_calc, :hidden_author])
show_fields([:name, :secret, :secret_calc, :visible_author, :hidden_author])

routes do
base("/hidden_spec_posts")
Expand Down