Render better errors for ENUM validation
[akkoma] / lib / pleroma / web / api_spec / render_error.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.RenderError do
6 @behaviour Plug
7
8 import Plug.Conn, only: [put_status: 2]
9 import Phoenix.Controller, only: [json: 2]
10 import Pleroma.Web.Gettext
11
12 @impl Plug
13 def init(opts), do: opts
14
15 @impl Plug
16
17 def call(conn, errors) do
18 errors =
19 Enum.map(errors, fn
20 %{name: nil, reason: :invalid_enum} = err ->
21 %OpenApiSpex.Cast.Error{err | name: err.value}
22
23 %{name: nil} = err ->
24 %OpenApiSpex.Cast.Error{err | name: List.last(err.path)}
25
26 err ->
27 err
28 end)
29
30 conn
31 |> put_status(:bad_request)
32 |> json(%{
33 error: errors |> Enum.map(&message/1) |> Enum.join(" "),
34 errors: errors |> Enum.map(&render_error/1)
35 })
36 end
37
38 defp render_error(error) do
39 pointer = OpenApiSpex.path_to_string(error)
40
41 %{
42 title: "Invalid value",
43 source: %{
44 pointer: pointer
45 },
46 message: OpenApiSpex.Cast.Error.message(error)
47 }
48 end
49
50 defp message(%{reason: :invalid_schema_type, type: type, name: name}) do
51 gettext("%{name} - Invalid schema.type. Got: %{type}.",
52 name: name,
53 type: inspect(type)
54 )
55 end
56
57 defp message(%{reason: :null_value, name: name} = error) do
58 case error.type do
59 nil ->
60 gettext("%{name} - null value.", name: name)
61
62 type ->
63 gettext("%{name} - null value where %{type} expected.",
64 name: name,
65 type: type
66 )
67 end
68 end
69
70 defp message(%{reason: :all_of, meta: %{invalid_schema: invalid_schema}}) do
71 gettext(
72 "Failed to cast value as %{invalid_schema}. Value must be castable using `allOf` schemas listed.",
73 invalid_schema: invalid_schema
74 )
75 end
76
77 defp message(%{reason: :any_of, meta: %{failed_schemas: failed_schemas}}) do
78 gettext("Failed to cast value using any of: %{failed_schemas}.",
79 failed_schemas: failed_schemas
80 )
81 end
82
83 defp message(%{reason: :one_of, meta: %{failed_schemas: failed_schemas}}) do
84 gettext("Failed to cast value to one of: %{failed_schemas}.", failed_schemas: failed_schemas)
85 end
86
87 defp message(%{reason: :min_length, length: length, name: name}) do
88 gettext("%{name} - String length is smaller than minLength: %{length}.",
89 name: name,
90 length: length
91 )
92 end
93
94 defp message(%{reason: :max_length, length: length, name: name}) do
95 gettext("%{name} - String length is larger than maxLength: %{length}.",
96 name: name,
97 length: length
98 )
99 end
100
101 defp message(%{reason: :unique_items, name: name}) do
102 gettext("%{name} - Array items must be unique.", name: name)
103 end
104
105 defp message(%{reason: :min_items, length: min, value: array, name: name}) do
106 gettext("%{name} - Array length %{length} is smaller than minItems: %{min}.",
107 name: name,
108 length: length(array),
109 min: min
110 )
111 end
112
113 defp message(%{reason: :max_items, length: max, value: array, name: name}) do
114 gettext("%{name} - Array length %{length} is larger than maxItems: %{}.",
115 name: name,
116 length: length(array),
117 max: max
118 )
119 end
120
121 defp message(%{reason: :multiple_of, length: multiple, value: count, name: name}) do
122 gettext("%{name} - %{count} is not a multiple of %{multiple}.",
123 name: name,
124 count: count,
125 multiple: multiple
126 )
127 end
128
129 defp message(%{reason: :exclusive_max, length: max, value: value, name: name})
130 when value >= max do
131 gettext("%{name} - %{value} is larger than exclusive maximum %{max}.",
132 name: name,
133 value: value,
134 max: max
135 )
136 end
137
138 defp message(%{reason: :maximum, length: max, value: value, name: name})
139 when value > max do
140 gettext("%{name} - %{value} is larger than inclusive maximum %{max}.",
141 name: name,
142 value: value,
143 max: max
144 )
145 end
146
147 defp message(%{reason: :exclusive_multiple, length: min, value: value, name: name})
148 when value <= min do
149 gettext("%{name} - %{value} is smaller than exclusive minimum %{min}.",
150 name: name,
151 value: value,
152 min: min
153 )
154 end
155
156 defp message(%{reason: :minimum, length: min, value: value, name: name})
157 when value < min do
158 gettext("%{name} - %{value} is smaller than inclusive minimum %{min}.",
159 name: name,
160 value: value,
161 min: min
162 )
163 end
164
165 defp message(%{reason: :invalid_type, type: type, value: value, name: name}) do
166 gettext("%{name} - Invalid %{type}. Got: %{value}.",
167 name: name,
168 value: OpenApiSpex.TermType.type(value),
169 type: type
170 )
171 end
172
173 defp message(%{reason: :invalid_format, format: format, name: name}) do
174 gettext("%{name} - Invalid format. Expected %{format}.", name: name, format: inspect(format))
175 end
176
177 defp message(%{reason: :invalid_enum, name: name}) do
178 gettext("%{name} - Invalid value for enum.", name: name)
179 end
180
181 defp message(%{reason: :polymorphic_failed, type: polymorphic_type}) do
182 gettext("Failed to cast to any schema in %{polymorphic_type}",
183 polymorphic_type: polymorphic_type
184 )
185 end
186
187 defp message(%{reason: :unexpected_field, name: name}) do
188 gettext("Unexpected field: %{name}.", name: safe_string(name))
189 end
190
191 defp message(%{reason: :no_value_for_discriminator, name: field}) do
192 gettext("Value used as discriminator for `%{field}` matches no schemas.", name: field)
193 end
194
195 defp message(%{reason: :invalid_discriminator_value, name: field}) do
196 gettext("No value provided for required discriminator `%{field}`.", name: field)
197 end
198
199 defp message(%{reason: :unknown_schema, name: name}) do
200 gettext("Unknown schema: %{name}.", name: name)
201 end
202
203 defp message(%{reason: :missing_field, name: name}) do
204 gettext("Missing field: %{name}.", name: name)
205 end
206
207 defp message(%{reason: :missing_header, name: name}) do
208 gettext("Missing header: %{name}.", name: name)
209 end
210
211 defp message(%{reason: :invalid_header, name: name}) do
212 gettext("Invalid value for header: %{name}.", name: name)
213 end
214
215 defp message(%{reason: :max_properties, meta: meta}) do
216 gettext(
217 "Object property count %{property_count} is greater than maxProperties: %{max_properties}.",
218 property_count: meta.property_count,
219 max_properties: meta.max_properties
220 )
221 end
222
223 defp message(%{reason: :min_properties, meta: meta}) do
224 gettext(
225 "Object property count %{property_count} is less than minProperties: %{min_properties}",
226 property_count: meta.property_count,
227 min_properties: meta.min_properties
228 )
229 end
230
231 defp safe_string(string) do
232 to_string(string) |> String.slice(0..39)
233 end
234 end