From 3ef93456aaf4dde78c141c2c2702c4f12002c778 Mon Sep 17 00:00:00 2001 From: njausteve Date: Sun, 22 Sep 2024 23:16:19 +0800 Subject: [PATCH] feat: Add label to PassFieldContent --- lib/structs/field_content.ex | 18 ++++++++---- lib/utils/validators.ex | 35 +++++++++++++++++++++++ test/structs/field_content_test.exs | 44 +++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 5 deletions(-) diff --git a/lib/structs/field_content.ex b/lib/structs/field_content.ex index 0cc6d4e..96d1d52 100644 --- a/lib/structs/field_content.ex +++ b/lib/structs/field_content.ex @@ -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 @@ -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 """ @@ -130,6 +133,7 @@ defmodule ExPass.Structs.FieldContent do • ignores_time_zone • is_relative • key + • label ## Parameters @@ -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}) @@ -158,10 +162,10 @@ defmodule ExPass.Structs.FieldContent do nil iex> FieldContent.new(%{key: "field4", attributed_value: "Click here", data_detector_types: ["PKDataDetectorTypeLink"], date_style: "PKDateStyleFull", is_relative: false}) - %FieldContent{key: "field4", attributed_value: "Click here", 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: "Click here", 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 @@ -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 @@ -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 diff --git a/lib/utils/validators.ex b/lib/utils/validators.ex index 3657efe..1183c2b 100644 --- a/lib/utils/validators.ex +++ b/lib/utils/validators.ex @@ -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{]*>.*?|]*/>}, "") diff --git a/test/structs/field_content_test.exs b/test/structs/field_content_test.exs index d7aee51..5370663 100644 --- a/test/structs/field_content_test.exs +++ b/test/structs/field_content_test.exs @@ -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