Skip to content

Commit 6767710

Browse files
committed
improvement: more consistency for route & query params
this also solves cases where route params did not properly inherit the corresponding types of their inputs closes #338
1 parent e148603 commit 6767710

File tree

2 files changed

+116
-56
lines changed

2 files changed

+116
-56
lines changed

lib/ash_json_api/json_schema/open_api.ex

Lines changed: 88 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1286,7 +1286,7 @@ if Code.ensure_loaded?(OpenApiSpex) do
12861286
description: action_description(action, route, resource),
12871287
operationId: route.name,
12881288
tags: [to_string(AshJsonApi.Resource.Info.type(resource))],
1289-
parameters: path_parameters(path_params, action) ++ query_parameters(route, resource),
1289+
parameters: parameters(route, resource, path_params),
12901290
responses: %{
12911291
:default => %Reference{
12921292
"$ref": "#/components/responses/errors"
@@ -1309,33 +1309,12 @@ if Code.ensure_loaded?(OpenApiSpex) do
13091309
end
13101310
end
13111311

1312-
@spec path_parameters(path_params :: [String.t()], action :: Actions.action()) ::
1313-
[Parameter.t()]
1314-
defp path_parameters(path_params, action) do
1315-
Enum.map(path_params, fn param ->
1316-
description =
1317-
action.arguments
1318-
|> Enum.find(&(to_string(&1.name) == param))
1319-
|> case do
1320-
%{description: description} when is_binary(description) -> description
1321-
_ -> nil
1322-
end
1323-
1324-
%Parameter{
1325-
name: param,
1326-
description: description,
1327-
in: :path,
1328-
required: true,
1329-
schema: %Schema{type: :string}
1330-
}
1331-
end)
1332-
end
1333-
1334-
@spec query_parameters(
1312+
@spec parameters(
13351313
Route.t(),
1336-
resource :: module
1314+
resource :: module,
1315+
route_params :: [String.t()]
13371316
) :: [Parameter.t()]
1338-
defp query_parameters(%{type: :index} = route, resource) do
1317+
defp parameters(%{type: :index} = route, resource, route_params) do
13391318
Enum.filter(
13401319
[
13411320
filter_parameter(resource, route),
@@ -1346,13 +1325,14 @@ if Code.ensure_loaded?(OpenApiSpex) do
13461325
],
13471326
& &1
13481327
)
1349-
|> Enum.concat(read_argument_parameters(route, resource))
1328+
|> Enum.concat(read_argument_parameters(route, resource, route_params))
13501329
|> Enum.map(fn param ->
13511330
Map.update!(param, :name, &to_string/1)
13521331
end)
1332+
|> apply_route_params(route_params)
13531333
end
13541334

1355-
defp query_parameters(%{type: type}, _resource)
1335+
defp parameters(%{type: type}, _resource, _route_params)
13561336
when type in [
13571337
:post_to_relationship,
13581338
:patch_relationship,
@@ -1361,19 +1341,21 @@ if Code.ensure_loaded?(OpenApiSpex) do
13611341
[]
13621342
end
13631343

1364-
defp query_parameters(%{type: type} = route, resource) when type in [:get, :related] do
1344+
defp parameters(%{type: type} = route, resource, route_params)
1345+
when type in [:get, :related] do
13651346
[include_parameter(resource), fields_parameter(resource)]
13661347
|> Enum.filter(& &1)
1367-
|> Enum.concat(read_argument_parameters(route, resource))
1348+
|> Enum.concat(read_argument_parameters(route, resource, route_params))
13681349
|> Enum.reverse()
13691350
|> Enum.map(fn param ->
13701351
Map.update!(param, :name, &to_string/1)
13711352
end)
13721353
|> Enum.uniq_by(& &1.name)
13731354
|> Enum.reverse()
1355+
|> apply_route_params(route_params)
13741356
end
13751357

1376-
defp query_parameters(route, resource) do
1358+
defp parameters(route, resource, route_params) do
13771359
action = Ash.Resource.Info.action(resource, route.action)
13781360

13791361
query_params =
@@ -1384,28 +1366,52 @@ if Code.ensure_loaded?(OpenApiSpex) do
13841366
if argument do
13851367
argument
13861368
else
1387-
if name in action.accept do
1369+
if name in Map.get(action, :accept, []) do
13881370
Ash.Resource.Info.attribute(resource, name)
13891371
else
13901372
nil
13911373
end
13921374
end
13931375
end)
1376+
|> Enum.concat(
1377+
Enum.map(route_params, fn route_param ->
1378+
case Enum.find(action.arguments, &(to_string(&1.name) == route_param)) do
1379+
nil ->
1380+
if Enum.any?(Map.get(action, :accept, []), &(to_string(&1) == route_param)) do
1381+
Ash.Resource.Info.attribute(resource, route_param)
1382+
else
1383+
nil
1384+
end
1385+
1386+
argument ->
1387+
argument
1388+
end
1389+
end)
1390+
)
13941391
|> Enum.filter(& &1)
13951392
|> Enum.map(fn argument_or_attribute ->
13961393
schema = resource_write_attribute_type(argument_or_attribute, resource, action.type)
13971394

1395+
location =
1396+
if to_string(argument_or_attribute.name) in route_params do
1397+
:path
1398+
else
1399+
:query
1400+
end
1401+
1402+
style =
1403+
if schema.type == :object && location == :query do
1404+
:deepObject
1405+
else
1406+
:form
1407+
end
1408+
13981409
%Parameter{
13991410
name: to_string(argument_or_attribute.name),
1400-
in: :query,
1401-
description:
1402-
argument_or_attribute.description || to_string(argument_or_attribute.name),
1403-
required: !argument_or_attribute.allow_nil?,
1404-
style:
1405-
case schema.type do
1406-
:object -> :deepObject
1407-
_ -> :form
1408-
end,
1411+
in: location,
1412+
description: argument_or_attribute.description,
1413+
required: location == :path || !argument_or_attribute.allow_nil?,
1414+
style: style,
14091415
schema: schema
14101416
}
14111417
end)
@@ -1423,6 +1429,26 @@ if Code.ensure_loaded?(OpenApiSpex) do
14231429
end)
14241430
|> Enum.uniq_by(& &1.name)
14251431
|> Enum.reverse()
1432+
|> apply_route_params(route_params)
1433+
end
1434+
1435+
defp apply_route_params(params, route_params) do
1436+
param_names = Enum.map(params, & &1.name)
1437+
1438+
route_params_to_add =
1439+
route_params
1440+
|> Enum.reject(&(&1 in param_names))
1441+
|> Enum.map(fn name ->
1442+
%Parameter{
1443+
name: name,
1444+
in: :path,
1445+
required: true,
1446+
style: :form,
1447+
schema: %Schema{type: :string}
1448+
}
1449+
end)
1450+
1451+
route_params_to_add ++ Enum.sort_by(params, &(&1.in == :query))
14261452
end
14271453

14281454
@spec filter_parameter(resource :: module, route :: AshJsonApi.Resource.Route.t()) ::
@@ -1636,26 +1662,36 @@ if Code.ensure_loaded?(OpenApiSpex) do
16361662
}
16371663
end
16381664

1639-
@spec read_argument_parameters(Route.t(), resource :: module) :: [Parameter.t()]
1640-
defp read_argument_parameters(route, resource) do
1665+
@spec read_argument_parameters(Route.t(), resource :: module, route_params :: [String.t()]) ::
1666+
[Parameter.t()]
1667+
defp read_argument_parameters(route, resource, route_params) do
16411668
action = Ash.Resource.Info.action(resource, route.action)
16421669

16431670
action.arguments
16441671
|> Enum.filter(& &1.public?)
1645-
|> without_path_arguments(route)
16461672
|> Enum.map(fn argument ->
16471673
schema = resource_attribute_type(argument, resource)
16481674

1675+
location =
1676+
if to_string(argument.name) in route_params do
1677+
:path
1678+
else
1679+
:query
1680+
end
1681+
1682+
style =
1683+
if schema.type == :object && location == :query do
1684+
:deepObject
1685+
else
1686+
:form
1687+
end
1688+
16491689
%Parameter{
16501690
name: argument.name,
1651-
in: :query,
1652-
description: argument.description || to_string(argument.name),
1653-
required: !argument.allow_nil?,
1654-
style:
1655-
case schema.type do
1656-
:object -> :deepObject
1657-
_ -> :form
1658-
end,
1691+
in: location,
1692+
description: argument.description,
1693+
required: location == :path || !argument.allow_nil?,
1694+
style: style,
16591695
schema: schema
16601696
}
16611697
end)

test/acceptance/open_api_test.exs

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ defmodule Test.Acceptance.OpenApiTest do
5858
route :post, "/say_hello/:to", :say_hello
5959
route :post, "/trigger_job", :trigger_job, query_params: [:job_id]
6060
route :post, "/trigger_job/:job_id", :trigger_job
61+
route :post, "/path_with_enum/:enum", :takes_enum
6162
route(:get, "returns_map", :returns_map)
6263
route(:get, "/get_foo", :get_foo)
6364
post_to_relationship :posts
@@ -94,6 +95,14 @@ defmodule Test.Acceptance.OpenApiTest do
9495
end)
9596
end
9697

