Skip to content

Commit

Permalink
Merge pull request #8 from njausteve/switch-to-typedstruct
Browse files Browse the repository at this point in the history
  • Loading branch information
njausteve authored Sep 16, 2024
2 parents ab73ef4 + 56c071f commit 58ab31f
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .formatter.exs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"],
import_deps: [:typed_struct]
import_deps: [:typedstruct]
]
13 changes: 13 additions & 0 deletions lib/structs/field_content.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ defmodule ExPass.Structs.FieldContent do
"""

use TypedStruct

alias ExPass.Utils.Converter
alias ExPass.Utils.Validators

@typedoc """
Expand Down Expand Up @@ -117,4 +119,15 @@ defmodule ExPass.Structs.FieldContent do
"""
end
end

defimpl Jason.Encoder do
def encode(field_content, opts) do
field_content
|> Map.from_struct()
|> Enum.filter(fn {_, v} -> v != nil end)
|> Enum.map(fn {k, v} -> {Converter.camelize_key(k), v} end)
|> Enum.into(%{})
|> Jason.Encode.map(opts)
end
end
end
47 changes: 47 additions & 0 deletions lib/utils/converter.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
defmodule ExPass.Utils.Converter do
@moduledoc """
Provides utility functions for converting data structures,
focusing on key format conversions.
"""

@doc """
Converts a key (atom or string) to camelCase format.
## Examples
iex> ExPass.Utils.Converter.camelize_key(:user_name)
:userName
iex> ExPass.Utils.Converter.camelize_key("first_name")
"firstName"
"""
@spec camelize_key(atom | String.t()) :: atom | String.t()
def camelize_key(key) when is_atom(key) do
key
|> Atom.to_string()
|> camelize_string()
|> String.to_atom()
end

def camelize_key(key) when is_binary(key) do
camelize_string(key)
end

defp camelize_string(string) do
string
|> String.split("_")
|> Enum.map_join("", &capitalize_or_preserve/1)
|> lcfirst()
end

defp capitalize_or_preserve(segment) do
if String.upcase(segment) == segment do
segment
else
String.capitalize(segment)
end
end

defp lcfirst(<<first::utf8, rest::binary>>), do: String.downcase(<<first::utf8>>) <> rest
end
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ defmodule ExPass.MixProject do
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:sobelow, "~> 0.13", only: [:dev, :test], runtime: false},
{:mix_test_watch, "~> 1.0", only: [:dev, :test], runtime: false},
{:typed_struct, "~> 0.1.4"}
{:typedstruct, "~> 0.5"}
]
end
end
2 changes: 1 addition & 1 deletion mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"mix_test_watch": {:hex, :mix_test_watch, "1.2.0", "1f9acd9e1104f62f280e30fc2243ae5e6d8ddc2f7f4dc9bceb454b9a41c82b42", [:mix], [{:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "278dc955c20b3fb9a3168b5c2493c2e5cffad133548d307e0a50c7f2cfbf34f6"},
"sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"},
"typed_struct": {:hex, :typed_struct, "0.1.4", "25971ce73a8b336dedf2f80e4dafaab111af127ba4773955b66805c89e197f6a", [:mix], [], "hexpm", "c48b743b1812189431e0280941790af150ae5372f7ac51e7097c03a261a35bd6"},
"typedstruct": {:hex, :typedstruct, "0.5.3", "d68ae424251a41b81a8d0c485328ab48edbd3858f3565bbdac21b43c056fc9b4", [:make, :mix], [], "hexpm", "b53b8186701417c0b2782bf02a2db5524f879b8488f91d1d83b97d84c2943432"},
}
57 changes: 57 additions & 0 deletions test/structs/field_content_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,61 @@ defmodule ExPass.Structs.FieldContentTest do
end
end
end

describe "JSON encoding" do
test "encodes FieldContent with string attributed_value" do
json =
%{attributed_value: "Hello, World!"}
|> FieldContent.new()
|> Jason.encode!()

assert json == ~s({"attributedValue":"Hello, World!"})
end

test "encodes FieldContent with number attributed_value" do
json =
%{attributed_value: 42}
|> FieldContent.new()
|> Jason.encode!()

assert json == ~s({"attributedValue":42})
end

test "encodes FieldContent with DateTime attributed_value" do
datetime = DateTime.from_naive!(~N[2023-01-01 12:00:00], "Etc/UTC")

json =
%{attributed_value: datetime}
|> FieldContent.new()
|> Jason.encode!()

assert json == ~s({"attributedValue":"2023-01-01T12:00:00Z"})
end

test "encodes FieldContent with Date attributed_value" do
json =
%{attributed_value: ~D[2023-01-01]}
|> FieldContent.new()
|> Jason.encode!()

assert json == ~s({"attributedValue":"2023-01-01"})
end

test "FieldContent with nil attributed_value are excluded from the final encoded json" do
json =
FieldContent.new()
|> Jason.encode!()

assert json == ~s({})
end

test "encodes FieldContent with HTML attributed_value" do
json =
%{attributed_value: "<a href='http://example.com'>Link</a>"}
|> FieldContent.new()
|> Jason.encode!()

assert json == ~s({"attributedValue":"<a href='http://example.com'>Link</a>"})
end
end
end

0 comments on commit 58ab31f

Please sign in to comment.