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