Skip to content

Commit

Permalink
[TOOL-564] Add detuple/1 and destruct/1 to Utils
Browse files Browse the repository at this point in the history
  • Loading branch information
PhiMed committed Mar 13, 2024
1 parent 74a1888 commit 03649c3
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 39 deletions.
43 changes: 7 additions & 36 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,47 +6,12 @@ on:
- '*'

jobs:
old_otp_version_tests:
runs-on: ubuntu-20.04
name: OTP ${{matrix.otp}} / Elixir ${{matrix.elixir}}
strategy:
matrix:
include:
- elixir: '1.10'
otp: '22'
- elixir: '1.10'
otp: '23'
- elixir: '1.11'
otp: '22'
- elixir: '1.11'
otp: '23'
- elixir: '1.12'
otp: '22'
- elixir: '1.12'
otp: '23'
- elixir: '1.13'
otp: '22'
- elixir: '1.13'
otp: '23'
- elixir: '1.14'
otp: '23'
steps:
- uses: actions/checkout@v4
- uses: erlef/setup-beam@v1
with:
otp-version: ${{matrix.otp}}
elixir-version: ${{matrix.elixir}}
- run: mix deps.get
- run: mix test

version_tests:
runs-on: ubuntu-latest
name: OTP ${{matrix.otp}} / Elixir ${{matrix.elixir}}
strategy:
matrix:
include:
- elixir: '1.11'
otp: '24'
- elixir: '1.12'
otp: '24'
- elixir: '1.13'
Expand All @@ -65,6 +30,12 @@ jobs:
otp: '25'
- elixir: '1.15'
otp: '26'
- elixir: '1.16'
otp: '24'
- elixir: '1.16'
otp: '25'
- elixir: '1.16'
otp: '26'
steps:
- uses: actions/checkout@v4
- uses: erlef/setup-beam@v1
Expand All @@ -76,7 +47,7 @@ jobs:

all_versions_tests:
name: "All Versions Tests"
needs: [old_otp_version_tests, version_tests]
needs: version_tests
runs-on: ubuntu-latest
steps:
- run: echo "Elixir tests for many versions of Elixir and OTP have successfully completed."
Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog for v0.x

## v2.0.0 (2024-03-11)

### Enhancements

* [Airbrake.Utils] Add `detuple/1` to support CBRelay in parsing Airbrake params before transmitting
* [Airbrake.Utils] Add `destruct/1` to support CBRelay in parsing Airbrake params before transmitting

### Breaking Change

* Drop support for Elixir <1.12

## v1.0.0 (2023-10-12)

### Enhancements
Expand Down
47 changes: 47 additions & 0 deletions lib/airbrake/utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,51 @@ defmodule Airbrake.Utils do
do: {k, @filtered_value},
else: {k, filter(v, filtered_attributes)}
end

@doc """
Turns tuples into lists for JSON serialization of Airbrake payloads
"""
def detuple(%module{} = struct) do
fields = struct |> Map.from_struct() |> detuple()
struct(module, fields)
end

def detuple(map) when is_map(map) do
Enum.into(map, %{}, fn {k, v} -> {detuple(k), detuple(v)} end)
end

def detuple(list) when is_list(list) do
Enum.map(list, &detuple/1)
end

def detuple(tuple) when is_tuple(tuple) do
tuple |> Tuple.to_list() |> detuple()
end

def detuple(other) do
other
end

@doc """
Recursively breaks down structs for JSON serialization of Airbrake payloads
"""
def destruct(%_module{} = struct) do
struct |> Map.from_struct() |> destruct()
end

def destruct(map) when is_map(map) do
Enum.into(map, %{}, fn {k, v} -> {destruct(k), destruct(v)} end)
end

def destruct(list) when is_list(list) do
Enum.map(list, &destruct/1)
end

def destruct(tuple) when is_tuple(tuple) do
tuple |> Tuple.to_list() |> destruct() |> List.to_tuple()
end