98+
action :takes_enum do
99+
argument(:enum, :atom, constraints: [one_of: [:one, :two]])
100+
101+
run(fn _input, _ ->
102+
:ok
103+
end)
104+
end
105+
97106
action :returns_map, :map do
98107
constraints(fields: [a: [type: :integer], b: [type: :string, allow_nil?: false]])
99108

@@ -320,7 +329,7 @@ defmodule Test.Acceptance.OpenApiTest do
320329
end
321330

322331
test "API routes are mapped to OpenAPI Operations", %{open_api_spec: %OpenApi{} = api_spec} do
323-
assert map_size(api_spec.paths) == 12
332+
assert map_size(api_spec.paths) == 13
324333

325334
assert %{"/authors" => _, "/authors/{id}" => _, "/posts" => _, "/posts/{id}" => _} =
326335
api_spec.paths
@@ -439,7 +448,6 @@ defmodule Test.Acceptance.OpenApiTest do
439448
%Parameter{
440449
name: "job_id",
441450
in: :query,
442-
description: "job_id",
443451
required: false,
444452
schema: %Schema{type: :string},
445453
style: :form
@@ -452,8 +460,8 @@ defmodule Test.Acceptance.OpenApiTest do
452460
%Parameter{
453461
name: "job_id",
454462
in: :path,
455-
description: nil,
456463
required: true,
464+
style: :form,
457465
schema: %Schema{type: :string}
458466
}
459467
]
@@ -469,6 +477,22 @@ defmodule Test.Acceptance.OpenApiTest do
469477
}
470478
end
471479

480+
test "generic route paths have enums", %{
481+
open_api_spec: %OpenApi{} = api_spec
482+
} do
483+
assert generic_action_schema = api_spec.paths["/authors/path_with_enum/{enum}"].post
484+
485+
assert generic_action_schema.parameters == [
486+
%Parameter{
487+
name: "enum",
488+
in: :path,
489+
required: true,
490+
schema: %OpenApiSpex.Schema{enum: ["one", "two"], type: :string},
491+
style: :form
492+
}
493+
]
494+
end
495+
472496
test "API routes use `name` as operationId", %{
473497
open_api_spec: %OpenApi{} = api_spec
474498
} do
@@ -805,7 +829,7 @@ defmodule Test.Acceptance.OpenApiTest do
805829
%Parameter{} = filter = operation.parameters |> Enum.find(&(&1.name == "id"))
806830
assert filter.in == :path
807831
assert filter.required == true
808-
assert filter.style == nil
832+
assert filter.style == :form
809833
%Schema{} = schema = filter.schema
810834
assert schema.type == :string
811835
end

0 commit comments

Comments
 (0)