Skip to content

Commit 2892eaf

Browse files
authored
Make Sentry.Interfaces.Request a struct (#611)
1 parent 59e8ebb commit 2892eaf

File tree

7 files changed

+82
-27
lines changed

7 files changed

+82
-27
lines changed

lib/sentry/client.ex

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ defmodule Sentry.Client do
111111
|> update_if_present(:message, &String.slice(&1, 0, @max_message_length))
112112
|> update_if_present(:breadcrumbs, fn bcs -> Enum.map(bcs, &Map.from_struct/1) end)
113113
|> update_if_present(:sdk, &Map.from_struct/1)
114+
|> update_if_present(:request, &(&1 |> Map.from_struct() |> remove_nils()))
114115
|> update_if_present(:extra, &sanitize_non_jsonable_values(&1, json_library))
115116
|> update_if_present(:user, &sanitize_non_jsonable_values(&1, json_library))
116117
|> update_if_present(:tags, &sanitize_non_jsonable_values(&1, json_library))
@@ -125,6 +126,10 @@ defmodule Sentry.Client do
125126
end)
126127
end
127128

129+
defp remove_nils(map) when is_map(map) do
130+
:maps.filter(fn _key, value -> not is_nil(value) end, map)
131+
end
132+
128133
defp sanitize_non_jsonable_values(map, json_library) do
129134
# We update the existing map instead of building a new one from scratch
130135
# due to performance reasons. See the docs for :maps.map/2.

lib/sentry/context.ex

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,23 @@ defmodule Sentry.Context do
8282
optional(atom()) => term()
8383
}
8484

85+
@typedoc """
86+
Request context.
87+
88+
See `set_request_context/1`. This map gets eventually converted
89+
into a `Sentry.Interfaces.Request` struct.
90+
"""
91+
@typedoc since: "9.0.0"
92+
@type request_context() :: %{
93+
optional(:method) => String.t() | nil,
94+
optional(:url) => String.t() | nil,
95+
optional(:query_string) => String.t() | map() | [{String.t(), String.t()}] | nil,
96+
optional(:data) => term(),
97+
optional(:cookies) => String.t() | map() | [{String.t(), String.t()}] | nil,
98+
optional(:headers) => map() | nil,
99+
optional(:env) => map() | nil
100+
}
101+
85102
@typedoc """
86103
Breadcrumb info.
87104
@@ -154,9 +171,9 @@ defmodule Sentry.Context do
154171
"""
155172
@spec get_all() :: %{
156173
user: user_context(),
174+
request: request_context(),
157175
tags: tags(),
158176
extra: extra(),
159-
request: Interfaces.request(),
160177
breadcrumbs: list()
161178
}
162179
def get_all do
@@ -296,7 +313,7 @@ defmodule Sentry.Context do
296313
}
297314
298315
"""
299-
@spec set_request_context(Interfaces.request()) :: :ok
316+
@spec set_request_context(request_context()) :: :ok
300317
def set_request_context(request_context) when is_map(request_context) do
301318
set_context(@request_key, request_context)
302319
end

lib/sentry/event.ex

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ defmodule Sentry.Event do
7070
contexts: Interfaces.context(),
7171
exception: [Interfaces.Exception.t()],
7272
message: String.t() | nil,
73-
request: Interfaces.request(),
73+
request: Interfaces.Request.t() | nil,
7474
sdk: Interfaces.SDK.t() | nil,
7575
user: Interfaces.user() | nil,
7676

@@ -113,7 +113,7 @@ defmodule Sentry.Event do
113113
modules: %{},
114114
platform: :elixir,
115115
release: nil,
116-
request: %{},
116+
request: %Interfaces.Request{},
117117
sdk: nil,
118118
server_name: nil,
119119
tags: %{},
@@ -212,7 +212,7 @@ defmodule Sentry.Event do
212212
@spec create_event([option]) :: t()
213213
when option:
214214
{:user, Interfaces.user()}
215-
| {:request, Interfaces.request()}
215+
| {:request, map()}
216216
| {:extra, Context.extra()}
217217
| {:breadcrumbs, Context.breadcrumb()}
218218
| {:tags, Context.tags()}
@@ -274,7 +274,7 @@ defmodule Sentry.Event do
274274
modules: Map.new(@deps, &{&1, to_string(Application.spec(&1, :vsn))}),
275275
original_exception: exception,
276276
release: Config.release(),
277-
request: request,
277+
request: coerce_request(request),
278278
sdk: @sdk,
279279
server_name: Config.server_name() || to_string(:net_adm.localhost()),
280280
source: source,
@@ -321,6 +321,18 @@ defmodule Sentry.Event do
321321
end
322322
end
323323

324+
@request_fields %Interfaces.Request{} |> Map.from_struct() |> Map.keys() |> MapSet.new()
325+
326+
defp coerce_request(request) do
327+
Enum.reduce(request, %Interfaces.Request{}, fn {key, value}, acc ->
328+
if key in @request_fields do
329+
Map.replace!(acc, key, value)
330+
else
331+
raise ArgumentError, "unknown field for the request interface: #{inspect(key)}"
332+
end
333+
end)
334+
end
335+
324336
@doc """
325337
Transforms an exception to a Sentry event.
326338

