Apply suggestion to lib/pleroma/web/api_spec/cast_and_validate.ex
[akkoma] / lib / pleroma / web / api_spec / cast_and_validate.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2019-2020 Moxley Stratton, Mike Buhot <https://github.com/open-api-spex/open_api_spex>, MPL-2.0
3 # Copyright © 2020 Pleroma Authors <https://pleroma.social/>
4 # SPDX-License-Identifier: AGPL-3.0-only
5
6 defmodule Pleroma.Web.ApiSpec.CastAndValidate do
7 @moduledoc """
8 This plug is based on [`OpenApiSpex.Plug.CastAndValidate`]
9 (https://github.com/open-api-spex/open_api_spex/blob/master/lib/open_api_spex/plug/cast_and_validate.ex).
10 The main difference is ignoring unexpected query params
11 instead of throwing an error. Also, the default rendering
12 error module is `Pleroma.Web.ApiSpec.RenderError`.
13 """
14
15 @behaviour Plug
16
17 alias Plug.Conn
18
19 @impl Plug
20 def init(opts) do
21 opts
22 |> Map.new()
23 |> Map.put_new(:render_error, Pleroma.Web.ApiSpec.RenderError)
24 end
25
26 @impl Plug
27 def call(%{private: %{open_api_spex: private_data}} = conn, %{
28 operation_id: operation_id,
29 render_error: render_error
30 }) do
31 spec = private_data.spec
32 operation = private_data.operation_lookup[operation_id]
33
34 content_type =
35 case Conn.get_req_header(conn, "content-type") do
36 [header_value | _] ->
37 header_value
38 |> String.split(";")
39 |> List.first()
40
41 _ ->
42 nil
43 end
44
45 private_data = Map.put(private_data, :operation_id, operation_id)
46 conn = Conn.put_private(conn, :open_api_spex, private_data)
47
48 case cast_and_validate(spec, operation, conn, content_type) do
49 {:ok, conn} ->
50 conn
51
52 {:error, reason} ->
53 opts = render_error.init(reason)
54
55 conn
56 |> render_error.call(opts)
57 |> Plug.Conn.halt()
58 end
59 end
60
61 def call(
62 %{
63 private: %{
64 phoenix_controller: controller,
65 phoenix_action: action,
66 open_api_spex: private_data
67 }
68 } = conn,
69 opts
70 ) do
71 operation =
72 case private_data.operation_lookup[{controller, action}] do
73 nil ->
74 operation_id = controller.open_api_operation(action).operationId
75 operation = private_data.operation_lookup[operation_id]
76
77 operation_lookup =
78 private_data.operation_lookup
79 |> Map.put({controller, action}, operation)
80
81 OpenApiSpex.Plug.Cache.adapter().put(
82 private_data.spec_module,
83 {private_data.spec, operation_lookup}
84 )
85
86 operation
87
88 operation ->
89 operation
90 end
91
92 if operation.operationId do
93 call(conn, Map.put(opts, :operation_id, operation.operationId))
94 else
95 raise "operationId was not found in action API spec"
96 end
97 end
98
99 def call(conn, opts), do: OpenApiSpex.Plug.CastAndValidate.call(conn, opts)
100
101 defp cast_and_validate(spec, operation, conn, content_type) do
102 case OpenApiSpex.cast_and_validate(spec, operation, conn, content_type) do
103 {:ok, conn} ->
104 {:ok, conn}
105
106 # Remove unexpected query params and cast/validate again
107 {:error, errors} ->
108 query_params =
109 Enum.reduce(errors, conn.query_params, fn
110 %{reason: :unexpected_field, name: name, path: [name]}, params ->
111 Map.delete(params, name)
112
113 _, params ->
114 params
115 end)
116
117 conn = %Conn{conn | query_params: query_params}
118 OpenApiSpex.cast_and_validate(spec, operation, conn, content_type)
119 end
120 end
121 end