Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions lib/renatils/map.ex
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,36 @@ defmodule Renatils.Map do
end

def stringify_keys(v), do: v

@doc """
Merges `map2` onto `map1` if `condition` is truthy.
"""
@spec merge_if(map(), map(), boolean()) ::
map()
def merge_if(map1, map2, condition) do
if condition do
Map.merge(map1, map2)
else
map1
end
end

@doc """
Recursively iterates over the given input, transforming any structs it finds into a map.
"""
@spec destructify(term) ::
term
def destructify(struct) when is_struct(struct) do
struct
|> Map.from_struct()
|> destructify()
end

def destructify(map) when is_map(map) do
Enum.reduce(map, %{}, fn {k, v}, acc ->
Map.put(acc, destructify(k), destructify(v))
end)
end

def destructify(v), do: v
end
13 changes: 13 additions & 0 deletions lib/renatils/timer.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
defmodule Renatils.Timer do
@doc """
Formats the duration input (precision = microseconds) to a human-friendly format.
"""
@spec format_duration(microseconds :: integer) ::
binary()
def format_duration(d) when d < 1000, do: "#{d}μs"
def format_duration(d) when d < 10_000, do: "#{Float.round(d / 1000, 2)}ms"
def format_duration(d) when d < 100_000, do: "#{Float.round(d / 1000, 1)}ms"
def format_duration(d) when d < 1_000_000, do: "#{trunc(d / 1000)}ms"
def format_duration(d) when d < 10_000_000, do: "#{Float.round(d / 1_000_000, 1)}s"
def format_duration(d), do: "#{trunc(d / 1_000_000)}s"
end
77 changes: 77 additions & 0 deletions test/renatils/map_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,81 @@ defmodule Renatils.MapTest do
end)
end
end

describe "merge_if/3" do
test "merges both maps if condition is true" do
assert %{a: 1, b: 2} == Renatils.Map.merge_if(%{a: 1}, %{b: 2}, true)
assert %{a: 2, b: 3} == Renatils.Map.merge_if(%{a: 1}, %{a: 2, b: 3}, true)
end

test "performs a no-op if condition is false" do
assert %{a: 1} == Renatils.Map.merge_if(%{a: 1}, %{b: 2}, false)
end
end

describe "destructify/1" do
test "destructifies a struct" do
# Let's use DateTime as example struct
dt = DateTime.utc_now()
assert is_struct(dt)

# It's converted to map, identical to `Map.from_struct/1`
assert Map.from_struct(dt) == Renatils.Map.destructify(dt)
end

test "recursively destructifies a struct" do
dt = DateTime.utc_now()

# At first we have a nested map, with `now.is.the.time` being a struct
nested_map = %{now: %{is: %{the: %{time: dt}}}}
assert is_struct(nested_map.now.is.the.time)

# Now we have a new map. where `now.is.the.time` is a map
new_map = Renatils.Map.destructify(nested_map)
refute is_struct(new_map.now.is.the.time)
assert is_map(new_map.now.is.the.time)

# As a second example, we now have nested structs
nested_struct =
URI.new!("foo")
|> Map.put(:host, URI.new!("bar"))

# Both structs were destructified
new_map = Renatils.Map.destructify(nested_struct)
assert %{path: "foo", host: %{path: "bar"}} = new_map
refute is_struct(new_map)
refute is_struct(new_map.host)
end

test "destructifies a struct that is a map key" do
# Did you know structs can be a map *key*?
dt = DateTime.utc_now()
map = %{dt => :now}

# Initially, the map's only key is a struct
[key] = Map.keys(map)
assert is_struct(key)

# Once it's been destructified, now the map's only key is a map
new_map = Renatils.Map.destructify(map)
[new_key] = Map.keys(new_map)
refute is_struct(new_key)
assert is_map(new_key)
end

test "performs a no-op on regular maps and values" do
[
%{foo: :bar},
%{"abc" => %{xyz: 123}},
-50,
"foo",
false,
1.0
]
|> Enum.each(fn value ->
# Non-struct values remain unchanged
assert value == Renatils.Map.destructify(value)
end)
end
end
end
22 changes: 22 additions & 0 deletions test/renatils/timer_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
defmodule Renatils.TimerTest do
use ExUnit.Case, async: true

describe "format_duration/1" do
test "formats the input based on its order of magnitude" do
[
{5, "5μs"},
{51, "51μs"},
{510, "510μs"},
{5_100, "5.1ms"},
{51_100, "51.1ms"},
{511_100, "511ms"},
{5_111_100, "5.1s"},
{51_111_100, "51s"},
{510_111_100, "510s"}
]
|> Enum.each(fn {input, expected_format} ->
assert expected_format == Renatils.Timer.format_duration(input)
end)
end
end
end