def destruct(other) do
other
end
end
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ defmodule Airbrake.Mixfile do
[
app: :airbrake_client,
version: "1.0.0",
elixir: "~> 1.10",
elixir: "~> 1.12",
elixirc_paths: elixirc_paths(Mix.env()),
package: package(),
aliases: aliases(),
Expand Down
88 changes: 86 additions & 2 deletions test/airbrake/utils_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ defmodule Airbrake.UtilsTest do

alias Airbrake.Utils

@moduletag :focus

defmodule Struct do
defstruct [:baz, :qux]
end
Expand Down Expand Up @@ -48,4 +46,90 @@ defmodule Airbrake.UtilsTest do
}
end
end

describe "detuple/1" do
test "converts tuples to lists" do
input = %Struct{baz: {1, 2}, qux: {:ok, "foobar"}}
expected = %Struct{baz: [1, 2], qux: [:ok, "foobar"]}

assert Utils.detuple(input) == expected
end

test "returns de-tupled data when input contains deeply nested tuples" do
input = %Struct{baz: [1, 2, {3, 4}], qux: %{foo: %{bar: {9, 9, 9, 9}}}}
expected = %Struct{baz: [1, 2, [3, 4]], qux: %{foo: %{bar: [9, 9, 9, 9]}}}

assert Utils.detuple(input) == expected
end

test "returns detupled data when input is a map with tuples" do
input = %{baz: {1, 2}, qux: {:ok, "sucess"}}
expected = %{baz: [1, 2], qux: [:ok, "sucess"]}

assert Utils.detuple(input) == expected
end

test "returns detupled data when input is a list with tuples" do
input = ["foo", {:ok, "sucess"}]
expected = ["foo", [:ok, "sucess"]]

assert Utils.detuple(input) == expected
end

test "returns a list when input is nested tuples" do
input = {:ok, {:error, "something"}}
expected = [:ok, [:error, "something"]]

assert Utils.detuple(input) == expected
end

property "scalars are returned unchanged" do
check all scalar <- one_of([integer(), float(), string(:utf8), atom(:alphanumeric), boolean()]) do
assert Utils.detuple(scalar) == scalar
end
end
end

describe "destruct/1" do
test "converts structs to maps" do
input = %{a: %Struct{baz: 100, qux: 200}, b: "foo", c: %Struct{baz: 1, qux: 2}}
expected = %{a: %{baz: 100, qux: 200}, b: "foo", c: %{baz: 1, qux: 2}}

assert Utils.destruct(input) == expected
end

test "returns de-structed data when input contains deeply nested structs" do
input = %Struct{baz: [1, 2, {3, %Struct{baz: "bar"}}], qux: %{foo: %{bar: %Struct{baz: "bar"}, qux: "foo"}}}
expected = %{baz: [1, 2, {3, %{baz: "bar", qux: nil}}], qux: %{foo: %{bar: %{baz: "bar", qux: nil}, qux: "foo"}}}

assert Utils.destruct(input) == expected
end

test "returns de-structed data when input is a list containing a struct" do
input = ["foo", %Struct{baz: 100, qux: 200}]
expected = ["foo", %{baz: 100, qux: 200}]

assert Utils.destruct(input) == expected
end

test "returns the de-structed data when input is a tuple containing a struct" do
input = {:ok, %Struct{baz: 100, qux: 200}}
expected = {:ok, %{baz: 100, qux: 200}}

assert Utils.destruct(input) == expected
end

test "returns a map of the original data when input contains nested structs" do
input = %Struct{baz: 100, qux: %Struct{baz: 100, qux: 200}}
expected = %{baz: 100, qux: %{baz: 100, qux: 200}}

assert Utils.destruct(input) == expected
end

property "scalars are returned unchanged" do
check all scalar <- one_of([integer(), float(), string(:utf8), atom(:alphanumeric), boolean()]) do
assert Utils.destruct(scalar) == scalar
end
end
end
end

0 comments on commit 03649c3

Please sign in to comment.