Skip to content

Commit

Permalink
Merge pull request #159 from njausteve/add-label-to-pass-content-field
Browse files Browse the repository at this point in the history
feat: Add label to PassFieldContent
  • Loading branch information
njausteve authored Sep 22, 2024
2 parents 1700494 + 3ef9345 commit b5d46d8
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 5 deletions.
18 changes: 13 additions & 5 deletions lib/structs/field_content.ex
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ defmodule ExPass.Structs.FieldContent do
This key doesn't affect the pass relevance calculation.
- `key`: A unique key that identifies a field in the pass. This field is required.
- `label`: The text for a field label. This field is optional.
"""

use TypedStruct
Expand Down Expand Up @@ -115,6 +117,7 @@ defmodule ExPass.Structs.FieldContent do
field :ignores_time_zone, boolean(), default: nil
field :is_relative, boolean(), default: nil
field :key, String.t(), enforce: true
field :label, String.t(), default: nil
end

@doc """
Expand All @@ -130,6 +133,7 @@ defmodule ExPass.Structs.FieldContent do
• ignores_time_zone
• is_relative
• key
• label
## Parameters
Expand All @@ -146,10 +150,10 @@ defmodule ExPass.Structs.FieldContent do
## Examples
iex> FieldContent.new(%{key: "field1", attributed_value: "Hello, World!"})
%FieldContent{key: "field1", attributed_value: "Hello, World!", change_message: nil, currency_code: nil, data_detector_types: nil, date_style: nil, ignores_time_zone: nil, is_relative: nil}
%FieldContent{key: "field1", attributed_value: "Hello, World!", change_message: nil, currency_code: nil, data_detector_types: nil, date_style: nil, ignores_time_zone: nil, is_relative: nil, label: nil}
iex> FieldContent.new(%{key: "field2", attributed_value: 42, data_detector_types: ["PKDataDetectorTypePhoneNumber"], date_style: "PKDateStyleShort", ignores_time_zone: true, is_relative: false})
%FieldContent{key: "field2", attributed_value: 42, change_message: nil, currency_code: nil, data_detector_types: ["PKDataDetectorTypePhoneNumber"], date_style: "PKDateStyleShort", ignores_time_zone: true, is_relative: false}
%FieldContent{key: "field2", attributed_value: 42, change_message: nil, currency_code: nil, data_detector_types: ["PKDataDetectorTypePhoneNumber"], date_style: "PKDateStyleShort", ignores_time_zone: true, is_relative: false, label: nil}
iex> datetime = ~U[2023-04-15 14:30:00Z]
iex> field_content = FieldContent.new(%{key: "field3", attributed_value: datetime, currency_code: "USD", date_style: "PKDateStyleLong", ignores_time_zone: true, is_relative: true})
Expand All @@ -158,10 +162,10 @@ defmodule ExPass.Structs.FieldContent do
nil
iex> FieldContent.new(%{key: "field4", attributed_value: "<a href='http://example.com'>Click here</a>", data_detector_types: ["PKDataDetectorTypeLink"], date_style: "PKDateStyleFull", is_relative: false})
%FieldContent{key: "field4", attributed_value: "<a href='http://example.com'>Click here</a>", change_message: nil, currency_code: nil, data_detector_types: ["PKDataDetectorTypeLink"], date_style: "PKDateStyleFull", ignores_time_zone: nil, is_relative: false}
%FieldContent{key: "field4", attributed_value: "<a href='http://example.com'>Click here</a>", change_message: nil, currency_code: nil, data_detector_types: ["PKDataDetectorTypeLink"], date_style: "PKDateStyleFull", ignores_time_zone: nil, is_relative: false, label: nil}
iex> FieldContent.new(%{key: "field5", attributed_value: "No detectors", data_detector_types: [], change_message: "Updated to %@", ignores_time_zone: true, is_relative: true})
%FieldContent{key: "field5", attributed_value: "No detectors", change_message: "Updated to %@", currency_code: nil, data_detector_types: [], date_style: nil, ignores_time_zone: true, is_relative: true}
iex> FieldContent.new(%{key: "field5", attributed_value: "No detectors", data_detector_types: [], change_message: "Updated to %@", ignores_time_zone: true, is_relative: true, label: "Field Label"})
%FieldContent{key: "field5", attributed_value: "No detectors", change_message: "Updated to %@", currency_code: nil, data_detector_types: [], date_style: nil, ignores_time_zone: true, is_relative: true, label: "Field Label"}
"""
@spec new(map()) :: %__MODULE__{}
def new(attrs \\ %{}) do
Expand All @@ -176,6 +180,7 @@ defmodule ExPass.Structs.FieldContent do
|> validate(:ignores_time_zone, &Validators.validate_boolean_field(&1, :ignores_time_zone))
|> validate(:is_relative, &Validators.validate_boolean_field(&1, :is_relative))
|> validate(:key, &Validators.validate_required_string(&1, :key))
|> validate(:label, &Validators.validate_optional_string(&1, :label))

struct!(__MODULE__, attrs)
end
Expand Down Expand Up @@ -217,6 +222,9 @@ defmodule ExPass.Structs.FieldContent do
:key ->
"key is a required field and must be a non-empty string"

:label ->
"label must be a string if provided"

_ ->
""
end
Expand Down
35 changes: 35 additions & 0 deletions lib/utils/validators.ex
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,41 @@ defmodule ExPass.Utils.Validators do
def validate_required_string(value, _field_name) when is_binary(value), do: :ok
def validate_required_string(_, field_name), do: {:error, "#{field_name} must be a string"}

@doc """
Validates an optional string field.
The field must be a string or nil.
## Parameters
* `value` - The value to validate.
* `field_name` - The name of the field being validated as an atom.
## Returns
* `:ok` if the value is a valid string or nil.
* `{:error, reason}` if the value is not valid, where reason is a string explaining the error.
## Examples
iex> validate_optional_string("valid string", :label)
:ok
iex> validate_optional_string("", :label)
:ok
iex> validate_optional_string(nil, :label)
:ok
iex> validate_optional_string(123, :label)
{:error, "label must be a string"}
"""
@spec validate_optional_string(String.t() | nil, atom()) :: :ok | {:error, String.t()}
def validate_optional_string(nil, _field_name), do: :ok
def validate_optional_string(value, _field_name) when is_binary(value), do: :ok
def validate_optional_string(_, field_name), do: {:error, "#{field_name} must be a string"}

defp contains_unsupported_html_tags?(string) do
# Remove all valid anchor tags
string_without_anchors = String.replace(string, ~r{<a\s[^>]*>.*?</a>|<a\s[^>]*/>}, "")
Expand Down
44 changes: 44 additions & 0 deletions test/structs/field_content_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -363,4 +363,48 @@ defmodule ExPass.Structs.FieldContentTest do
assert encoded =~ ~s("key":"trimmed_key")
end
end

describe "label" do
test "new/1 creates a valid FieldContent struct with a label" do
result = FieldContent.new(%{key: "test_key", label: "Test Label"})

assert %FieldContent{key: "test_key", label: "Test Label"} = result
encoded = Jason.encode!(result)
assert encoded =~ ~s("key":"test_key")
assert encoded =~ ~s("label":"Test Label")
end

test "new/1 creates a valid FieldContent struct without a label" do
result = FieldContent.new(%{key: "test_key"})

assert %FieldContent{key: "test_key", label: nil} = result
encoded = Jason.encode!(result)
assert encoded =~ ~s("key":"test_key")
refute encoded =~ "label"
end

test "new/1 trims whitespace from label" do
result = FieldContent.new(%{key: "test_key", label: " Trimmed Label "})

assert %FieldContent{key: "test_key", label: "Trimmed Label"} = result
encoded = Jason.encode!(result)
assert encoded =~ ~s("key":"test_key")
assert encoded =~ ~s("label":"Trimmed Label")
end

test "new/1 raises ArgumentError when label is not a string" do
assert_raise ArgumentError, ~r/label must be a string/, fn ->
FieldContent.new(%{key: "test_key", label: 123})
end
end

test "new/1 allows an empty string for label" do
result = FieldContent.new(%{key: "test_key", label: ""})

assert %FieldContent{key: "test_key", label: ""} = result
encoded = Jason.encode!(result)
assert encoded =~ ~s("key":"test_key")
assert encoded =~ ~s("label":"")
end
end
end

0 comments on commit b5d46d8

Please sign in to comment.