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 © 2017-2022 Pleroma Authors <https://pleroma.social/>
4 # SPDX-License-Identifier: AGPL-3.0-only
6 defmodule Pleroma.Web.ApiSpec.CastAndValidate do
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 instead of throwing
11 an error and a config option (`[Pleroma.Web.ApiSpec.CastAndValidate, :strict]`)
12 to disable this behavior. Also, the default rendering error module
13 is `Pleroma.Web.ApiSpec.RenderError`.
18 alias OpenApiSpex.Plug.PutApiSpec
25 |> Map.put_new(:render_error, Pleroma.Web.ApiSpec.RenderError)
30 def call(conn, %{operation_id: operation_id, render_error: render_error}) do
31 {spec, operation_lookup} = PutApiSpec.get_spec_and_operation_lookup(conn)
32 operation = operation_lookup[operation_id]
35 case Conn.get_req_header(conn, "content-type") do
45 conn = Conn.put_private(conn, :operation_id, operation_id)
47 case cast_and_validate(spec, operation, conn, content_type, strict?()) do
52 opts = render_error.init(reason)
55 |> render_error.call(opts)
63 phoenix_controller: controller,
64 phoenix_action: action,
65 open_api_spex: %{spec_module: spec_module}
70 {spec, operation_lookup} = PutApiSpec.get_spec_and_operation_lookup(conn)
73 case operation_lookup[{controller, action}] do
75 operation_id = controller.open_api_operation(action).operationId
76 operation = operation_lookup[operation_id]
78 operation_lookup = Map.put(operation_lookup, {controller, action}, operation)
80 OpenApiSpex.Plug.Cache.adapter().put(spec_module, {spec, operation_lookup})
88 if operation.operationId do
89 call(conn, Map.put(opts, :operation_id, operation.operationId))
91 raise "operationId was not found in action API spec"
95 def call(conn, opts), do: OpenApiSpex.Plug.CastAndValidate.call(conn, opts)
97 defp cast_and_validate(spec, operation, conn, content_type, true = _strict) do
98 OpenApiSpex.cast_and_validate(spec, operation, conn, content_type)
101 defp cast_and_validate(spec, operation, conn, content_type, false = _strict) do
102 case OpenApiSpex.cast_and_validate(spec, operation, conn, content_type) do
106 # Remove unexpected query params and cast/validate again
109 Enum.reduce(errors, conn.query_params, fn
110 %{reason: :unexpected_field, name: name, path: [name]}, params ->
111 Map.delete(params, name)
113 # Filter out empty params
114 %{reason: :invalid_type, path: [name_atom], value: ""}, params ->
115 Map.delete(params, to_string(name_atom))
117 %{reason: :invalid_enum, name: nil, path: path, value: value}, params ->
118 path = path |> Enum.reverse() |> tl() |> Enum.reverse() |> list_items_to_string()
119 update_in(params, path, &List.delete(&1, value))
125 conn = %Conn{conn | query_params: query_params}
126 OpenApiSpex.cast_and_validate(spec, operation, conn, content_type)
130 defp list_items_to_string(list) do
132 i when is_atom(i) -> to_string(i)
137 defp strict?, do: Pleroma.Config.get([__MODULE__, :strict], false)