Skip to content

Commit 7de53cb

Browse files
authored
Validate keys given to operation/2 macro (#675)
1 parent 9967e2f commit 7de53cb

File tree

2 files changed

+75
-4
lines changed

2 files changed

+75
-4
lines changed

lib/open_api_spex/controller_specs.ex

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -366,15 +366,50 @@ defmodule OpenApiSpex.ControllerSpecs do
366366
end
367367
end
368368

369+
@operation_permitted_keys [
370+
:callbacks,
371+
:description,
372+
:deprecated,
373+
:external_docs,
374+
:operation_id,
375+
:parameters,
376+
:request_body,
377+
:responses,
378+
:security,
379+
:summary,
380+
:tags
381+
]
382+
369383
@doc """
370384
Define an Operation for a controller action.
371385
372386
See `OpenApiSpex.ControllerSpecs` for usage and examples.
387+
388+
Permitted operation keys:
389+
390+
#{Enum.map_join(@operation_permitted_keys, "\n", fn k -> "* `#{k}`" end)}
391+
* keys prefixed by `x-` for extensions
373392
"""
374393
def operation_spec(_module, _action, nil = _spec), do: nil
375394
def operation_spec(_module, _action, false = _spec), do: nil
376395

377396
def operation_spec(module, action, spec) do
397+
validation_result =
398+
spec
399+
|> Enum.reject(fn {key, _val} -> extension_key?(key) end)
400+
|> Keyword.validate(@operation_permitted_keys)
401+
402+
case validation_result do
403+
{:ok, _spec} ->
404+
:ok
405+
406+
{:error, unknown_keys} ->
407+
raise ArgumentError,
408+
"Unknown keys given to operation/2: #{inspect(unknown_keys)}. " <>
409+
"Allowed keys are: #{inspect(@operation_permitted_keys)}, " <>
410+
"and keys starting with 'x-'."
411+
end
412+
378413
spec = Map.new(spec)
379414
shared_tags = Module.get_attribute(module, :shared_tags, []) |> List.flatten()
380415

@@ -386,9 +421,7 @@ defmodule OpenApiSpex.ControllerSpecs do
386421

387422
extensions =
388423
spec
389-
|> Enum.filter(fn {key, _val} ->
390-
is_atom(key) && String.starts_with?(to_string(key), "x-")
391-
end)
424+
|> Enum.filter(fn {key, _val} -> extension_key?(key) end)
392425
|> Map.new(fn {key, value} -> {to_string(key), value} end)
393426

394427
%Operation{
@@ -406,4 +439,8 @@ defmodule OpenApiSpex.ControllerSpecs do
406439
extensions: extensions
407440
}
408441
end
442+
443+
defp extension_key?(key) do
444+
is_atom(key) && String.starts_with?(to_string(key), "x-")
445+
end
409446
end

test/controller_specs_test.exs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ defmodule OpenApiSpex.ControllerSpecsTest do
77
alias OpenApiSpexTest.DslController
88
alias OpenApiSpexTest.DslControllerOperationStructs
99

10-
describe "operation/1" do
10+
describe "operation/2" do
1111
test "supports :parameters" do
1212
assert %OpenApiSpex.Operation{
1313
responses: %{},
@@ -157,5 +157,39 @@ defmodule OpenApiSpex.ControllerSpecsTest do
157157
assert %OpenApiSpex.Operation{extensions: %{"x-foo" => "bar"}} =
158158
DslController.open_api_operation(:index)
159159
end
160+
161+
test "raises when unknown key is provided" do
162+
msg =
163+
"Unknown keys given to operation/2: [:unknown]. Allowed keys are: " <>
164+
"[:callbacks, :description, :deprecated, :external_docs, :operation_id, :parameters, " <>
165+
":request_body, :responses, :security, :summary, :tags], and keys starting with 'x-'."
166+
167+
assert_raise ArgumentError, msg, fn ->
168+
Code.eval_string("""
169+
defmodule TestController do
170+
use OpenApiSpex.ControllerSpecs
171+
172+
operation :index,
173+
summary: "Users index",
174+
parameters: [
175+
username: [
176+
in: :query,
177+
description: "Filter by username",
178+
type: :string
179+
]
180+
],
181+
responses: [
182+
ok: {"Users index response", "application/json", UsersIndexResponse}
183+
],
184+
unknown: "value",
185+
"x-foo": "bar"
186+
187+
def index(conn, _) do
188+
json(conn, [])
189+
end
190+
end
191+
""")
192+
end
193+
end
160194
end
161195
end

0 commit comments

Comments
 (0)