lib/sentry/interfaces.ex

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,6 @@ defmodule Sentry.Interfaces do
3333
optional(atom()) => term()
3434
}
3535

36-
@typedoc """
37-
The **request** interface.
38-
39-
See <https://develop.sentry.dev/sdk/event-payloads/request>.
40-
"""
41-
@typedoc since: "9.0.0"
42-
@type request() :: %{
43-
optional(:method) => String.t() | nil,
44-
optional(:url) => String.t() | nil,
45-
optional(:query_string) => String.t() | map() | [{String.t(), String.t()}] | nil,
46-
optional(:data) => term(),
47-
optional(:cookies) => String.t() | map() | [{String.t(), String.t()}] | nil,
48-
optional(:headers) => map() | nil,
49-
optional(:env) => map() | nil
50-
}
51-
5236
@typedoc """
5337
The generic **context** interface.
5438
@@ -57,6 +41,37 @@ defmodule Sentry.Interfaces do
5741
@typedoc since: "9.0.0"
5842
@type context() :: map()
5943

44+
defmodule Request do
45+
@moduledoc """
46+
The struct for the **request** interface.
47+
48+
See <https://develop.sentry.dev/sdk/event-payloads/request>.
49+
"""
50+
51+
@moduledoc since: "9.0.0"
52+
53+
@typedoc since: "9.0.0"
54+
@type t() :: %__MODULE__{
55+
method: String.t() | nil,
56+
url: String.t() | nil,
57+
query_string: String.t() | map() | [{String.t(), String.t()}] | nil,
58+
data: term(),
59+
cookies: String.t() | map() | [{String.t(), String.t()}] | nil,
60+
headers: map() | nil,
61+
env: map() | nil
62+
}
63+
64+
defstruct [
65+
:method,
66+
:url,
67+
:query_string,
68+
:data,
69+
:cookies,
70+
:headers,
71+
:env
72+
]
73+
end
74+
6075
defmodule SDK do
6176
@moduledoc """
6277
The struct for the **SDK** interface.

test/context_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ defmodule Sentry.ContextTest do
33

44
import Sentry.TestEnvironmentHelper
55

6-
alias Sentry.{Context, Event}
6+
alias Sentry.{Context, Event, Interfaces}
77

88
doctest Context, except: [add_breadcrumb: 1]
99

@@ -64,7 +64,7 @@ defmodule Sentry.ContextTest do
6464

6565
event = Event.create_event(request: %{method: "GET"})
6666

67-
assert event.request == %{url: "https://wow", method: "GET"}
67+
assert event.request == %Interfaces.Request{url: "https://wow", method: "GET"}
6868
end
6969

7070
test "passing in extra context as option overrides Context" do

test/envelope_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule Sentry.EnvelopeTest do
22
use ExUnit.Case, async: true
33

4-
alias Sentry.{Envelope, Event}
4+
alias Sentry.{Envelope, Event, Interfaces}
55

66
describe "from_binary/1" do
77
test "parses envelope with empty headers" do
@@ -62,7 +62,7 @@ defmodule Sentry.EnvelopeTest do
6262
original_exception: nil,
6363
platform: :elixir,
6464
release: nil,
65-
request: %{},
65+
request: %Interfaces.Request{},
6666
server_name: "john-linux",
6767
tags: %{},
6868
timestamp: "2021-10-09T03:53:22",

test/event_test.exs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ defmodule Sentry.EventTest do
165165

166166
assert event.user == %{id: 2, username: "foo", email: "[email protected]"}
167167

168-
assert event.request == %{
168+
assert event.request == %Interfaces.Request{
169169
method: "POST",
170170
url: "https://a.com",
171171
data: "yes"
@@ -259,6 +259,12 @@ defmodule Sentry.EventTest do
259259
assert event.source == :plug
260260
assert event.original_exception == exception
261261
end
262+
263+
test "raises if the :request option contains non-request fields" do
264+
assert_raise ArgumentError, ~r/unknown field for the request interface: :bad_key/, fn ->
265+
Event.create_event(request: %{method: "GET", bad_key: :indeed})
266+
end
267+
end
262268
end
263269

264270
test "respects the max_breadcrumbs configuration" do

0 commit comments

Comments
 (0)