Skip to content

Commit 437cd32

Browse files
barnabasJclaude
andauthored
fix: Apply field_names mapping to OpenAPI filter schema (#444)
The field_names DSL was respected at runtime for filter parsing (via filter_ref_transformer) but the generated OpenAPI spec still advertised filter properties and component schema ids under the raw internal field name. A resource with field_names(title: :subject) accepted filter[subject]=... at runtime but the spec advertised filter[title]=... Route the filter property keys (attribute/calculation/aggregate) and the "-filter-<name>" component schema id through AshJsonApi.Resource.Info.field_to_json_key/2 so the spec matches runtime behavior. Relationship keys and boolean combinators are unaffected as they are not subject to field_names mapping. This also changes the filter property key type from atom to string, consistent with how the rest of the spec represents JSON:API keys. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 231baff commit 437cd32

3 files changed

Lines changed: 48 additions & 18 deletions

File tree

lib/ash_json_api/json_schema/open_api.ex

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3039,7 +3039,8 @@ if Code.ensure_loaded?(OpenApiSpex) do
30393039
end
30403040

30413041
defp attribute_filter_field_type(resource, attribute) do
3042-
AshJsonApi.Resource.Info.type(resource) <> "-filter-" <> to_string(attribute.name)
3042+
AshJsonApi.Resource.Info.type(resource) <>
3043+
"-filter-" <> AshJsonApi.Resource.Info.field_to_json_key(resource, attribute.name)
30433044
end
30443045

30453046
defp resource_filter_fields(resource, domains) do
@@ -3093,7 +3094,7 @@ if Code.ensure_loaded?(OpenApiSpex) do
30933094
|> filter_shown_fields(resource)
30943095
|> Enum.filter(&filterable?(&1, resource))
30953096
|> Enum.map(fn calculation ->
3096-
{calculation.name,
3097+
{AshJsonApi.Resource.Info.field_to_json_key(resource, calculation.name),
30973098
%Reference{
30983099
"$ref": "#/components/schemas/#{attribute_filter_field_type(resource, calculation)}"
30993100
}}
@@ -3109,7 +3110,7 @@ if Code.ensure_loaded?(OpenApiSpex) do
31093110
|> filter_shown_fields(resource)
31103111
|> Enum.filter(&filterable?(&1, resource))
31113112
|> Enum.map(fn attribute ->
3112-
{attribute.name,
3113+
{AshJsonApi.Resource.Info.field_to_json_key(resource, attribute.name),
31133114
%Reference{
31143115
"$ref": "#/components/schemas/#{attribute_filter_field_type(resource, attribute)}"
31153116
}}
@@ -3123,7 +3124,7 @@ if Code.ensure_loaded?(OpenApiSpex) do
31233124
|> filter_shown_fields(resource)
31243125
|> Enum.filter(&filterable?(&1, resource))
31253126
|> Enum.map(fn aggregate ->
3126-
{aggregate.name,
3127+
{AshJsonApi.Resource.Info.field_to_json_key(resource, aggregate.name),
31273128
%Reference{
31283129
"$ref": "#/components/schemas/#{attribute_filter_field_type(resource, aggregate)}"
31293130
}}

test/acceptance/field_names_test.exs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,35 @@ defmodule Test.Acceptance.FieldNamesTest do
737737
end
738738
end
739739

740+
# ─── OpenAPI filter schema ────────────────────────────────────────────────────
741+
742+
describe "field_names – OpenAPI filter schema" do
743+
test "filter properties use the renamed attribute keys" do
744+
api_spec =
745+
AshJsonApi.Controllers.OpenApi.spec(%{private: %{}}, domains: [Domain])
746+
747+
filter_schema = api_spec.components.schemas["post-filter"]
748+
749+
assert Map.has_key?(filter_schema.properties, "subject")
750+
assert Map.has_key?(filter_schema.properties, "content")
751+
refute Map.has_key?(filter_schema.properties, "title")
752+
refute Map.has_key?(filter_schema.properties, "body")
753+
end
754+
755+
test "filter property $refs and component schema ids use the renamed keys" do
756+
api_spec =
757+
AshJsonApi.Controllers.OpenApi.spec(%{private: %{}}, domains: [Domain])
758+
759+
filter_schema = api_spec.components.schemas["post-filter"]
760+
761+
assert filter_schema.properties["subject"]."$ref" ==
762+
"#/components/schemas/post-filter-subject"
763+
764+
assert Map.has_key?(api_spec.components.schemas, "post-filter-subject")
765+
refute Map.has_key?(api_spec.components.schemas, "post-filter-title")
766+
end
767+
end
768+
740769
# ─── Sparse fieldsets ─────────────────────────────────────────────────────────
741770

742771
describe "field_names – sparse fieldsets (fields[])" do

test/acceptance/open_api_test.exs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -467,10 +467,10 @@ defmodule Test.Acceptance.OpenApiTest do
467467
refute Map.has_key?(relationships, :hidden_author)
468468

469469
filter_schema = api_spec.components.schemas["hidden-spec-post-filter"]
470-
assert Map.has_key?(filter_schema.properties, :name)
470+
assert Map.has_key?(filter_schema.properties, "name")
471471
assert Map.has_key?(filter_schema.properties, :visible_author)
472-
refute Map.has_key?(filter_schema.properties, :secret)
473-
refute Map.has_key?(filter_schema.properties, :secret_calc)
472+
refute Map.has_key?(filter_schema.properties, "secret")
473+
refute Map.has_key?(filter_schema.properties, "secret_calc")
474474
refute Map.has_key?(filter_schema.properties, :hidden_author)
475475
refute Map.has_key?(api_spec.components.schemas, "hidden-spec-post-filter-secret")
476476
refute Map.has_key?(api_spec.components.schemas, "hidden-spec-post-filter-secret_calc")
@@ -694,35 +694,35 @@ defmodule Test.Acceptance.OpenApiTest do
694694
type: :deepObject,
695695
description: "Filters the query to results matching the given filter object",
696696
properties: %{
697-
author: %Reference{"$ref": "#/components/schemas/author-filter"},
698-
author_id: %Reference{
697+
:author => %Reference{"$ref": "#/components/schemas/author-filter"},
698+
"author_id" => %Reference{
699699
"$ref": "#/components/schemas/post-filter-author_id"
700700
},
701-
count_of_tags: %Reference{
701+
"count_of_tags" => %Reference{
702702
"$ref": "#/components/schemas/post-filter-count_of_tags"
703703
},
704-
email: %Reference{"$ref": "#/components/schemas/post-filter-email"},
705-
hidden: %Reference{"$ref": "#/components/schemas/post-filter-hidden"},
706-
id: %Reference{"$ref": "#/components/schemas/post-filter-id"},
707-
name: %Reference{"$ref": "#/components/schemas/post-filter-name"},
708-
and: %Schema{
704+
"email" => %Reference{"$ref": "#/components/schemas/post-filter-email"},
705+
"hidden" => %Reference{"$ref": "#/components/schemas/post-filter-hidden"},
706+
"id" => %Reference{"$ref": "#/components/schemas/post-filter-id"},
707+
"name" => %Reference{"$ref": "#/components/schemas/post-filter-name"},
708+
:and => %Schema{
709709
type: :array,
710710
items: %Reference{
711711
"$ref": "#/components/schemas/post-filter"
712712
},
713713
uniqueItems: true
714714
},
715-
or: %Schema{
715+
:or => %Schema{
716716
type: :array,
717717
items: %Reference{
718718
"$ref": "#/components/schemas/post-filter"
719719
},
720720
uniqueItems: true
721721
},
722-
name_twice: %Reference{
722+
"name_twice" => %Reference{
723723
"$ref": "#/components/schemas/post-filter-name_twice"
724724
},
725-
not: %Reference{"$ref": "#/components/schemas/post-filter"}
725+
:not => %Reference{"$ref": "#/components/schemas/post-filter"}
726726
},
727727
additionalProperties: false,
728728
example: ""

0 commit comments

Comments
 (0)